1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 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.optimize.peephole;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.*;
27import proguard.classfile.constant.visitor.ConstantVisitor;
28import proguard.classfile.editor.*;
29import proguard.classfile.instruction.*;
30import proguard.classfile.instruction.visitor.InstructionVisitor;
31import proguard.classfile.util.*;
32import proguard.classfile.visitor.MemberVisitor;
33import proguard.optimize.info.*;
34
35import java.util.Stack;
36
37/**
38 * This AttributeVisitor inlines short methods or methods that are only invoked
39 * once, in the code attributes that it visits.
40 *
41 * @author Eric Lafortune
42 */
43public class MethodInliner
44extends      SimplifiedVisitor
45implements   AttributeVisitor,
46             InstructionVisitor,
47             ConstantVisitor,
48             MemberVisitor
49{
50    private static final int MAXIMUM_INLINED_CODE_LENGTH       = Integer.parseInt(System.getProperty("maximum.inlined.code.length",      "8"));
51    private static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "8000"));
52    private static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000"));
53    private static final int MAXIMUM_CODE_EXPANSION            = 2;
54    private static final int MAXIMUM_EXTRA_CODE_LENGTH         = 128;
55
56    //*
57    private static final boolean DEBUG = false;
58    /*/
59    private static       boolean DEBUG = true;
60    //*/
61
62
63    private final boolean            microEdition;
64    private final boolean            allowAccessModification;
65    private final boolean            inlineSingleInvocations;
66    private final InstructionVisitor extraInlinedInvocationVisitor;
67
68    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
69    private final AccessMethodMarker    accessMethodMarker    = new AccessMethodMarker();
70    private final CatchExceptionMarker  catchExceptionMarker  = new CatchExceptionMarker();
71    private final StackSizeComputer     stackSizeComputer     = new StackSizeComputer();
72
73    private ProgramClass       targetClass;
74    private ProgramMethod      targetMethod;
75    private ConstantAdder      constantAdder;
76    private ExceptionInfoAdder exceptionInfoAdder;
77    private int                estimatedResultingCodeLength;
78    private boolean            inlining;
79    private Stack              inliningMethods              = new Stack();
80    private boolean            emptyInvokingStack;
81    private int                uninitializedObjectCount;
82    private int                variableOffset;
83    private boolean            inlined;
84    private boolean            inlinedAny;
85
86
87    /**
88     * Creates a new MethodInliner.
89     * @param microEdition            indicates whether the resulting code is
90     *                                targeted at Java Micro Edition.
91     * @param allowAccessModification indicates whether the access modifiers of
92     *                                classes and class members can be changed
93     *                                in order to inline methods.
94     * @param inlineSingleInvocations indicates whether the single invocations
95     *                                should be inlined, or, alternatively,
96     *                                short methods.
97     */
98    public MethodInliner(boolean microEdition,
99                         boolean allowAccessModification,
100                         boolean inlineSingleInvocations)
101    {
102        this(microEdition,
103             allowAccessModification,
104             inlineSingleInvocations,
105             null);
106    }
107
108
109    /**
110     * Creates a new MethodInliner.
111     * @param microEdition            indicates whether the resulting code is
112     *                                targeted at Java Micro Edition.
113     * @param allowAccessModification indicates whether the access modifiers of
114     *                                classes and class members can be changed
115     *                                in order to inline methods.
116     * @param inlineSingleInvocations indicates whether the single invocations
117     *                                should be inlined, or, alternatively,
118     *                                short methods.
119     * @param extraInlinedInvocationVisitor an optional extra visitor for all
120     *                                      inlined invocation instructions.
121     */
122    public MethodInliner(boolean            microEdition,
123                         boolean            allowAccessModification,
124                         boolean            inlineSingleInvocations,
125                         InstructionVisitor extraInlinedInvocationVisitor)
126    {
127        this.microEdition                  = microEdition;
128        this.allowAccessModification       = allowAccessModification;
129        this.inlineSingleInvocations       = inlineSingleInvocations;
130        this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor;
131    }
132
133
134    // Implementations for AttributeVisitor.
135
136    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
137
138
139    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
140    {
141        if (!inlining)
142        {
143//            codeAttributeComposer.DEBUG = DEBUG =
144//                clazz.getName().equals("abc/Def") &&
145//                method.getName(clazz).equals("abc");
146
147            targetClass                  = (ProgramClass)clazz;
148            targetMethod                 = (ProgramMethod)method;
149            constantAdder                = new ConstantAdder(targetClass);
150            exceptionInfoAdder           = new ExceptionInfoAdder(targetClass, codeAttributeComposer);
151            estimatedResultingCodeLength = codeAttribute.u4codeLength;
152            inliningMethods.clear();
153            uninitializedObjectCount     = method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ? 1 : 0;
154            inlinedAny                   = false;
155            codeAttributeComposer.reset();
156            stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute);
157
158            // Append the body of the code.
159            copyCode(clazz, method, codeAttribute);
160
161            targetClass   = null;
162            targetMethod  = null;
163            constantAdder = null;
164
165            // Update the code attribute if any code has been inlined.
166            if (inlinedAny)
167            {
168                codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
169
170                // Update the accessing flags.
171                codeAttribute.instructionsAccept(clazz, method, accessMethodMarker);
172
173                // Update the exception catching flags.
174                catchExceptionMarker.visitCodeAttribute(clazz, method, codeAttribute);
175            }
176        }
177
178        // Only inline the method if it is invoked once or if it is short.
179        else if ((inlineSingleInvocations ?
180                      MethodInvocationMarker.getInvocationCount(method) == 1 :
181                      codeAttribute.u4codeLength <= MAXIMUM_INLINED_CODE_LENGTH) &&
182                 estimatedResultingCodeLength + codeAttribute.u4codeLength <
183                 (microEdition ?
184                     MAXIMUM_RESULTING_CODE_LENGTH_JME :
185                     MAXIMUM_RESULTING_CODE_LENGTH_JSE))
186        {
187            if (DEBUG)
188            {
189                System.out.println("MethodInliner: inlining ["+
190                                   clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] in ["+
191                                   targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]");
192            }
193
194            // Ignore the removal of the original method invocation,
195            // the addition of the parameter setup, and
196            // the modification of a few inlined instructions.
197            estimatedResultingCodeLength += codeAttribute.u4codeLength;
198
199            // Append instructions to store the parameters.
200            storeParameters(clazz, method);
201
202            // Inline the body of the code.
203            copyCode(clazz, method, codeAttribute);
204
205            inlined    = true;
206            inlinedAny = true;
207        }
208    }
209
210
211    /**
212     * Appends instructions to pop the parameters for the given method, storing
213     * them in new local variables.
214     */
215    private void storeParameters(Clazz clazz, Method method)
216    {
217        String descriptor = method.getDescriptor(clazz);
218
219        boolean isStatic =
220            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
221
222        // Count the number of parameters, taking into account their categories.
223        int parameterCount  = ClassUtil.internalMethodParameterCount(descriptor);
224        int parameterSize   = ClassUtil.internalMethodParameterSize(descriptor);
225        int parameterOffset = isStatic ? 0 : 1;
226
227        // Store the parameter types.
228        String[] parameterTypes = new String[parameterSize];
229
230        InternalTypeEnumeration internalTypeEnumeration =
231            new InternalTypeEnumeration(descriptor);
232
233        for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++)
234        {
235            String parameterType = internalTypeEnumeration.nextType();
236            parameterTypes[parameterIndex] = parameterType;
237            if (ClassUtil.internalTypeSize(parameterType) == 2)
238            {
239                parameterIndex++;
240            }
241        }
242
243        codeAttributeComposer.beginCodeFragment(parameterSize+1);
244
245        // Go over the parameter types backward, storing the stack entries
246        // in their corresponding variables.
247        for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--)
248        {
249            String parameterType = parameterTypes[parameterIndex];
250            if (parameterType != null)
251            {
252                byte opcode;
253                switch (parameterType.charAt(0))
254                {
255                    case ClassConstants.INTERNAL_TYPE_BOOLEAN:
256                    case ClassConstants.INTERNAL_TYPE_BYTE:
257                    case ClassConstants.INTERNAL_TYPE_CHAR:
258                    case ClassConstants.INTERNAL_TYPE_SHORT:
259                    case ClassConstants.INTERNAL_TYPE_INT:
260                        opcode = InstructionConstants.OP_ISTORE;
261                        break;
262
263                    case ClassConstants.INTERNAL_TYPE_LONG:
264                        opcode = InstructionConstants.OP_LSTORE;
265                        break;
266
267                    case ClassConstants.INTERNAL_TYPE_FLOAT:
268                        opcode = InstructionConstants.OP_FSTORE;
269                        break;
270
271                    case ClassConstants.INTERNAL_TYPE_DOUBLE:
272                        opcode = InstructionConstants.OP_DSTORE;
273                        break;
274
275                    default:
276                        opcode = InstructionConstants.OP_ASTORE;
277                        break;
278                }
279
280                codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1,
281                                                        new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex).shrink());
282            }
283        }
284
285        // Put the 'this' reference in variable 0 (plus offset).
286        if (!isStatic)
287        {
288            codeAttributeComposer.appendInstruction(parameterSize,
289                                                    new VariableInstruction(InstructionConstants.OP_ASTORE, variableOffset).shrink());
290        }
291
292        codeAttributeComposer.endCodeFragment();
293    }
294
295
296    /**
297     * Appends the code of the given code attribute.
298     */
299    private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute)
300    {
301        // The code may expand, due to expanding constant and variable
302        // instructions.
303        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
304
305        // Copy the instructions.
306        codeAttribute.instructionsAccept(clazz, method, this);
307
308        // Copy the exceptions.
309        codeAttribute.exceptionsAccept(clazz, method, exceptionInfoAdder);
310
311        // Append a label just after the code.
312        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
313
314        codeAttributeComposer.endCodeFragment();
315    }
316
317
318    // Implementations for InstructionVisitor.
319
320    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
321    {
322        codeAttributeComposer.appendInstruction(offset, instruction.shrink());
323    }
324
325
326    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
327    {
328        // Are we inlining this instruction?
329        if (inlining)
330        {
331            // Replace any return instructions by branches to the end of the code.
332            switch (simpleInstruction.opcode)
333            {
334                case InstructionConstants.OP_IRETURN:
335                case InstructionConstants.OP_LRETURN:
336                case InstructionConstants.OP_FRETURN:
337                case InstructionConstants.OP_DRETURN:
338                case InstructionConstants.OP_ARETURN:
339                case InstructionConstants.OP_RETURN:
340                    // Are we not at the last instruction?
341                    if (offset < codeAttribute.u4codeLength-1)
342                    {
343                        // Replace the return instruction by a branch instruction.
344                        Instruction branchInstruction =
345                            new BranchInstruction(InstructionConstants.OP_GOTO_W,
346                                                  codeAttribute.u4codeLength - offset);
347
348                        codeAttributeComposer.appendInstruction(offset,
349                                                                branchInstruction.shrink());
350                    }
351                    else
352                    {
353                        // Just leave out the instruction, but put in a label,
354                        // for the sake of any other branch instructions.
355                        codeAttributeComposer.appendLabel(offset);
356                    }
357
358                    return;
359            }
360        }
361
362        codeAttributeComposer.appendInstruction(offset, simpleInstruction.shrink());
363    }
364
365
366    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
367    {
368        // Are we inlining this instruction?
369        if (inlining)
370        {
371            // Update the variable index.
372            variableInstruction.variableIndex += variableOffset;
373        }
374
375        codeAttributeComposer.appendInstruction(offset, variableInstruction.shrink());
376    }
377
378
379    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
380    {
381        // Is it a method invocation?
382        switch (constantInstruction.opcode)
383        {
384            case InstructionConstants.OP_NEW:
385                uninitializedObjectCount++;
386                break;
387
388            case InstructionConstants.OP_INVOKEVIRTUAL:
389            case InstructionConstants.OP_INVOKESPECIAL:
390            case InstructionConstants.OP_INVOKESTATIC:
391            case InstructionConstants.OP_INVOKEINTERFACE:
392                // See if we can inline it.
393                inlined = false;
394
395                // Append a label, in case the invocation will be inlined.
396                codeAttributeComposer.appendLabel(offset);
397
398                emptyInvokingStack =
399                    !inlining &&
400                    stackSizeComputer.isReachable(offset) &&
401                    stackSizeComputer.getStackSize(offset) == 0;
402
403                variableOffset += codeAttribute.u2maxLocals;
404
405                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
406
407                variableOffset -= codeAttribute.u2maxLocals;
408
409                // Was the method inlined?
410                if (inlined)
411                {
412                    if (extraInlinedInvocationVisitor != null)
413                    {
414                        extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
415                    }
416
417                    // The invocation itself is no longer necessary.
418                    return;
419                }
420
421                break;
422        }
423
424        // Are we inlining this instruction?
425        if (inlining)
426        {
427            // Make sure the constant is present in the constant pool of the
428            // target class.
429            constantInstruction.constantIndex =
430                constantAdder.addConstant(clazz, constantInstruction.constantIndex);
431        }
432
433        codeAttributeComposer.appendInstruction(offset, constantInstruction.shrink());
434    }
435
436
437    // Implementations for ConstantVisitor.
438
439    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) {}
440
441
442    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
443    {
444        methodrefConstant.referencedMemberAccept(this);
445    }
446
447
448    // Implementations for MemberVisitor.
449
450    public void visitAnyMember(Clazz Clazz, Member member) {}
451
452
453    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
454    {
455        int accessFlags = programMethod.getAccessFlags();
456
457        if (// Only inline the method if it is private, static, or final.
458            (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE |
459                            ClassConstants.INTERNAL_ACC_STATIC  |
460                            ClassConstants.INTERNAL_ACC_FINAL)) != 0                               &&
461
462            // Only inline the method if it is not synchronized, etc.
463            (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED |
464                            ClassConstants.INTERNAL_ACC_NATIVE       |
465                            ClassConstants.INTERNAL_ACC_INTERFACE    |
466                            ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0                            &&
467
468            // Don't inline an <init> method, except in an <init> method in the
469            // same class.
470//            (!programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
471//             (programClass.equals(targetClass) &&
472//              targetMethod.getName(targetClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))) &&
473            !programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)  &&
474
475            // Don't inline a method into itself.
476            (!programMethod.equals(targetMethod) ||
477             !programClass.equals(targetClass))                                                    &&
478
479            // Only inline the method if it isn't recursing.
480            !inliningMethods.contains(programMethod)                                               &&
481
482            // Only inline the method if its target class has at least the
483            // same version number as the source class, in order to avoid
484            // introducing incompatible constructs.
485            targetClass.u4version >= programClass.u4version                                        &&
486
487            // Only inline the method if it doesn't invoke a super method, or if
488            // it is in the same class.
489            (!SuperInvocationMarker.invokesSuperMethods(programMethod) ||
490             programClass.equals(targetClass))                                                     &&
491
492            // Only inline the method if it doesn't branch backward while there
493            // are uninitialized objects.
494            (!BackwardBranchMarker.branchesBackward(programMethod) ||
495             uninitializedObjectCount == 0)                                                        &&
496
497            // Only inline if the code access of the inlined method allows it.
498            (allowAccessModification ||
499             ((!AccessMethodMarker.accessesPrivateCode(programMethod) ||
500               programClass.equals(targetClass)) &&
501
502              (!AccessMethodMarker.accessesPackageCode(programMethod) ||
503               ClassUtil.internalPackageName(programClass.getName()).equals(
504               ClassUtil.internalPackageName(targetClass.getName())))))                            &&
505
506//               (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
507//                targetClass.extends_(programClass) ||
508//                targetClass.implements_(programClass)) ||
509            (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
510             programClass.equals(targetClass))                                                     &&
511
512            // Only inline the method if it doesn't catch exceptions, or if it
513            // is invoked with an empty stack.
514            (!CatchExceptionMarker.catchesExceptions(programMethod) ||
515             emptyInvokingStack)                                                                   &&
516
517            // Only inline the method if it comes from the same class or from
518            // a class with a static initializer.
519            (programClass.equals(targetClass) ||
520             programClass.findMethod(ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
521                                     ClassConstants.INTERNAL_METHOD_TYPE_CLINIT) == null))
522        {
523//            System.out.print("MethodInliner: inlining ");
524//            programMethod.accept(programClass, new SimpleClassPrinter(true));
525//            System.out.print("               in       ");
526//            targetMethod.accept(targetClass, new SimpleClassPrinter(true));
527//
528//            System.out.println("  Private:   "+
529//                               (!AccessMethodMarker.accessesPrivateCode(programMethod) ||
530//                                programClass.equals(targetClass)));
531//
532//            System.out.println("  Package:   "+
533//                               (!AccessMethodMarker.accessesPackageCode(programMethod) ||
534//                                ClassUtil.internalPackageName(programClass.getName()).equals(
535//                                ClassUtil.internalPackageName(targetClass.getName()))));
536//
537//            System.out.println("  Protected: "+
538//                               ((!AccessMethodMarker.accessesProtectedCode(programMethod) ||
539//                                 targetClass.extends_(programClass) ||
540//                                 targetClass.implements_(programClass)) ||
541//                                ClassUtil.internalPackageName(programClass.getName()).equals(
542//                                ClassUtil.internalPackageName(targetClass.getName()))));
543
544            boolean oldInlining = inlining;
545            inlining = true;
546            inliningMethods.push(programMethod);
547
548            // Inline the method body.
549            programMethod.attributesAccept(programClass, this);
550
551            // Update the optimization information of the target method.
552            MethodOptimizationInfo info =
553                MethodOptimizationInfo.getMethodOptimizationInfo(targetMethod);
554            if (info != null)
555            {
556                info.merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod));
557            }
558
559            inlining = oldInlining;
560            inliningMethods.pop();
561        }
562        else if (programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
563        {
564            uninitializedObjectCount--;
565        }
566    }
567}
568