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