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.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.KeepMarker;
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
159            ex.printStackTrace();
160            System.err.println("Not inlining this method");
161
162            if (DEBUG)
163            {
164                targetMethod.accept(targetClass, new ClassPrinter());
165                if (inlining)
166                {
167                    method.accept(clazz, new ClassPrinter());
168                }
169
170                throw ex;
171            }
172        }
173    }
174
175
176    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
177    {
178        if (!inlining)
179        {
180//            codeAttributeComposer.DEBUG = DEBUG =
181//                clazz.getName().equals("abc/Def") &&
182//                method.getName(clazz).equals("abc");
183
184            targetClass                  = (ProgramClass)clazz;
185            targetMethod                 = (ProgramMethod)method;
186            constantAdder                = new ConstantAdder(targetClass);
187            exceptionInfoAdder           = new ExceptionInfoAdder(targetClass, codeAttributeComposer);
188            estimatedResultingCodeLength = codeAttribute.u4codeLength;
189            inliningMethods.clear();
190            uninitializedObjectCount     = method.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT) ? 1 : 0;
191            inlinedAny                   = false;
192            codeAttributeComposer.reset();
193            stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute);
194
195            // Append the body of the code.
196            copyCode(clazz, method, codeAttribute);
197
198            targetClass   = null;
199            targetMethod  = null;
200            constantAdder = null;
201
202            // Update the code attribute if any code has been inlined.
203            if (inlinedAny)
204            {
205                codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
206
207                // Update the accessing flags.
208                codeAttribute.instructionsAccept(clazz, method, accessMethodMarker);
209
210                // Update the exception catching flags.
211                catchExceptionMarker.visitCodeAttribute(clazz, method, codeAttribute);
212            }
213        }
214
215        // Only inline the method if it is invoked once or if it is short.
216        else if ((inlineSingleInvocations ?
217                      MethodInvocationMarker.getInvocationCount(method) == 1 :
218                      codeAttribute.u4codeLength <= MAXIMUM_INLINED_CODE_LENGTH) &&
219                 estimatedResultingCodeLength + codeAttribute.u4codeLength <
220                 (microEdition ?
221                     MAXIMUM_RESULTING_CODE_LENGTH_JME :
222                     MAXIMUM_RESULTING_CODE_LENGTH_JSE))
223        {
224            if (DEBUG)
225            {
226                System.out.println("MethodInliner: inlining ["+
227                                   clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] in ["+
228                                   targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]");
229            }
230
231            // Ignore the removal of the original method invocation,
232            // the addition of the parameter setup, and
233            // the modification of a few inlined instructions.
234            estimatedResultingCodeLength += codeAttribute.u4codeLength;
235
236            // Append instructions to store the parameters.
237            storeParameters(clazz, method);
238
239            // Inline the body of the code.
240            copyCode(clazz, method, codeAttribute);
241
242            inlined    = true;
243            inlinedAny = true;
244        }
245    }
246
247
248    /**
249     * Appends instructions to pop the parameters for the given method, storing
250     * them in new local variables.
251     */
252    private void storeParameters(Clazz clazz, Method method)
253    {
254        String descriptor = method.getDescriptor(clazz);
255
256        boolean isStatic =
257            (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0;
258
259        // Count the number of parameters, taking into account their categories.
260        int parameterCount  = ClassUtil.internalMethodParameterCount(descriptor);
261        int parameterSize   = ClassUtil.internalMethodParameterSize(descriptor);
262        int parameterOffset = isStatic ? 0 : 1;
263
264        // Store the parameter types.
265        String[] parameterTypes = new String[parameterSize];
266
267        InternalTypeEnumeration internalTypeEnumeration =
268            new InternalTypeEnumeration(descriptor);
269
270        for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++)
271        {
272            String parameterType = internalTypeEnumeration.nextType();
273            parameterTypes[parameterIndex] = parameterType;
274            if (ClassUtil.internalTypeSize(parameterType) == 2)
275            {
276                parameterIndex++;
277            }
278        }
279
280        codeAttributeComposer.beginCodeFragment(parameterSize+1);
281
282        // Go over the parameter types backward, storing the stack entries
283        // in their corresponding variables.
284        for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--)
285        {
286            String parameterType = parameterTypes[parameterIndex];
287            if (parameterType != null)
288            {
289                byte opcode;
290                switch (parameterType.charAt(0))
291                {
292                    case ClassConstants.TYPE_BOOLEAN:
293                    case ClassConstants.TYPE_BYTE:
294                    case ClassConstants.TYPE_CHAR:
295                    case ClassConstants.TYPE_SHORT:
296                    case ClassConstants.TYPE_INT:
297                        opcode = InstructionConstants.OP_ISTORE;
298                        break;
299
300                    case ClassConstants.TYPE_LONG:
301                        opcode = InstructionConstants.OP_LSTORE;
302                        break;
303
304                    case ClassConstants.TYPE_FLOAT:
305                        opcode = InstructionConstants.OP_FSTORE;
306                        break;
307
308                    case ClassConstants.TYPE_DOUBLE:
309                        opcode = InstructionConstants.OP_DSTORE;
310                        break;
311
312                    default:
313                        opcode = InstructionConstants.OP_ASTORE;
314                        break;
315                }
316
317                codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1,
318                                                        new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex));
319            }
320        }
321
322        // Put the 'this' reference in variable 0 (plus offset).
323        if (!isStatic)
324        {
325            codeAttributeComposer.appendInstruction(parameterSize,
326                                                    new VariableInstruction(InstructionConstants.OP_ASTORE, variableOffset));
327        }
328
329        codeAttributeComposer.endCodeFragment();
330    }
331
332
333    /**
334     * Appends the code of the given code attribute.
335     */
336    private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute)
337    {
338        // The code may expand, due to expanding constant and variable
339        // instructions.
340        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
341
342        // Copy the instructions.
343        codeAttribute.instructionsAccept(clazz, method, this);
344
345        // Append a label just after the code.
346        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
347
348        // Copy the exceptions.
349        codeAttribute.exceptionsAccept(clazz, method, exceptionInfoAdder);
350
351        codeAttributeComposer.endCodeFragment();
352    }
353
354
355    // Implementations for InstructionVisitor.
356
357    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
358    {
359        codeAttributeComposer.appendInstruction(offset, instruction);
360    }
361
362
363    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
364    {
365        // Are we inlining this instruction?
366        if (inlining)
367        {
368            // Replace any return instructions by branches to the end of the code.
369            switch (simpleInstruction.opcode)
370            {
371                case InstructionConstants.OP_IRETURN:
372                case InstructionConstants.OP_LRETURN:
373                case InstructionConstants.OP_FRETURN:
374                case InstructionConstants.OP_DRETURN:
375                case InstructionConstants.OP_ARETURN:
376                case InstructionConstants.OP_RETURN:
377                    // Are we not at the last instruction?
378                    if (offset < codeAttribute.u4codeLength-1)
379                    {
380                        // Replace the return instruction by a branch instruction.
381                        Instruction branchInstruction =
382                            new BranchInstruction(InstructionConstants.OP_GOTO_W,
383                                                  codeAttribute.u4codeLength - offset);
384
385                        codeAttributeComposer.appendInstruction(offset,
386                                                                branchInstruction);
387                    }
388                    else
389                    {
390                        // Just leave out the instruction, but put in a label,
391                        // for the sake of any other branch instructions.
392                        codeAttributeComposer.appendLabel(offset);
393                    }
394
395                    return;
396            }
397        }
398
399        codeAttributeComposer.appendInstruction(offset, simpleInstruction);
400    }
401
402
403    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
404    {
405        // Are we inlining this instruction?
406        if (inlining)
407        {
408            // Update the variable index.
409            variableInstruction.variableIndex += variableOffset;
410        }
411
412        codeAttributeComposer.appendInstruction(offset, variableInstruction);
413    }
414
415
416    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
417    {
418        // Is it a method invocation?
419        switch (constantInstruction.opcode)
420        {
421            case InstructionConstants.OP_NEW:
422                uninitializedObjectCount++;
423                break;
424
425            case InstructionConstants.OP_INVOKEVIRTUAL:
426            case InstructionConstants.OP_INVOKESPECIAL:
427            case InstructionConstants.OP_INVOKESTATIC:
428            case InstructionConstants.OP_INVOKEINTERFACE:
429                // See if we can inline it.
430                inlined = false;
431
432                // Append a label, in case the invocation will be inlined.
433                codeAttributeComposer.appendLabel(offset);
434
435                emptyInvokingStack =
436                    !inlining &&
437                    stackSizeComputer.isReachable(offset) &&
438                    stackSizeComputer.getStackSize(offset) == 0;
439
440                variableOffset += codeAttribute.u2maxLocals;
441
442                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
443
444                variableOffset -= codeAttribute.u2maxLocals;
445
446                // Was the method inlined?
447                if (inlined)
448                {
449                    if (extraInlinedInvocationVisitor != null)
450                    {
451                        extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
452                    }
453
454                    // The invocation itself is no longer necessary.
455                    return;
456                }
457
458                break;
459        }
460
461        // Are we inlining this instruction?
462        if (inlining)
463        {
464            // Make sure the constant is present in the constant pool of the
465            // target class.
466            constantInstruction.constantIndex =
467                constantAdder.addConstant(clazz, constantInstruction.constantIndex);
468        }
469
470        codeAttributeComposer.appendInstruction(offset, constantInstruction);
471    }
472
473
474    // Implementations for ConstantVisitor.
475
476    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
477    {
478        refConstant.referencedMemberAccept(this);
479    }
480
481
482    // Implementations for MemberVisitor.
483
484    public void visitAnyMember(Clazz Clazz, Member member) {}
485
486
487    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
488    {
489        int accessFlags = programMethod.getAccessFlags();
490
491        if (// Don't inline methods that must be preserved.
492            !KeepMarker.isKept(programMethod)                                                     &&
493
494            // Only inline the method if it is private, static, or final.
495            // This currently precludes default interface methods, because
496            // they can't be final.
497            (accessFlags & (ClassConstants.ACC_PRIVATE |
498                            ClassConstants.ACC_STATIC  |
499                            ClassConstants.ACC_FINAL)) != 0                                       &&
500
501            // Only inline the method if it is not synchronized, etc.
502            (accessFlags & (ClassConstants.ACC_SYNCHRONIZED |
503                            ClassConstants.ACC_NATIVE       |
504                            ClassConstants.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.METHOD_NAME_INIT) ||
509//             (programClass.equals(targetClass) &&
510//              targetMethod.getName(targetClass).equals(ClassConstants.METHOD_NAME_INIT))) &&
511            !programMethod.getName(programClass).equals(ClassConstants.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 a
526            // dynamic method, or if it is in the same class.
527            (!SuperInvocationMarker.invokesSuperMethods(programMethod) &&
528             !DynamicInvocationMarker.invokesDynamically(programMethod) ||
529             programClass.equals(targetClass))                                                    &&
530
531            // Only inline the method if it doesn't branch backward while there
532            // are uninitialized objects.
533            (!BackwardBranchMarker.branchesBackward(programMethod) ||
534             uninitializedObjectCount == 0)                                                       &&
535
536            // Only inline if the code access of the inlined method allows it.
537            (allowAccessModification ||
538             ((!AccessMethodMarker.accessesPrivateCode(programMethod) ||
539               programClass.equals(targetClass)) &&
540
541              (!AccessMethodMarker.accessesPackageCode(programMethod) ||
542               ClassUtil.internalPackageName(programClass.getName()).equals(
543               ClassUtil.internalPackageName(targetClass.getName())))))                           &&
544
545//               (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
546//                targetClass.extends_(programClass) ||
547//                targetClass.implements_(programClass)) ||
548            (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
549             programClass.equals(targetClass))                                                    &&
550
551            // Only inline the method if it doesn't catch exceptions, or if it
552            // is invoked with an empty stack.
553            (!CatchExceptionMarker.catchesExceptions(programMethod) ||
554             emptyInvokingStack)                                                                  &&
555
556            // Only inline the method if it comes from the a class with at most
557            // a subset of the initialized superclasses.
558            ((accessFlags & ClassConstants.ACC_STATIC) == 0 ||
559             programClass.equals(targetClass)                        ||
560             initializedSuperClasses(targetClass).containsAll(initializedSuperClasses(programClass))))
561        {
562            boolean oldInlining = inlining;
563            inlining = true;
564            inliningMethods.push(programMethod);
565
566            // Inline the method body.
567            programMethod.attributesAccept(programClass, this);
568
569            // Update the optimization information of the target method.
570            MethodOptimizationInfo info =
571                MethodOptimizationInfo.getMethodOptimizationInfo(targetMethod);
572            if (info != null)
573            {
574                info.merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod));
575            }
576
577            inlining = oldInlining;
578            inliningMethods.pop();
579        }
580        else if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT))
581        {
582            uninitializedObjectCount--;
583        }
584    }
585
586
587    /**
588     * Returns the set of superclasses and interfaces that are initialized.
589     */
590    private Set initializedSuperClasses(Clazz clazz)
591    {
592        Set set = new HashSet();
593
594        // Visit all superclasses and interfaces, collecting the ones that have
595        // static initializers.
596        clazz.hierarchyAccept(true, true, true, false,
597                              new StaticInitializerContainingClassFilter(
598                              new ClassCollector(set)));
599
600        return set;
601    }
602}
603