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.preverify;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.editor.CodeAttributeComposer;
27import proguard.classfile.instruction.*;
28import proguard.classfile.instruction.visitor.InstructionVisitor;
29import proguard.classfile.util.SimplifiedVisitor;
30import proguard.classfile.visitor.*;
31import proguard.optimize.peephole.BranchTargetFinder;
32
33/**
34 * This AttributeVisitor inlines local subroutines (jsr/ret) in the code
35 * attributes that it visits.
36 *
37 * @author Eric Lafortune
38 */
39public class CodeSubroutineInliner
40extends      SimplifiedVisitor
41implements   AttributeVisitor,
42             InstructionVisitor,
43             ExceptionInfoVisitor
44{
45    //*
46    private static final boolean DEBUG = false;
47    /*/
48    private static       boolean DEBUG = System.getProperty("csi") != null;
49    //*/
50
51
52    private final BranchTargetFinder    branchTargetFinder    = new BranchTargetFinder();
53    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true, true);
54
55    private ExceptionInfoVisitor subroutineExceptionInliner = this;
56    private int                  clipStart                  = 0;
57    private int                  clipEnd                    = Integer.MAX_VALUE;
58
59
60    // Implementations for AttributeVisitor.
61
62    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
63
64
65    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
66    {
67//        DEBUG =
68//            clazz.getName().equals("abc/Def") &&
69//            method.getName(clazz).equals("abc");
70//        CodeAttributeComposer.DEBUG = DEBUG;
71
72        // TODO: Remove this when the subroutine inliner has stabilized.
73        // Catch any unexpected exceptions from the actual visiting method.
74        try
75        {
76            // Process the code.
77            visitCodeAttribute0(clazz, method, codeAttribute);
78        }
79        catch (RuntimeException ex)
80        {
81            System.err.println("Unexpected error while inlining subroutines:");
82            System.err.println("  Class       = ["+clazz.getName()+"]");
83            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
84            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
85
86            if (DEBUG)
87            {
88                method.accept(clazz, new ClassPrinter());
89            }
90
91            throw ex;
92        }
93    }
94
95
96    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
97    {
98        branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
99
100        // Don't bother if there aren't any subroutines anyway.
101        if (!branchTargetFinder.containsSubroutines())
102        {
103            return;
104        }
105
106        if (DEBUG)
107        {
108            System.out.println("SubroutineInliner: processing ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
109        }
110
111        // Append the body of the code.
112        codeAttributeComposer.reset();
113        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
114
115        // Copy the non-subroutine instructions.
116        int offset  = 0;
117        while (offset < codeAttribute.u4codeLength)
118        {
119            Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
120            int instructionLength = instruction.length(offset);
121
122            // Is this returning subroutine?
123            if (branchTargetFinder.isSubroutine(offset) &&
124                branchTargetFinder.isSubroutineReturning(offset))
125            {
126                // Skip the subroutine.
127                if (DEBUG)
128                {
129                    System.out.println("  Skipping original subroutine instruction "+instruction.toString(offset));
130                }
131
132                // Append a label at this offset instead.
133                codeAttributeComposer.appendLabel(offset);
134            }
135            else
136            {
137                // Copy the instruction, inlining any subroutine call recursively.
138                instruction.accept(clazz, method, codeAttribute, offset, this);
139            }
140
141            offset += instructionLength;
142        }
143
144        // Copy the exceptions. Note that exceptions with empty try blocks
145        // are automatically removed.
146        codeAttribute.exceptionsAccept(clazz,
147                                       method,
148                                       subroutineExceptionInliner);
149
150        if (DEBUG)
151        {
152            System.out.println("  Appending label after code at ["+offset+"]");
153        }
154
155        // Append a label just after the code.
156        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
157
158        // End and update the code attribute.
159        codeAttributeComposer.endCodeFragment();
160        codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
161    }
162
163
164    /**
165     * Appends the specified subroutine.
166     */
167    private void inlineSubroutine(Clazz         clazz,
168                                  Method        method,
169                                  CodeAttribute codeAttribute,
170                                  int           subroutineInvocationOffset,
171                                  int           subroutineStart)
172    {
173        int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart);
174
175        if (DEBUG)
176        {
177            System.out.println("  Inlining subroutine ["+subroutineStart+" -> "+subroutineEnd+"] at ["+subroutineInvocationOffset+"]");
178        }
179
180        // Don't go inlining exceptions that are already applicable to this
181        // subroutine invocation.
182        ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner;
183        int                  oldClipStart                  = clipStart;
184        int                  oldClipEnd                    = clipEnd;
185
186        subroutineExceptionInliner =
187            new ExceptionExcludedOffsetFilter(subroutineInvocationOffset,
188                                              subroutineExceptionInliner);
189        clipStart = subroutineStart;
190        clipEnd   = subroutineEnd;
191
192        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
193
194        // Copy the subroutine instructions, inlining any subroutine calls
195        // recursively.
196        codeAttribute.instructionsAccept(clazz,
197                                         method,
198                                         subroutineStart,
199                                         subroutineEnd,
200                                         this);
201
202        if (DEBUG)
203        {
204            System.out.println("    Appending label after inlined subroutine at ["+subroutineEnd+"]");
205        }
206
207        // Append a label just after the code.
208        codeAttributeComposer.appendLabel(subroutineEnd);
209
210        // Inline the subroutine exceptions.
211        codeAttribute.exceptionsAccept(clazz,
212                                       method,
213                                       subroutineStart,
214                                       subroutineEnd,
215                                       subroutineExceptionInliner);
216
217        // We can again inline exceptions that are applicable to this
218        // subroutine invocation.
219        subroutineExceptionInliner = oldSubroutineExceptionInliner;
220        clipStart                  = oldClipStart;
221        clipEnd                    = oldClipEnd;
222
223        codeAttributeComposer.endCodeFragment();
224    }
225
226
227    // Implementations for InstructionVisitor.
228
229    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
230    {
231        // Append the instruction.
232        codeAttributeComposer.appendInstruction(offset, instruction);
233    }
234
235
236    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
237    {
238        byte opcode = variableInstruction.opcode;
239        if (opcode == InstructionConstants.OP_RET)
240        {
241            // Is the return instruction the last instruction of the subroutine?
242            if (branchTargetFinder.subroutineEnd(offset) == offset + variableInstruction.length(offset))
243            {
244                if (DEBUG)
245                {
246                    System.out.println("    Replacing subroutine return at ["+offset+"] by a label");
247                }
248
249                // Append a label at this offset instead of the subroutine return.
250                codeAttributeComposer.appendLabel(offset);
251            }
252            else
253            {
254                if (DEBUG)
255                {
256                    System.out.println("    Replacing subroutine return at ["+offset+"] by a simple branch");
257                }
258
259                // Replace the instruction by a branch.
260                Instruction replacementInstruction =
261                    new BranchInstruction(InstructionConstants.OP_GOTO,
262                                          branchTargetFinder.subroutineEnd(offset) - offset);
263
264                codeAttributeComposer.appendInstruction(offset, replacementInstruction);
265            }
266        }
267        else if (branchTargetFinder.isSubroutineStart(offset))
268        {
269            if (DEBUG)
270            {
271                System.out.println("    Replacing first subroutine instruction at ["+offset+"] by a label");
272            }
273
274            // Append a label at this offset instead of saving the subroutine
275            // return address.
276            codeAttributeComposer.appendLabel(offset);
277        }
278        else
279        {
280            // Append the instruction.
281            codeAttributeComposer.appendInstruction(offset, variableInstruction);
282        }
283    }
284
285
286    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
287    {
288        byte opcode = branchInstruction.opcode;
289        if (opcode == InstructionConstants.OP_JSR ||
290            opcode == InstructionConstants.OP_JSR_W)
291        {
292            int branchOffset = branchInstruction.branchOffset;
293            int branchTarget = offset + branchOffset;
294
295            // Is the subroutine ever returning?
296            if (branchTargetFinder.isSubroutineReturning(branchTarget))
297            {
298                // Append a label at this offset instead of the subroutine invocation.
299                codeAttributeComposer.appendLabel(offset);
300
301                // Inline the invoked subroutine.
302                inlineSubroutine(clazz,
303                                 method,
304                                 codeAttribute,
305                                 offset,
306                                 branchTarget);
307            }
308            else
309            {
310                if (DEBUG)
311                {
312                    System.out.println("Replacing subroutine invocation at ["+offset+"] by a simple branch");
313                }
314
315                // Replace the subroutine invocation by a simple branch.
316                Instruction replacementInstruction =
317                    new BranchInstruction(InstructionConstants.OP_GOTO,
318                                          branchOffset);
319
320                codeAttributeComposer.appendInstruction(offset, replacementInstruction);
321            }
322        }
323        else
324        {
325            // Append the instruction.
326            codeAttributeComposer.appendInstruction(offset, branchInstruction);
327        }
328    }
329
330
331    // Implementations for ExceptionInfoVisitor.
332
333    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
334    {
335        int startPC   = Math.max(exceptionInfo.u2startPC, clipStart);
336        int endPC     = Math.min(exceptionInfo.u2endPC,   clipEnd);
337        int handlerPC = exceptionInfo.u2handlerPC;
338        int catchType = exceptionInfo.u2catchType;
339
340        // Exclude any subroutine invocations that jump out of the try block,
341        // by adding a try block before (and later on, after) each invocation.
342        for (int offset = startPC; offset < endPC; offset++)
343        {
344            if (branchTargetFinder.isSubroutineInvocation(offset))
345            {
346                Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
347                int instructionLength = instruction.length(offset);
348
349                // Is it a subroutine invocation?
350                if (!exceptionInfo.isApplicable(offset + ((BranchInstruction)instruction).branchOffset))
351                {
352                    if (DEBUG)
353                    {
354                        System.out.println("  Appending extra exception ["+startPC+" -> "+offset+"] -> "+handlerPC);
355                    }
356
357                    // Append a try block that ends before the subroutine invocation.
358                    codeAttributeComposer.appendException(new ExceptionInfo(startPC,
359                                                                            offset,
360                                                                            handlerPC,
361                                                                            catchType));
362
363                    // The next try block will start after the subroutine invocation.
364                    startPC = offset + instructionLength;
365                }
366            }
367        }
368
369        if (DEBUG)
370        {
371            if (startPC == exceptionInfo.u2startPC &&
372                endPC   == exceptionInfo.u2endPC)
373            {
374                System.out.println("  Appending exception ["+startPC+" -> "+endPC+"] -> "+handlerPC);
375            }
376            else
377            {
378                System.out.println("  Appending clipped exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+"] ~> ["+startPC+" -> "+endPC+"] -> "+handlerPC);
379            }
380        }
381
382        // Append the exception. Note that exceptions with empty try blocks
383        // are automatically ignored.
384        codeAttributeComposer.appendException(new ExceptionInfo(startPC,
385                                                                endPC,
386                                                                handlerPC,
387                                                                catchType));
388    }
389}
390