1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.classfile.editor;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.CodeAttribute;
25import proguard.classfile.attribute.visitor.AttributeVisitor;
26import proguard.classfile.instruction.*;
27import proguard.classfile.instruction.visitor.InstructionVisitor;
28import proguard.classfile.util.SimplifiedVisitor;
29
30/**
31 * This InstructionVisitor writes out the instructions that it visits,
32 * collecting instructions that have to be widened. As an AttributeVisitor,
33 * it then applies the collected changes. The process will be repeated
34 * recursively, if necessary. The caller still has to update the frame sizes.
35 *
36 * @author Eric Lafortune
37 */
38public class InstructionWriter
39extends      SimplifiedVisitor
40implements   InstructionVisitor,
41             AttributeVisitor
42{
43    //*
44    private static final boolean DEBUG = false;
45    /*/
46    public  static       boolean DEBUG = false;
47    //*/
48
49
50    private int codeLength;
51
52    private CodeAttributeEditor codeAttributeEditor;
53
54
55    /**
56     * Resets the accumulated code.
57     * @param codeLength the length of the code that will be edited next.
58     */
59    public void reset(int codeLength)
60    {
61        this.codeLength = codeLength;
62
63        if (codeAttributeEditor != null)
64        {
65            codeAttributeEditor.reset(codeLength);
66        }
67    }
68
69
70    /**
71     * Extends the size of the accumulated code.
72     * @param codeLength the length of the code that will be edited next.
73     */
74    public void extend(int codeLength)
75    {
76        this.codeLength = codeLength;
77
78        if (codeAttributeEditor != null)
79        {
80            codeAttributeEditor.extend(codeLength);
81        }
82    }
83
84
85    // Implementations for InstructionVisitor.
86
87    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
88    {
89        // Try to write out the instruction.
90        // Simple instructions should always fit.
91        simpleInstruction.write(codeAttribute, offset);
92    }
93
94
95    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
96    {
97        try
98        {
99            // Try to write out the instruction.
100            constantInstruction.write(codeAttribute, offset);
101        }
102        catch (IllegalArgumentException exception)
103        {
104            // Create a new constant instruction that will fit.
105            Instruction replacementInstruction =
106                new ConstantInstruction(constantInstruction.opcode,
107                                        constantInstruction.constantIndex,
108                                        constantInstruction.constant);
109
110            if (DEBUG)
111            {
112                System.out.println("  "+constantInstruction.toString(offset)+" will be widened to "+replacementInstruction.toString());
113            }
114
115            replaceInstruction(offset, replacementInstruction);
116
117            // Write out a dummy constant instruction for now.
118            constantInstruction.constantIndex = 0;
119            constantInstruction.constant      = 0;
120            constantInstruction.write(codeAttribute, offset);
121        }
122    }
123
124
125    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
126    {
127        try
128        {
129            // Try to write out the instruction.
130            variableInstruction.write(codeAttribute, offset);
131        }
132        catch (IllegalArgumentException exception)
133        {
134            // Create a new variable instruction that will fit.
135            Instruction replacementInstruction =
136                new VariableInstruction(variableInstruction.opcode,
137                                        variableInstruction.variableIndex,
138                                        variableInstruction.constant);
139
140            replaceInstruction(offset, replacementInstruction);
141
142            if (DEBUG)
143            {
144                System.out.println("  "+variableInstruction.toString(offset)+" will be widened to "+replacementInstruction.toString());
145            }
146
147            // Write out a dummy variable instruction for now.
148            variableInstruction.variableIndex = 0;
149            variableInstruction.constant      = 0;
150            variableInstruction.write(codeAttribute, offset);
151        }
152    }
153
154
155    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
156    {
157        try
158        {
159            // Try to write out the instruction.
160            branchInstruction.write(codeAttribute, offset);
161        }
162        catch (IllegalArgumentException exception)
163        {
164            // Create a new unconditional branch that will fit.
165            Instruction replacementInstruction =
166                new BranchInstruction(InstructionConstants.OP_GOTO_W,
167                                      branchInstruction.branchOffset);
168
169            // Create a new instruction that will fit.
170            switch (branchInstruction.opcode)
171            {
172                default:
173                {
174                    // Create a new branch instruction that will fit.
175                    replacementInstruction =
176                        new BranchInstruction(branchInstruction.opcode,
177                                              branchInstruction.branchOffset);
178
179                    break;
180                }
181
182                // Some special cases, for which a wide branch doesn't exist.
183                case InstructionConstants.OP_IFEQ:
184                case InstructionConstants.OP_IFNE:
185                case InstructionConstants.OP_IFLT:
186                case InstructionConstants.OP_IFGE:
187                case InstructionConstants.OP_IFGT:
188                case InstructionConstants.OP_IFLE:
189                case InstructionConstants.OP_IFICMPEQ:
190                case InstructionConstants.OP_IFICMPNE:
191                case InstructionConstants.OP_IFICMPLT:
192                case InstructionConstants.OP_IFICMPGE:
193                case InstructionConstants.OP_IFICMPGT:
194                case InstructionConstants.OP_IFICMPLE:
195                case InstructionConstants.OP_IFACMPEQ:
196                case InstructionConstants.OP_IFACMPNE:
197                {
198                    // Insert the complementary conditional branch.
199                    Instruction complementaryConditionalBranch =
200                        new BranchInstruction((byte)(((branchInstruction.opcode+1) ^ 1) - 1),
201                                              (1+2) + (1+4));
202
203                    insertBeforeInstruction(offset, complementaryConditionalBranch);
204
205                    // Create a new unconditional branch that will fit.
206                    break;
207                }
208
209                case InstructionConstants.OP_IFNULL:
210                case InstructionConstants.OP_IFNONNULL:
211                {
212                    // Insert the complementary conditional branch.
213                    Instruction complementaryConditionalBranch =
214                        new BranchInstruction((byte)(branchInstruction.opcode ^ 1),
215                                              (1+2) + (1+4));
216
217                    insertBeforeInstruction(offset, complementaryConditionalBranch);
218
219                    // Create a new unconditional branch that will fit.
220                    break;
221                }
222            }
223
224            if (DEBUG)
225            {
226                System.out.println("  "+branchInstruction.toString(offset)+" will be widened to "+replacementInstruction.toString());
227            }
228
229            replaceInstruction(offset, replacementInstruction);
230
231            // Write out a dummy branch instruction for now.
232            branchInstruction.branchOffset = 0;
233            branchInstruction.write(codeAttribute, offset);
234        }
235    }
236
237
238    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
239    {
240        // Try to write out the instruction.
241        // Switch instructions should always fit.
242        switchInstruction.write(codeAttribute, offset);
243    }
244
245
246    // Implementations for AttributeVisitor.
247
248    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
249    {
250        // Avoid doing any work if nothing is changing anyway.
251        if (codeAttributeEditor != null)
252        {
253            if (DEBUG)
254            {
255                System.out.println("InstructionWriter: widening instructions in "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
256            }
257
258            // Apply the collected expansions.
259            codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
260
261            // Don't keep the editor around. We're assuming it won't be needed
262            // very often, so we don't want to be resetting it all the time.
263            codeAttributeEditor = null;
264        }
265    }
266
267
268    // Small utility methods.
269
270    /**
271     * Remembers to place the given instruction right before the instruction
272     * at the given offset.
273     */
274    private void insertBeforeInstruction(int instructionOffset, Instruction instruction)
275    {
276        ensureCodeAttributeEditor();
277
278        // Replace the instruction.
279        codeAttributeEditor.insertBeforeInstruction(instructionOffset, instruction);
280    }
281
282
283    /**
284     * Remembers to replace the instruction at the given offset by the given
285     * instruction.
286     */
287    private void replaceInstruction(int instructionOffset, Instruction instruction)
288    {
289        ensureCodeAttributeEditor();
290
291        // Replace the instruction.
292        codeAttributeEditor.replaceInstruction(instructionOffset, instruction);
293    }
294
295
296    /**
297     * Remembers to place the given instruction right after the instruction
298     * at the given offset.
299     */
300    private void insertAfterInstruction(int instructionOffset, Instruction instruction)
301    {
302        ensureCodeAttributeEditor();
303
304        // Replace the instruction.
305        codeAttributeEditor.insertAfterInstruction(instructionOffset, instruction);
306    }
307
308
309    /**
310     * Makes sure there is a code attribute editor for the given code attribute.
311     */
312    private void ensureCodeAttributeEditor()
313    {
314        if (codeAttributeEditor == null)
315        {
316            codeAttributeEditor = new CodeAttributeEditor(false, true);
317            codeAttributeEditor.reset(codeLength);
318        }
319    }
320}
321