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