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