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