001: /*
002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
003: * of Java bytecode.
004: *
005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
006: *
007: * This library is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: * This library is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
015: * for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public License
018: * along with this library; if not, write to the Free Software Foundation,
019: * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: package proguard.optimize.peephole;
022:
023: import proguard.classfile.*;
024: import proguard.classfile.attribute.*;
025: import proguard.classfile.attribute.visitor.*;
026: import proguard.classfile.constant.*;
027: import proguard.classfile.constant.visitor.ConstantVisitor;
028: import proguard.classfile.editor.*;
029: import proguard.classfile.instruction.*;
030: import proguard.classfile.instruction.visitor.InstructionVisitor;
031: import proguard.classfile.util.*;
032: import proguard.classfile.visitor.MemberVisitor;
033: import proguard.optimize.info.*;
034:
035: import java.util.Stack;
036:
037: /**
038: * This AttributeVisitor inlines short methods or methods that are only invoked
039: * once, in the code attributes that it visits.
040: *
041: * @author Eric Lafortune
042: */
043: public class MethodInliner extends SimplifiedVisitor implements
044: AttributeVisitor, InstructionVisitor, ExceptionInfoVisitor,
045: ConstantVisitor, MemberVisitor {
046: private static final int MAXIMUM_INLINED_CODE_LENGTH = Integer
047: .parseInt(System.getProperty("maximum.inlined.code.length",
048: "8"));
049: private static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer
050: .parseInt(System.getProperty(
051: "maximum.resulting.code.length", "8000"));
052: private static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer
053: .parseInt(System.getProperty(
054: "maximum.resulting.code.length", "2000"));
055: private static final int MAXIMUM_CODE_EXPANSION = 2;
056: private static final int MAXIMUM_EXTRA_CODE_LENGTH = 128;
057:
058: //*
059: private static final boolean DEBUG = false;
060: /*/
061: private static boolean DEBUG = true;
062: //*/
063:
064: private final boolean microEdition;
065: private final boolean allowAccessModification;
066: private final boolean inlineSingleInvocations;
067: private final InstructionVisitor extraInlinedInvocationVisitor;
068:
069: private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
070: private final AccessMethodMarker accessMethodMarker = new AccessMethodMarker();
071: private final CatchExceptionMarker catchExceptionMarker = new CatchExceptionMarker();
072: private final ConstantAdder constantAdder = new ConstantAdder();
073: private final StackSizeComputer stackSizeComputer = new StackSizeComputer();
074:
075: private ProgramClass targetClass;
076: private ProgramMethod targetMethod;
077: private int estimatedResultingCodeLength;
078: private boolean inlining;
079: private Stack inliningMethods = new Stack();
080: private boolean emptyInvokingStack;
081: private int uninitializedObjectCount;
082: private int variableOffset;
083: private boolean inlined;
084: private boolean inlinedAny;
085:
086: /**
087: * Creates a new MethodInliner.
088: * @param microEdition indicates whether the resulting code is
089: * targeted at Java Micro Edition.
090: * @param allowAccessModification indicates whether the access modifiers of
091: * classes and class members can be changed
092: * in order to inline methods.
093: * @param inlineSingleInvocations indicates whether the single invocations
094: * should be inlined, or, alternatively,
095: * short methods.
096: */
097: public MethodInliner(boolean microEdition,
098: boolean allowAccessModification,
099: boolean inlineSingleInvocations) {
100: this (microEdition, allowAccessModification,
101: inlineSingleInvocations, null);
102: }
103:
104: /**
105: * Creates a new MethodInliner.
106: * @param microEdition indicates whether the resulting code is
107: * targeted at Java Micro Edition.
108: * @param allowAccessModification indicates whether the access modifiers of
109: * classes and class members can be changed
110: * in order to inline methods.
111: * @param inlineSingleInvocations indicates whether the single invocations
112: * should be inlined, or, alternatively,
113: * short methods.
114: * @param extraInlinedInvocationVisitor an optional extra visitor for all
115: * inlined invocation instructions.
116: */
117: public MethodInliner(boolean microEdition,
118: boolean allowAccessModification,
119: boolean inlineSingleInvocations,
120: InstructionVisitor extraInlinedInvocationVisitor) {
121: this .microEdition = microEdition;
122: this .allowAccessModification = allowAccessModification;
123: this .inlineSingleInvocations = inlineSingleInvocations;
124: this .extraInlinedInvocationVisitor = extraInlinedInvocationVisitor;
125: }
126:
127: // Implementations for AttributeVisitor.
128:
129: public void visitAnyAttribute(Clazz clazz, Attribute attribute) {
130: }
131:
132: public void visitCodeAttribute(Clazz clazz, Method method,
133: CodeAttribute codeAttribute) {
134: if (!inlining) {
135: // codeAttributeComposer.DEBUG = DEBUG =
136: // clazz.getName().equals("abc/Def") &&
137: // method.getName(clazz).equals("abc");
138:
139: targetClass = (ProgramClass) clazz;
140: targetMethod = (ProgramMethod) method;
141: estimatedResultingCodeLength = codeAttribute.u4codeLength;
142: inliningMethods.clear();
143: uninitializedObjectCount = method.getName(clazz).equals(
144: ClassConstants.INTERNAL_METHOD_NAME_INIT) ? 1 : 0;
145: inlinedAny = false;
146: codeAttributeComposer.reset();
147: constantAdder.setTargetClass(targetClass);
148: stackSizeComputer.visitCodeAttribute(clazz, method,
149: codeAttribute);
150:
151: // Append the body of the code.
152: copyCode(clazz, method, codeAttribute);
153:
154: targetClass = null;
155: targetMethod = null;
156: constantAdder.setTargetClass(null);
157:
158: // Update the code attribute if any code has been inlined.
159: if (inlinedAny) {
160: codeAttributeComposer.visitCodeAttribute(clazz, method,
161: codeAttribute);
162:
163: // Update the accessing flags.
164: codeAttribute.instructionsAccept(clazz, method,
165: accessMethodMarker);
166:
167: // Update the exception catching flags.
168: catchExceptionMarker.visitCodeAttribute(clazz, method,
169: codeAttribute);
170: }
171: }
172:
173: // Only inline the method if it is invoked once or if it is short.
174: else if ((inlineSingleInvocations ? MethodInvocationMarker
175: .getInvocationCount(method) == 1
176: : codeAttribute.u4codeLength <= MAXIMUM_INLINED_CODE_LENGTH)
177: && estimatedResultingCodeLength
178: + codeAttribute.u4codeLength < (microEdition ? MAXIMUM_RESULTING_CODE_LENGTH_JME
179: : MAXIMUM_RESULTING_CODE_LENGTH_JSE)) {
180: if (DEBUG) {
181: System.out
182: .println("MethodInliner: inlining ["
183: + clazz.getName()
184: + "."
185: + method.getName(clazz)
186: + method.getDescriptor(clazz)
187: + "] in ["
188: + targetClass.getName()
189: + "."
190: + targetMethod.getName(targetClass)
191: + targetMethod
192: .getDescriptor(targetClass)
193: + "]");
194: }
195:
196: // Ignore the removal of the original method invocation,
197: // the addition of the parameter setup, and
198: // the modification of a few inlined instructions.
199: estimatedResultingCodeLength += codeAttribute.u4codeLength;
200:
201: // Append instructions to store the parameters.
202: storeParameters(clazz, method);
203:
204: // Inline the body of the code.
205: copyCode(clazz, method, codeAttribute);
206:
207: inlined = true;
208: inlinedAny = true;
209: }
210: }
211:
212: /**
213: * Appends instructions to pop the parameters for the given method, storing
214: * them in new local variables.
215: */
216: private void storeParameters(Clazz clazz, Method method) {
217:
218: String descriptor = method.getDescriptor(clazz);
219:
220: boolean isStatic = (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
221:
222: // Count the number of parameters, taking into account their categories.
223: int parameterCount = ClassUtil
224: .internalMethodParameterCount(descriptor);
225: int parameterSize = ClassUtil
226: .internalMethodParameterSize(descriptor);
227: int parameterOffset = isStatic ? 0 : 1;
228:
229: // Store the parameter types.
230: String[] parameterTypes = new String[parameterSize];
231:
232: InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(
233: descriptor);
234:
235: for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++) {
236: String parameterType = internalTypeEnumeration.nextType();
237: parameterTypes[parameterIndex] = parameterType;
238: if (ClassUtil.internalTypeSize(parameterType) == 2) {
239: parameterIndex++;
240: }
241: }
242:
243: codeAttributeComposer
244: .beginCodeFragment((parameterOffset + parameterCount) * 4);
245:
246: // Go over the parameter types backward, storing the stack entries
247: // in their corresponding variables.
248: for (int parameterIndex = parameterSize - 1; parameterIndex >= 0; parameterIndex--) {
249: String parameterType = parameterTypes[parameterIndex];
250: if (parameterType != null) {
251: byte opcode;
252: switch (parameterType.charAt(0)) {
253: case ClassConstants.INTERNAL_TYPE_BOOLEAN:
254: case ClassConstants.INTERNAL_TYPE_BYTE:
255: case ClassConstants.INTERNAL_TYPE_CHAR:
256: case ClassConstants.INTERNAL_TYPE_SHORT:
257: case ClassConstants.INTERNAL_TYPE_INT:
258: opcode = InstructionConstants.OP_ISTORE;
259: break;
260:
261: case ClassConstants.INTERNAL_TYPE_LONG:
262: opcode = InstructionConstants.OP_LSTORE;
263: break;
264:
265: case ClassConstants.INTERNAL_TYPE_FLOAT:
266: opcode = InstructionConstants.OP_FSTORE;
267: break;
268:
269: case ClassConstants.INTERNAL_TYPE_DOUBLE:
270: opcode = InstructionConstants.OP_DSTORE;
271: break;
272:
273: default:
274: opcode = InstructionConstants.OP_ASTORE;
275: break;
276: }
277:
278: codeAttributeComposer.appendInstruction(parameterSize
279: - parameterIndex - 1, new VariableInstruction(
280: opcode, variableOffset + parameterOffset
281: + parameterIndex).shrink());
282: }
283: }
284:
285: // Put the 'this' reference in variable 0 (plus offset).
286: if (!isStatic) {
287: codeAttributeComposer.appendInstruction(parameterSize,
288: new VariableInstruction(
289: InstructionConstants.OP_ASTORE,
290: variableOffset).shrink());
291: }
292:
293: codeAttributeComposer.endCodeFragment();
294: }
295:
296: /**
297: * Appends the code of the given code attribute.
298: */
299: private void copyCode(Clazz clazz, Method method,
300: CodeAttribute codeAttribute) {
301: // The code may expand, due to expanding constant and variable
302: // instructions.
303: codeAttributeComposer
304: .beginCodeFragment(codeAttribute.u4codeLength
305: * MAXIMUM_CODE_EXPANSION
306: + MAXIMUM_EXTRA_CODE_LENGTH);
307:
308: // Copy the instructions.
309: codeAttribute.instructionsAccept(clazz, method, this );
310:
311: // Copy the exceptions.
312: codeAttribute.exceptionsAccept(clazz, method, this );
313:
314: // Append a label just after the code.
315: codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
316:
317: codeAttributeComposer.endCodeFragment();
318: }
319:
320: // Implementations for InstructionVisitor.
321:
322: public void visitAnyInstruction(Clazz clazz, Method method,
323: CodeAttribute codeAttribute, int offset,
324: Instruction instruction) {
325: codeAttributeComposer.appendInstruction(offset, instruction
326: .shrink());
327: }
328:
329: public void visitSimpleInstruction(Clazz clazz, Method method,
330: CodeAttribute codeAttribute, int offset,
331: SimpleInstruction simpleInstruction) {
332: // Are we inlining this instruction?
333: if (inlining) {
334: // Replace any return instructions by branches to the end of the code.
335: switch (simpleInstruction.opcode) {
336: case InstructionConstants.OP_IRETURN:
337: case InstructionConstants.OP_LRETURN:
338: case InstructionConstants.OP_FRETURN:
339: case InstructionConstants.OP_DRETURN:
340: case InstructionConstants.OP_ARETURN:
341: case InstructionConstants.OP_RETURN:
342: // Are we not at the last instruction?
343: if (offset < codeAttribute.u4codeLength - 1) {
344: // Replace the return instruction by a branch instruction.
345: Instruction branchInstruction = new BranchInstruction(
346: InstructionConstants.OP_GOTO_W,
347: codeAttribute.u4codeLength - offset);
348:
349: codeAttributeComposer.appendInstruction(offset,
350: branchInstruction.shrink());
351: } else {
352: // Just leave out the instruction, but put in a label,
353: // for the sake of any other branch instructions.
354: codeAttributeComposer.appendLabel(offset);
355: }
356:
357: return;
358: }
359: }
360:
361: codeAttributeComposer.appendInstruction(offset,
362: simpleInstruction.shrink());
363: }
364:
365: public void visitVariableInstruction(Clazz clazz, Method method,
366: CodeAttribute codeAttribute, int offset,
367: VariableInstruction variableInstruction) {
368: // Are we inlining this instruction?
369: if (inlining) {
370: // Update the variable index.
371: variableInstruction.variableIndex += variableOffset;
372: }
373:
374: codeAttributeComposer.appendInstruction(offset,
375: variableInstruction.shrink());
376: }
377:
378: public void visitConstantInstruction(Clazz clazz, Method method,
379: CodeAttribute codeAttribute, int offset,
380: ConstantInstruction constantInstruction) {
381: // Is it a method invocation?
382: switch (constantInstruction.opcode) {
383: case InstructionConstants.OP_NEW:
384: uninitializedObjectCount++;
385: break;
386:
387: case InstructionConstants.OP_INVOKEVIRTUAL:
388: case InstructionConstants.OP_INVOKESPECIAL:
389: case InstructionConstants.OP_INVOKESTATIC:
390: case InstructionConstants.OP_INVOKEINTERFACE:
391: // See if we can inline it.
392: inlined = false;
393:
394: // Append a label, in case the invocation will be inlined.
395: codeAttributeComposer.appendLabel(offset);
396:
397: emptyInvokingStack = !inlining
398: && stackSizeComputer.isReachable(offset)
399: && stackSizeComputer.getStackSize(offset) == 0;
400:
401: variableOffset += codeAttribute.u2maxLocals;
402:
403: clazz.constantPoolEntryAccept(
404: constantInstruction.constantIndex, this );
405:
406: variableOffset -= codeAttribute.u2maxLocals;
407:
408: // Was the method inlined?
409: if (inlined) {
410: if (extraInlinedInvocationVisitor != null) {
411: extraInlinedInvocationVisitor
412: .visitConstantInstruction(clazz, method,
413: codeAttribute, offset,
414: 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: // Make sure the constant is present in the constant pool of the
427: // target class.
428: clazz.constantPoolEntryAccept(
429: constantInstruction.constantIndex, constantAdder);
430:
431: // Let the instruction point to this constant.
432: constantInstruction.constantIndex = constantAdder
433: .getConstantIndex();
434: }
435:
436: codeAttributeComposer.appendInstruction(offset,
437: constantInstruction.shrink());
438: }
439:
440: // Implementations for ExceptionInfoVisitor.
441:
442: public void visitExceptionInfo(Clazz clazz, Method method,
443: CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) {
444: int catchType = exceptionInfo.u2catchType;
445:
446: if (inlining && catchType != 0) {
447: // Make sure the constant is present in the constant pool of the
448: // target class.
449: clazz.constantPoolEntryAccept(catchType, constantAdder);
450:
451: // Let the exception point to this constant.
452: catchType = constantAdder.getConstantIndex();
453: }
454:
455: codeAttributeComposer.appendException(new ExceptionInfo(
456: exceptionInfo.u2startPC, exceptionInfo.u2endPC,
457: exceptionInfo.u2handlerPC, catchType));
458: }
459:
460: // Implementations for ConstantVisitor.
461:
462: public void visitInterfaceMethodrefConstant(Clazz clazz,
463: InterfaceMethodrefConstant interfaceMethodrefConstant) {
464: }
465:
466: public void visitMethodrefConstant(Clazz clazz,
467: MethodrefConstant methodrefConstant) {
468: methodrefConstant.referencedMemberAccept(this );
469: }
470:
471: // Implementations for MemberVisitor.
472:
473: public void visitAnyMember(Clazz Clazz, Member member) {
474: }
475:
476: public void visitProgramMethod(ProgramClass programClass,
477: ProgramMethod programMethod) {
478: int accessFlags = programMethod.getAccessFlags();
479:
480: if (// Only inline the method if it is private, static, or final.
481: (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE
482: | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_FINAL)) != 0
483: &&
484:
485: // Only inline the method if it is not synchronized, etc.
486: (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED
487: | ClassConstants.INTERNAL_ACC_NATIVE
488: | ClassConstants.INTERNAL_ACC_INTERFACE | ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0
489: &&
490:
491: // Don't inline an <init> method, except in an <init> method in the
492: // same class.
493: // (!programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
494: // (programClass.equals(targetClass) &&
495: // targetMethod.getName(targetClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))) &&
496: !programMethod.getName(programClass).equals(
497: ClassConstants.INTERNAL_METHOD_NAME_INIT)
498: &&
499:
500: // Don't inline a method into itself.
501: (!programMethod.equals(targetMethod) || !programClass
502: .equals(targetClass))
503: &&
504:
505: // Only inline the method if it isn't recursing.
506: !inliningMethods.contains(programMethod)
507: &&
508:
509: // Only inline the method if its target class has at least the
510: // same version number as the source class, in order to avoid
511: // introducing incompatible constructs.
512: targetClass.u4version >= programClass.u4version
513: &&
514:
515: // Only inline the method if it doesn't invoke a super method, or if
516: // it is in the same class.
517: (!SuperInvocationMarker
518: .invokesSuperMethods(programMethod) || programClass
519: .equals(targetClass))
520: &&
521:
522: // Only inline the method if it doesn't branch backward while there
523: // are uninitialized objects.
524: (!BackwardBranchMarker.branchesBackward(programMethod) || uninitializedObjectCount == 0)
525: &&
526:
527: // Only inline if the code access of the inlined method allows it.
528: (allowAccessModification || ((!AccessMethodMarker
529: .accessesPrivateCode(programMethod) || programClass
530: .equals(targetClass)) &&
531:
532: (!AccessMethodMarker.accessesPackageCode(programMethod) || ClassUtil
533: .internalPackageName(programClass.getName())
534: .equals(
535: ClassUtil
536: .internalPackageName(targetClass
537: .getName())))))
538: &&
539:
540: // (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
541: // targetClass.extends_(programClass) ||
542: // targetClass.implements_(programClass)) ||
543: (!AccessMethodMarker
544: .accessesProtectedCode(programMethod) || programClass
545: .equals(targetClass))
546: &&
547:
548: // Only inline the method if it doesn't catch exceptions, or if it
549: // is invoked with an empty stack.
550: (!CatchExceptionMarker.catchesExceptions(programMethod) || emptyInvokingStack)
551: &&
552:
553: // Only inline the method if it comes from the same class or from
554: // a class with a static initializer.
555: (programClass.equals(targetClass) || programClass
556: .findMethod(
557: ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
558: ClassConstants.INTERNAL_METHOD_TYPE_CLINIT) == null)) {
559: // System.out.print("MethodInliner: inlining ");
560: // programMethod.accept(programClass, new SimpleClassPrinter(true));
561: // System.out.print(" in ");
562: // targetMethod.accept(targetClass, new SimpleClassPrinter(true));
563: //
564: // System.out.println(" Private: "+
565: // (!AccessMethodMarker.accessesPrivateCode(programMethod) ||
566: // programClass.equals(targetClass)));
567: //
568: // System.out.println(" Package: "+
569: // (!AccessMethodMarker.accessesPackageCode(programMethod) ||
570: // ClassUtil.internalPackageName(programClass.getName()).equals(
571: // ClassUtil.internalPackageName(targetClass.getName()))));
572: //
573: // System.out.println(" Protected: "+
574: // ((!AccessMethodMarker.accessesProtectedCode(programMethod) ||
575: // targetClass.extends_(programClass) ||
576: // targetClass.implements_(programClass)) ||
577: // ClassUtil.internalPackageName(programClass.getName()).equals(
578: // ClassUtil.internalPackageName(targetClass.getName()))));
579:
580: boolean oldInlining = inlining;
581: inlining = true;
582: inliningMethods.push(programMethod);
583:
584: // Inline the method body.
585: programMethod.attributesAccept(programClass, this);
586:
587: inlining = oldInlining;
588: inliningMethods.pop();
589: } else if (programMethod.getName(programClass).equals(
590: ClassConstants.INTERNAL_METHOD_NAME_INIT)) {
591: uninitializedObjectCount--;
592: }
593: }
594: }
|