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