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 */ 21 package proguard.optimize.evaluation; 22 23 import proguard.classfile.*; 24 import proguard.classfile.attribute.*; 25 import proguard.classfile.attribute.visitor.*; 26 import proguard.classfile.constant.ClassConstant; 27 import proguard.classfile.instruction.*; 28 import proguard.classfile.util.*; 29 import proguard.classfile.visitor.*; 30 import proguard.evaluation.*; 31 import proguard.evaluation.value.*; 32 import proguard.optimize.peephole.BranchTargetFinder; 33 34 import java.util.Arrays; 35 36 /** 37 * This AttributeVisitor performs partial evaluation on the code attributes 38 * that it visits. 39 * 40 * @author Eric Lafortune 41 */ 42 public class PartialEvaluator 43 extends SimplifiedVisitor 44 implements AttributeVisitor, 45 ExceptionInfoVisitor 46 { 47 //* 48 private static final boolean DEBUG = false; 49 private static final boolean DEBUG_RESULTS = false; 50 /*/ 51 private static boolean DEBUG = true; 52 private static boolean DEBUG_RESULTS = true; 53 //*/ 54 55 private static final int MAXIMUM_EVALUATION_COUNT = 5; 56 57 public static final int NONE = -2; 58 public static final int AT_METHOD_ENTRY = -1; 59 public static final int AT_CATCH_ENTRY = -1; 60 61 private final ValueFactory valueFactory; 62 private final InvocationUnit invocationUnit; 63 private final boolean evaluateAllCode; 64 65 private InstructionOffsetValue[] branchOriginValues = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH]; 66 private InstructionOffsetValue[] branchTargetValues = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH]; 67 private TracedVariables[] variablesBefore = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH]; 68 private TracedStack[] stacksBefore = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH]; 69 private TracedVariables[] variablesAfter = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH]; 70 private TracedStack[] stacksAfter = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH]; 71 private boolean[] generalizedContexts = new boolean[ClassConstants.TYPICAL_CODE_LENGTH]; 72 private int[] evaluationCounts = new int[ClassConstants.TYPICAL_CODE_LENGTH]; 73 private boolean evaluateExceptions; 74 75 private final BasicBranchUnit branchUnit; 76 private final BranchTargetFinder branchTargetFinder; 77 78 private final java.util.Stack callingInstructionBlockStack; 79 private final java.util.Stack instructionBlockStack = new java.util.Stack(); 80 81 82 /** 83 * Creates a simple PartialEvaluator. 84 */ PartialEvaluator()85 public PartialEvaluator() 86 { 87 this(new ValueFactory(), 88 new BasicInvocationUnit(new ValueFactory()), 89 true); 90 } 91 92 93 /** 94 * Creates a new PartialEvaluator. 95 * @param valueFactory the value factory that will create all values 96 * during evaluation. 97 * @param invocationUnit the invocation unit that will handle all 98 * communication with other fields and methods. 99 * @param evaluateAllCode a flag that specifies whether all casts, branch 100 * targets, and exception handlers should be 101 * evaluated, even if they are unnecessary or 102 * unreachable. 103 */ PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode)104 public PartialEvaluator(ValueFactory valueFactory, 105 InvocationUnit invocationUnit, 106 boolean evaluateAllCode) 107 { 108 this(valueFactory, 109 invocationUnit, 110 evaluateAllCode, 111 evaluateAllCode ? 112 new BasicBranchUnit() : 113 new TracedBranchUnit(), 114 new BranchTargetFinder(), 115 null); 116 } 117 118 119 /** 120 * Creates a new PartialEvaluator, based on an existing one. 121 * @param partialEvaluator the subroutine calling partial evaluator. 122 */ PartialEvaluator(PartialEvaluator partialEvaluator)123 private PartialEvaluator(PartialEvaluator partialEvaluator) 124 { 125 this(partialEvaluator.valueFactory, 126 partialEvaluator.invocationUnit, 127 partialEvaluator.evaluateAllCode, 128 partialEvaluator.branchUnit, 129 partialEvaluator.branchTargetFinder, 130 partialEvaluator.instructionBlockStack); 131 } 132 133 134 /** 135 * Creates a new PartialEvaluator. 136 * @param valueFactory the value factory that will create 137 * all values during evaluation. 138 * @param invocationUnit the invocation unit that will handle 139 * all communication with other fields 140 * and methods. 141 * @param evaluateAllCode a flag that specifies whether all 142 * casts, branch targets, and exception 143 * handlers should be evaluated, even 144 * if they are unnecessary or 145 * unreachable. 146 * @param branchUnit the branch unit that will handle all 147 * branches. 148 * @param branchTargetFinder the utility class that will find all 149 * branches. 150 * @param callingInstructionBlockStack the stack of instruction blocks to 151 * be evaluated 152 */ PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode, BasicBranchUnit branchUnit, BranchTargetFinder branchTargetFinder, java.util.Stack callingInstructionBlockStack)153 private PartialEvaluator(ValueFactory valueFactory, 154 InvocationUnit invocationUnit, 155 boolean evaluateAllCode, 156 BasicBranchUnit branchUnit, 157 BranchTargetFinder branchTargetFinder, 158 java.util.Stack callingInstructionBlockStack) 159 { 160 this.valueFactory = valueFactory; 161 this.invocationUnit = invocationUnit; 162 this.evaluateAllCode = evaluateAllCode; 163 this.branchUnit = branchUnit; 164 this.branchTargetFinder = branchTargetFinder; 165 this.callingInstructionBlockStack = callingInstructionBlockStack == null ? 166 this.instructionBlockStack : 167 callingInstructionBlockStack; 168 } 169 170 171 // Implementations for AttributeVisitor. 172 visitAnyAttribute(Clazz clazz, Attribute attribute)173 public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 174 175 visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)176 public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 177 { 178 // DEBUG = DEBUG_RESULTS = 179 // clazz.getName().equals("abc/Def") && 180 // method.getName(clazz).equals("abc"); 181 182 // TODO: Remove this when the partial evaluator has stabilized. 183 // Catch any unexpected exceptions from the actual visiting method. 184 try 185 { 186 // Process the code. 187 visitCodeAttribute0(clazz, method, codeAttribute); 188 } 189 catch (RuntimeException ex) 190 { 191 System.err.println("Unexpected error while performing partial evaluation:"); 192 System.err.println(" Class = ["+clazz.getName()+"]"); 193 System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 194 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 195 196 if (DEBUG) 197 { 198 method.accept(clazz, new ClassPrinter()); 199 200 System.out.println("Evaluation results:"); 201 202 int offset = 0; 203 do 204 { 205 if (isBranchOrExceptionTarget(offset)) 206 { 207 System.out.println("Branch target from ["+branchOriginValues[offset]+"]:"); 208 if (isTraced(offset)) 209 { 210 System.out.println(" Vars: "+variablesBefore[offset]); 211 System.out.println(" Stack: "+stacksBefore[offset]); 212 } 213 } 214 215 Instruction instruction = InstructionFactory.create(codeAttribute.code, 216 offset); 217 System.out.println(instruction.toString(offset)); 218 219 if (isTraced(offset)) 220 { 221 int initializationOffset = branchTargetFinder.initializationOffset(offset); 222 if (initializationOffset != NONE) 223 { 224 System.out.println(" is to be initialized at ["+initializationOffset+"]"); 225 } 226 227 InstructionOffsetValue branchTargets = branchTargets(offset); 228 if (branchTargets != null) 229 { 230 System.out.println(" has overall been branching to "+branchTargets); 231 } 232 233 System.out.println(" Vars: "+variablesAfter[offset]); 234 System.out.println(" Stack: "+stacksAfter[offset]); 235 } 236 237 offset += instruction.length(offset); 238 } 239 while (offset < codeAttribute.u4codeLength); 240 } 241 242 throw ex; 243 } 244 } 245 246 visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)247 public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) 248 { 249 // Evaluate the instructions, starting at the entry point. 250 if (DEBUG) 251 { 252 System.out.println(); 253 System.out.println("Partial evaluation: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); 254 System.out.println(" Max locals = "+codeAttribute.u2maxLocals); 255 System.out.println(" Max stack = "+codeAttribute.u2maxStack); 256 } 257 258 // Reuse the existing variables and stack objects, ensuring the right size. 259 TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals); 260 TracedStack stack = new TracedStack(codeAttribute.u2maxStack); 261 262 // Initialize the reusable arrays and variables. 263 initializeArrays(codeAttribute); 264 initializeParameters(clazz, method, codeAttribute, variables); 265 266 // Find all instruction offsets,... 267 codeAttribute.accept(clazz, method, branchTargetFinder); 268 269 // Start executing the first instruction block. 270 evaluateInstructionBlockAndExceptionHandlers(clazz, 271 method, 272 codeAttribute, 273 variables, 274 stack, 275 0, 276 codeAttribute.u4codeLength); 277 278 if (DEBUG_RESULTS) 279 { 280 System.out.println("Evaluation results:"); 281 282 int offset = 0; 283 do 284 { 285 if (isBranchOrExceptionTarget(offset)) 286 { 287 System.out.println("Branch target from ["+branchOriginValues[offset]+"]:"); 288 if (isTraced(offset)) 289 { 290 System.out.println(" Vars: "+variablesBefore[offset]); 291 System.out.println(" Stack: "+stacksBefore[offset]); 292 } 293 } 294 295 Instruction instruction = InstructionFactory.create(codeAttribute.code, 296 offset); 297 System.out.println(instruction.toString(offset)); 298 299 if (isTraced(offset)) 300 { 301 int initializationOffset = branchTargetFinder.initializationOffset(offset); 302 if (initializationOffset >= 0) 303 { 304 System.out.println(" is to be initialized at ["+initializationOffset+"]"); 305 } 306 307 InstructionOffsetValue branchTargets = branchTargets(offset); 308 if (branchTargets != null) 309 { 310 System.out.println(" has overall been branching to "+branchTargets); 311 } 312 313 System.out.println(" Vars: "+variablesAfter[offset]); 314 System.out.println(" Stack: "+stacksAfter[offset]); 315 } 316 317 offset += instruction.length(offset); 318 } 319 while (offset < codeAttribute.u4codeLength); 320 } 321 } 322 323 324 /** 325 * Returns whether a block of instructions is ever used. 326 */ isTraced(int startOffset, int endOffset)327 public boolean isTraced(int startOffset, int endOffset) 328 { 329 for (int index = startOffset; index < endOffset; index++) 330 { 331 if (isTraced(index)) 332 { 333 return true; 334 } 335 } 336 337 return false; 338 } 339 340 341 /** 342 * Returns whether the instruction at the given offset has ever been 343 * executed during the partial evaluation. 344 */ isTraced(int instructionOffset)345 public boolean isTraced(int instructionOffset) 346 { 347 return evaluationCounts[instructionOffset] > 0; 348 } 349 350 351 /** 352 * Returns whether there is an instruction at the given offset. 353 */ isInstruction(int instructionOffset)354 public boolean isInstruction(int instructionOffset) 355 { 356 return branchTargetFinder.isInstruction(instructionOffset); 357 } 358 359 360 /** 361 * Returns whether the instruction at the given offset is the target of a 362 * branch instruction or an exception. 363 */ isBranchOrExceptionTarget(int instructionOffset)364 public boolean isBranchOrExceptionTarget(int instructionOffset) 365 { 366 return branchTargetFinder.isBranchTarget(instructionOffset) || 367 branchTargetFinder.isExceptionHandler(instructionOffset); 368 } 369 370 371 /** 372 * Returns whether the instruction at the given offset is the start of a 373 * subroutine. 374 */ isSubroutineStart(int instructionOffset)375 public boolean isSubroutineStart(int instructionOffset) 376 { 377 return branchTargetFinder.isSubroutineStart(instructionOffset); 378 } 379 380 381 /** 382 * Returns whether the instruction at the given offset is a subroutine 383 * invocation. 384 */ isSubroutineInvocation(int instructionOffset)385 public boolean isSubroutineInvocation(int instructionOffset) 386 { 387 return branchTargetFinder.isSubroutineInvocation(instructionOffset); 388 } 389 390 391 /** 392 * Returns whether the instruction at the given offset is part of a 393 * subroutine. 394 */ isSubroutine(int instructionOffset)395 public boolean isSubroutine(int instructionOffset) 396 { 397 return branchTargetFinder.isSubroutine(instructionOffset); 398 } 399 400 401 /** 402 * Returns whether the subroutine at the given offset is ever returning 403 * by means of a regular 'ret' instruction. 404 */ isSubroutineReturning(int instructionOffset)405 public boolean isSubroutineReturning(int instructionOffset) 406 { 407 return branchTargetFinder.isSubroutineReturning(instructionOffset); 408 } 409 410 411 /** 412 * Returns the offset after the subroutine that starts at the given 413 * offset. 414 */ subroutineEnd(int instructionOffset)415 public int subroutineEnd(int instructionOffset) 416 { 417 return branchTargetFinder.subroutineEnd(instructionOffset); 418 } 419 420 421 /** 422 * Returns the instruction offset at which the object instance that is 423 * created at the given 'new' instruction offset is initialized, or 424 * <code>NONE</code> if it is not being created. 425 */ initializationOffset(int instructionOffset)426 public int initializationOffset(int instructionOffset) 427 { 428 return branchTargetFinder.initializationOffset(instructionOffset); 429 } 430 431 432 /** 433 * Returns whether the method is an instance initializer. 434 */ isInitializer()435 public boolean isInitializer() 436 { 437 return branchTargetFinder.isInitializer(); 438 } 439 440 441 /** 442 * Returns the instruction offset at which this initializer is calling 443 * the "super" or "this" initializer method, or <code>NONE</code> if it is 444 * not an initializer. 445 */ superInitializationOffset()446 public int superInitializationOffset() 447 { 448 return branchTargetFinder.superInitializationOffset(); 449 } 450 451 452 /** 453 * Returns the offset of the 'new' instruction that corresponds to the 454 * invocation of the instance initializer at the given offset, or 455 * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or 456 * "this" initializer method, , or <code>NONE</code> if it is not a 'new' 457 * instruction. 458 */ creationOffset(int offset)459 public int creationOffset(int offset) 460 { 461 return branchTargetFinder.creationOffset(offset); 462 } 463 464 465 /** 466 * Returns the variables before execution of the instruction at the given 467 * offset. 468 */ getVariablesBefore(int instructionOffset)469 public TracedVariables getVariablesBefore(int instructionOffset) 470 { 471 return variablesBefore[instructionOffset]; 472 } 473 474 475 /** 476 * Returns the variables after execution of the instruction at the given 477 * offset. 478 */ getVariablesAfter(int instructionOffset)479 public TracedVariables getVariablesAfter(int instructionOffset) 480 { 481 return variablesAfter[instructionOffset]; 482 } 483 484 485 /** 486 * Returns the stack before execution of the instruction at the given 487 * offset. 488 */ getStackBefore(int instructionOffset)489 public TracedStack getStackBefore(int instructionOffset) 490 { 491 return stacksBefore[instructionOffset]; 492 } 493 494 495 /** 496 * Returns the stack after execution of the instruction at the given 497 * offset. 498 */ getStackAfter(int instructionOffset)499 public TracedStack getStackAfter(int instructionOffset) 500 { 501 return stacksAfter[instructionOffset]; 502 } 503 504 505 /** 506 * Returns the instruction offsets that branch to the given instruction 507 * offset. 508 */ branchOrigins(int instructionOffset)509 public InstructionOffsetValue branchOrigins(int instructionOffset) 510 { 511 return branchOriginValues[instructionOffset]; 512 } 513 514 515 /** 516 * Returns the instruction offsets to which the given instruction offset 517 * branches. 518 */ branchTargets(int instructionOffset)519 public InstructionOffsetValue branchTargets(int instructionOffset) 520 { 521 return branchTargetValues[instructionOffset]; 522 } 523 524 525 // Utility methods to evaluate instruction blocks. 526 527 /** 528 * Pushes block of instructions to be executed in the calling partial 529 * evaluator. 530 */ pushCallingInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset)531 private void pushCallingInstructionBlock(TracedVariables variables, 532 TracedStack stack, 533 int startOffset) 534 { 535 callingInstructionBlockStack.push(new MyInstructionBlock(variables, 536 stack, 537 startOffset)); 538 } 539 540 541 /** 542 * Pushes block of instructions to be executed in this partial evaluator. 543 */ pushInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset)544 private void pushInstructionBlock(TracedVariables variables, 545 TracedStack stack, 546 int startOffset) 547 { 548 instructionBlockStack.push(new MyInstructionBlock(variables, 549 stack, 550 startOffset)); 551 } 552 553 554 /** 555 * Evaluates the instruction block and the exception handlers covering the 556 * given instruction range in the given code. 557 */ evaluateInstructionBlockAndExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset, int endOffset)558 private void evaluateInstructionBlockAndExceptionHandlers(Clazz clazz, 559 Method method, 560 CodeAttribute codeAttribute, 561 TracedVariables variables, 562 TracedStack stack, 563 int startOffset, 564 int endOffset) 565 { 566 evaluateInstructionBlock(clazz, 567 method, 568 codeAttribute, 569 variables, 570 stack, 571 startOffset); 572 573 evaluateExceptionHandlers(clazz, 574 method, 575 codeAttribute, 576 startOffset, 577 endOffset); 578 } 579 580 581 /** 582 * Evaluates a block of instructions, starting at the given offset and ending 583 * at a branch instruction, a return instruction, or a throw instruction. 584 */ evaluateInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset)585 private void evaluateInstructionBlock(Clazz clazz, 586 Method method, 587 CodeAttribute codeAttribute, 588 TracedVariables variables, 589 TracedStack stack, 590 int startOffset) 591 { 592 // Execute the initial instruction block. 593 evaluateSingleInstructionBlock(clazz, 594 method, 595 codeAttribute, 596 variables, 597 stack, 598 startOffset); 599 600 // Execute all resulting instruction blocks on the execution stack. 601 while (!instructionBlockStack.empty()) 602 { 603 if (DEBUG) System.out.println("Popping alternative branch out of "+instructionBlockStack.size()+" blocks"); 604 605 MyInstructionBlock instructionBlock = 606 (MyInstructionBlock)instructionBlockStack.pop(); 607 608 evaluateSingleInstructionBlock(clazz, 609 method, 610 codeAttribute, 611 instructionBlock.variables, 612 instructionBlock.stack, 613 instructionBlock.startOffset); 614 } 615 } 616 617 618 /** 619 * Evaluates a block of instructions, starting at the given offset and ending 620 * at a branch instruction, a return instruction, or a throw instruction. 621 * Instruction blocks that are to be evaluated as a result are pushed on 622 * the given stack. 623 */ evaluateSingleInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset)624 private void evaluateSingleInstructionBlock(Clazz clazz, 625 Method method, 626 CodeAttribute codeAttribute, 627 TracedVariables variables, 628 TracedStack stack, 629 int startOffset) 630 { 631 byte[] code = codeAttribute.code; 632 633 if (DEBUG) 634 { 635 System.out.println("Instruction block starting at ["+startOffset+"] in "+ 636 ClassUtil.externalFullMethodDescription(clazz.getName(), 637 0, 638 method.getName(clazz), 639 method.getDescriptor(clazz))); 640 System.out.println("Init vars: "+variables); 641 System.out.println("Init stack: "+stack); 642 } 643 644 Processor processor = new Processor(variables, 645 stack, 646 valueFactory, 647 branchUnit, 648 invocationUnit, 649 evaluateAllCode); 650 651 int instructionOffset = startOffset; 652 653 int maxOffset = startOffset; 654 655 // Evaluate the subsequent instructions. 656 while (true) 657 { 658 if (maxOffset < instructionOffset) 659 { 660 maxOffset = instructionOffset; 661 } 662 663 // Maintain a generalized local variable frame and stack at this 664 // instruction offset, before execution. 665 int evaluationCount = evaluationCounts[instructionOffset]; 666 if (evaluationCount == 0) 667 { 668 // First time we're passing by this instruction. 669 if (variablesBefore[instructionOffset] == null) 670 { 671 // There's not even a context at this index yet. 672 variablesBefore[instructionOffset] = new TracedVariables(variables); 673 stacksBefore[instructionOffset] = new TracedStack(stack); 674 } 675 else 676 { 677 // Reuse the context objects at this index. 678 variablesBefore[instructionOffset].initialize(variables); 679 stacksBefore[instructionOffset].copy(stack); 680 } 681 682 // We'll execute in the generalized context, because it is 683 // the same as the current context. 684 generalizedContexts[instructionOffset] = true; 685 } 686 else 687 { 688 // Merge in the current context. 689 boolean variablesChanged = variablesBefore[instructionOffset].generalize(variables, true); 690 boolean stackChanged = stacksBefore[instructionOffset].generalize(stack); 691 692 //System.out.println("GVars: "+variablesBefore[instructionOffset]); 693 //System.out.println("GStack: "+stacksBefore[instructionOffset]); 694 695 // Bail out if the current context is the same as last time. 696 if (!variablesChanged && 697 !stackChanged && 698 generalizedContexts[instructionOffset]) 699 { 700 if (DEBUG) System.out.println("Repeated variables, stack, and branch targets"); 701 702 break; 703 } 704 705 // See if this instruction has been evaluated an excessive number 706 // of times. 707 if (evaluationCount >= MAXIMUM_EVALUATION_COUNT) 708 { 709 if (DEBUG) System.out.println("Generalizing current context after "+evaluationCount+" evaluations"); 710 711 // Continue, but generalize the current context. 712 // Note that the most recent variable values have to remain 713 // last in the generalizations, for the sake of the ret 714 // instruction. 715 variables.generalize(variablesBefore[instructionOffset], false); 716 stack.generalize(stacksBefore[instructionOffset]); 717 718 // We'll execute in the generalized context. 719 generalizedContexts[instructionOffset] = true; 720 } 721 else 722 { 723 // We'll execute in the current context. 724 generalizedContexts[instructionOffset] = false; 725 } 726 } 727 728 // We'll evaluate this instruction. 729 evaluationCounts[instructionOffset]++; 730 731 // Remember this instruction's offset with any stored value. 732 Value storeValue = new InstructionOffsetValue(instructionOffset); 733 variables.setProducerValue(storeValue); 734 stack.setProducerValue(storeValue); 735 736 // Reset the trace value. 737 InstructionOffsetValue traceValue = InstructionOffsetValue.EMPTY_VALUE; 738 739 // Note that the instruction is only volatile. 740 Instruction instruction = InstructionFactory.create(code, instructionOffset); 741 742 // By default, the next instruction will be the one after this 743 // instruction. 744 int nextInstructionOffset = instructionOffset + 745 instruction.length(instructionOffset); 746 InstructionOffsetValue nextInstructionOffsetValue = new InstructionOffsetValue(nextInstructionOffset); 747 branchUnit.resetCalled(); 748 branchUnit.setTraceBranchTargets(nextInstructionOffsetValue); 749 750 if (DEBUG) 751 { 752 System.out.println(instruction.toString(instructionOffset)); 753 } 754 755 try 756 { 757 // Process the instruction. The processor may modify the 758 // variables and the stack, and it may call the branch unit 759 // and the invocation unit. 760 instruction.accept(clazz, 761 method, 762 codeAttribute, 763 instructionOffset, 764 processor); 765 } 766 catch (RuntimeException ex) 767 { 768 System.err.println("Unexpected error while evaluating instruction:"); 769 System.err.println(" Class = ["+clazz.getName()+"]"); 770 System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); 771 System.err.println(" Instruction = "+instruction.toString(instructionOffset)); 772 System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); 773 774 throw ex; 775 } 776 777 // Collect the branch targets from the branch unit. 778 InstructionOffsetValue branchTargets = branchUnit.getTraceBranchTargets(); 779 int branchTargetCount = branchTargets.instructionOffsetCount(); 780 781 // Stop tracing. 782 branchUnit.setTraceBranchTargets(traceValue); 783 784 if (DEBUG) 785 { 786 if (branchUnit.wasCalled()) 787 { 788 System.out.println(" is branching to "+branchTargets); 789 } 790 if (branchTargetValues[instructionOffset] != null) 791 { 792 System.out.println(" has up till now been branching to "+branchTargetValues[instructionOffset]); 793 } 794 795 System.out.println(" Vars: "+variables); 796 System.out.println(" Stack: "+stack); 797 } 798 799 // Maintain a generalized local variable frame and stack at this 800 // instruction offset, after execution. 801 if (evaluationCount == 0) 802 { 803 // First time we're passing by this instruction. 804 if (variablesAfter[instructionOffset] == null) 805 { 806 // There's not even a context at this index yet. 807 variablesAfter[instructionOffset] = new TracedVariables(variables); 808 stacksAfter[instructionOffset] = new TracedStack(stack); 809 } 810 else 811 { 812 // Reuse the context objects at this index. 813 variablesAfter[instructionOffset].initialize(variables); 814 stacksAfter[instructionOffset].copy(stack); 815 } 816 } 817 else 818 { 819 // Merge in the current context. 820 variablesAfter[instructionOffset].generalize(variables, true); 821 stacksAfter[instructionOffset].generalize(stack); 822 } 823 824 // Did the branch unit get called? 825 if (branchUnit.wasCalled()) 826 { 827 // Accumulate the branch targets at this offset. 828 branchTargetValues[instructionOffset] = branchTargetValues[instructionOffset] == null ? 829 branchTargets : 830 branchTargetValues[instructionOffset].generalize(branchTargets).instructionOffsetValue(); 831 832 // Are there no branch targets at all? 833 if (branchTargetCount == 0) 834 { 835 // Exit from this code block. 836 break; 837 } 838 839 // Accumulate the branch origins at the branch target offsets. 840 InstructionOffsetValue instructionOffsetValue = new InstructionOffsetValue(instructionOffset); 841 for (int index = 0; index < branchTargetCount; index++) 842 { 843 int branchTarget = branchTargets.instructionOffset(index); 844 branchOriginValues[branchTarget] = branchOriginValues[branchTarget] == null ? 845 instructionOffsetValue: 846 branchOriginValues[branchTarget].generalize(instructionOffsetValue).instructionOffsetValue(); 847 } 848 849 // Are there multiple branch targets? 850 if (branchTargetCount > 1) 851 { 852 // Push them on the execution stack and exit from this block. 853 for (int index = 0; index < branchTargetCount; index++) 854 { 855 if (DEBUG) System.out.println("Pushing alternative branch #"+index+" out of "+branchTargetCount+", from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(index)+"]"); 856 857 pushInstructionBlock(new TracedVariables(variables), 858 new TracedStack(stack), 859 branchTargets.instructionOffset(index)); 860 } 861 862 break; 863 } 864 865 if (DEBUG) System.out.println("Definite branch from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(0)+"]"); 866 } 867 868 // Just continue with the next instruction. 869 instructionOffset = branchTargets.instructionOffset(0); 870 871 // Is this a subroutine invocation? 872 if (instruction.opcode == InstructionConstants.OP_JSR || 873 instruction.opcode == InstructionConstants.OP_JSR_W) 874 { 875 // Evaluate the subroutine in another partial evaluator. 876 evaluateSubroutine(clazz, 877 method, 878 codeAttribute, 879 variables, 880 stack, 881 instructionOffset, 882 instructionBlockStack); 883 884 break; 885 } 886 else if (instruction.opcode == InstructionConstants.OP_RET) 887 { 888 // Let the partial evaluator that has called the subroutine 889 // handle the evaluation after the return. 890 pushCallingInstructionBlock(new TracedVariables(variables), 891 new TracedStack(stack), 892 instructionOffset); 893 break; 894 } 895 } 896 897 if (DEBUG) System.out.println("Ending processing of instruction block starting at ["+startOffset+"]"); 898 } 899 900 901 /** 902 * Evaluates a subroutine and its exception handlers, starting at the given 903 * offset and ending at a subroutine return instruction. 904 */ evaluateSubroutine(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int subroutineStart, java.util.Stack instructionBlockStack)905 private void evaluateSubroutine(Clazz clazz, 906 Method method, 907 CodeAttribute codeAttribute, 908 TracedVariables variables, 909 TracedStack stack, 910 int subroutineStart, 911 java.util.Stack instructionBlockStack) 912 { 913 int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart); 914 915 if (DEBUG) System.out.println("Evaluating subroutine from "+subroutineStart+" to "+subroutineEnd); 916 917 // Create a temporary partial evaluator, so there are no conflicts 918 // with variables that are alive across subroutine invocations, between 919 // different invocations. 920 PartialEvaluator subroutinePartialEvaluator = 921 new PartialEvaluator(this); 922 923 subroutinePartialEvaluator.initializeArrays(codeAttribute); 924 925 // Evaluate the subroutine. 926 subroutinePartialEvaluator.evaluateInstructionBlockAndExceptionHandlers(clazz, 927 method, 928 codeAttribute, 929 variables, 930 stack, 931 subroutineStart, 932 subroutineEnd); 933 934 // Merge back the temporary partial evaluator. This way, we'll get 935 // the lowest common denominator of stacks and variables. 936 generalize(subroutinePartialEvaluator, 0, codeAttribute.u4codeLength); 937 938 if (DEBUG) System.out.println("Ending subroutine from "+subroutineStart+" to "+subroutineEnd); 939 } 940 941 942 /** 943 * Generalizes the results of this partial evaluator with those of another 944 * given partial evaluator, over a given range of instructions. 945 */ generalize(PartialEvaluator other, int codeStart, int codeEnd)946 private void generalize(PartialEvaluator other, 947 int codeStart, 948 int codeEnd) 949 { 950 if (DEBUG) System.out.println("Generalizing with temporary partial evaluation"); 951 952 for (int offset = codeStart; offset < codeEnd; offset++) 953 { 954 if (other.branchOriginValues[offset] != null) 955 { 956 branchOriginValues[offset] = branchOriginValues[offset] == null ? 957 other.branchOriginValues[offset] : 958 branchOriginValues[offset].generalize(other.branchOriginValues[offset]).instructionOffsetValue(); 959 } 960 961 if (other.isTraced(offset)) 962 { 963 if (other.branchTargetValues[offset] != null) 964 { 965 branchTargetValues[offset] = branchTargetValues[offset] == null ? 966 other.branchTargetValues[offset] : 967 branchTargetValues[offset].generalize(other.branchTargetValues[offset]).instructionOffsetValue(); 968 } 969 970 if (evaluationCounts[offset] == 0) 971 { 972 variablesBefore[offset] = other.variablesBefore[offset]; 973 stacksBefore[offset] = other.stacksBefore[offset]; 974 variablesAfter[offset] = other.variablesAfter[offset]; 975 stacksAfter[offset] = other.stacksAfter[offset]; 976 generalizedContexts[offset] = other.generalizedContexts[offset]; 977 evaluationCounts[offset] = other.evaluationCounts[offset]; 978 } 979 else 980 { 981 variablesBefore[offset].generalize(other.variablesBefore[offset], false); 982 stacksBefore[offset] .generalize(other.stacksBefore[offset]); 983 variablesAfter[offset] .generalize(other.variablesAfter[offset], false); 984 stacksAfter[offset] .generalize(other.stacksAfter[offset]); 985 //generalizedContexts[offset] 986 evaluationCounts[offset] += other.evaluationCounts[offset]; 987 } 988 } 989 } 990 } 991 992 993 /** 994 * Evaluates the exception handlers covering and targeting the given 995 * instruction range in the given code. 996 */ evaluateExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset)997 private void evaluateExceptionHandlers(Clazz clazz, 998 Method method, 999 CodeAttribute codeAttribute, 1000 int startOffset, 1001 int endOffset) 1002 { 1003 if (DEBUG) System.out.println("Evaluating exceptions covering ["+startOffset+" -> "+endOffset+"]:"); 1004 1005 ExceptionHandlerFilter exceptionEvaluator = 1006 new ExceptionHandlerFilter(startOffset, 1007 endOffset, 1008 this); 1009 1010 // Evaluate the exception catch blocks, until their entry variables 1011 // have stabilized. 1012 do 1013 { 1014 // Reset the flag to stop evaluating. 1015 evaluateExceptions = false; 1016 1017 // Evaluate all relevant exception catch blocks once. 1018 codeAttribute.exceptionsAccept(clazz, 1019 method, 1020 startOffset, 1021 endOffset, 1022 exceptionEvaluator); 1023 } 1024 while (evaluateExceptions); 1025 } 1026 1027 1028 // Implementations for ExceptionInfoVisitor. 1029 visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)1030 public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 1031 { 1032 int startPC = exceptionInfo.u2startPC; 1033 int endPC = exceptionInfo.u2endPC; 1034 1035 // Do we have to evaluate this exception catch block? 1036 if (isTraced(startPC, endPC)) 1037 { 1038 int handlerPC = exceptionInfo.u2handlerPC; 1039 int catchType = exceptionInfo.u2catchType; 1040 1041 if (DEBUG) System.out.println("Evaluating exception ["+startPC +" -> "+endPC +": "+handlerPC+"]:"); 1042 1043 // Reuse the existing variables and stack objects, ensuring the 1044 // right size. 1045 TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals); 1046 TracedStack stack = new TracedStack(codeAttribute.u2maxStack); 1047 1048 // Initialize the trace values. 1049 Value storeValue = new InstructionOffsetValue(AT_CATCH_ENTRY); 1050 variables.setProducerValue(storeValue); 1051 stack.setProducerValue(storeValue); 1052 1053 // Initialize the variables by generalizing the variables of the 1054 // try block. Make sure to include the results of the last 1055 // instruction for preverification. 1056 generalizeVariables(startPC, 1057 endPC, 1058 evaluateAllCode, 1059 variables); 1060 1061 // Initialize the the stack. 1062 //stack.push(valueFactory.createReference((ClassConstant)((ProgramClass)clazz).getConstant(exceptionInfo.u2catchType), false)); 1063 String catchClassName = catchType != 0 ? 1064 clazz.getClassName(catchType) : 1065 ClassConstants.NAME_JAVA_LANG_THROWABLE; 1066 1067 Clazz catchClass = catchType != 0 ? 1068 ((ClassConstant)((ProgramClass)clazz).getConstant(catchType)).referencedClass : 1069 null; 1070 1071 stack.push(valueFactory.createReferenceValue(catchClassName, 1072 catchClass, 1073 false)); 1074 1075 int evaluationCount = evaluationCounts[handlerPC]; 1076 1077 // Evaluate the instructions, starting at the entry point. 1078 evaluateInstructionBlock(clazz, 1079 method, 1080 codeAttribute, 1081 variables, 1082 stack, 1083 handlerPC); 1084 1085 // Remember to evaluate all exception handlers once more. 1086 if (!evaluateExceptions) 1087 { 1088 evaluateExceptions = evaluationCount < evaluationCounts[handlerPC]; 1089 } 1090 } 1091 // else if (evaluateAllCode) 1092 // { 1093 // if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"] yet"); 1094 // 1095 // // We don't have any information on the try block yet, but we do 1096 // // have to evaluate the exception handler. 1097 // // Remember to evaluate all exception handlers once more. 1098 // evaluateExceptions = true; 1099 // } 1100 else 1101 { 1102 if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"]"); 1103 } 1104 } 1105 1106 1107 // Small utility methods. 1108 1109 /** 1110 * Initializes the data structures for the variables, stack, etc. 1111 */ 1112 private void initializeArrays(CodeAttribute codeAttribute) 1113 { 1114 int codeLength = codeAttribute.u4codeLength; 1115 1116 // Create new arrays for storing information at each instruction offset. 1117 if (variablesAfter.length < codeLength) 1118 { 1119 // Create new arrays. 1120 branchOriginValues = new InstructionOffsetValue[codeLength]; 1121 branchTargetValues = new InstructionOffsetValue[codeLength]; 1122 variablesBefore = new TracedVariables[codeLength]; 1123 stacksBefore = new TracedStack[codeLength]; 1124 variablesAfter = new TracedVariables[codeLength]; 1125 stacksAfter = new TracedStack[codeLength]; 1126 generalizedContexts = new boolean[codeLength]; 1127 evaluationCounts = new int[codeLength]; 1128 } 1129 else 1130 { 1131 // Reset the arrays. 1132 Arrays.fill(branchOriginValues, null); 1133 Arrays.fill(branchTargetValues, null); 1134 Arrays.fill(generalizedContexts, false); 1135 Arrays.fill(evaluationCounts, 0); 1136 1137 for (int index = 0; index < codeLength; index++) 1138 { 1139 if (variablesBefore[index] != null) 1140 { 1141 variablesBefore[index].reset(codeAttribute.u2maxLocals); 1142 } 1143 1144 if (stacksBefore[index] != null) 1145 { 1146 stacksBefore[index].reset(codeAttribute.u2maxStack); 1147 } 1148 1149 if (variablesAfter[index] != null) 1150 { 1151 variablesAfter[index].reset(codeAttribute.u2maxLocals); 1152 } 1153 1154 if (stacksAfter[index] != null) 1155 { 1156 stacksAfter[index].reset(codeAttribute.u2maxStack); 1157 } 1158 } 1159 } 1160 } 1161 1162 1163 /** 1164 * Initializes the data structures for the variables, stack, etc. 1165 */ 1166 private void initializeParameters(Clazz clazz, 1167 Method method, 1168 CodeAttribute codeAttribute, 1169 TracedVariables variables) 1170 { 1171 // Create the method parameters. 1172 TracedVariables parameters = new TracedVariables(codeAttribute.u2maxLocals); 1173 1174 // Remember this instruction's offset with any stored value. 1175 Value storeValue = new InstructionOffsetValue(AT_METHOD_ENTRY); 1176 parameters.setProducerValue(storeValue); 1177 1178 // Initialize the method parameters. 1179 invocationUnit.enterMethod(clazz, method, parameters); 1180 1181 if (DEBUG) 1182 { 1183 System.out.println(" Params: "+parameters); 1184 } 1185 1186 // Initialize the variables with the parameters. 1187 variables.initialize(parameters); 1188 1189 // Set the store value of each parameter variable. 1190 InstructionOffsetValue atMethodEntry = new InstructionOffsetValue(AT_METHOD_ENTRY); 1191 1192 for (int index = 0; index < parameters.size(); index++) 1193 { 1194 variables.setProducerValue(index, atMethodEntry); 1195 } 1196 } 1197 1198 1199 /** 1200 * Generalize the local variable frames of a block of instructions. 1201 */ 1202 private void generalizeVariables(int startOffset, 1203 int endOffset, 1204 boolean includeAfterLastInstruction, 1205 TracedVariables generalizedVariables) 1206 { 1207 boolean first = true; 1208 int lastIndex = -1; 1209 1210 // Generalize the variables before each of the instructions in the block. 1211 for (int index = startOffset; index < endOffset; index++) 1212 { 1213 if (isTraced(index)) 1214 { 1215 TracedVariables tracedVariables = variablesBefore[index]; 1216 1217 if (first) 1218 { 1219 // Initialize the variables with the first traced local 1220 // variable frame. 1221 generalizedVariables.initialize(tracedVariables); 1222 1223 first = false; 1224 } 1225 else 1226 { 1227 // Generalize the variables with the traced local variable 1228 // frame. We can't use the return value, because local 1229 // generalization can be different a couple of times, 1230 // with the global generalization being the same. 1231 generalizedVariables.generalize(tracedVariables, false); 1232 } 1233 1234 lastIndex = index; 1235 } 1236 } 1237 1238 // Generalize the variables after the last instruction in the block, 1239 // if required. 1240 if (includeAfterLastInstruction && 1241 lastIndex >= 0) 1242 { 1243 TracedVariables tracedVariables = variablesAfter[lastIndex]; 1244 1245 if (first) 1246 { 1247 // Initialize the variables with the local variable frame. 1248 generalizedVariables.initialize(tracedVariables); 1249 } 1250 else 1251 { 1252 // Generalize the variables with the local variable frame. 1253 generalizedVariables.generalize(tracedVariables, false); 1254 } 1255 } 1256 1257 // Just clear the variables if there aren't any traced instructions 1258 // in the block. 1259 if (first) 1260 { 1261 generalizedVariables.reset(generalizedVariables.size()); 1262 } 1263 } 1264 1265 1266 private static class MyInstructionBlock 1267 { 1268 private TracedVariables variables; 1269 private TracedStack stack; 1270 private int startOffset; 1271 1272 1273 private MyInstructionBlock(TracedVariables variables, 1274 TracedStack stack, 1275 int startOffset) 1276 { 1277 this.variables = variables; 1278 this.stack = stack; 1279 this.startOffset = startOffset; 1280 } 1281 } 1282 } 1283