/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2010 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.optimize.evaluation; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.ClassConstant; import proguard.classfile.instruction.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.evaluation.*; import proguard.evaluation.value.*; import proguard.optimize.peephole.BranchTargetFinder; /** * This AttributeVisitor performs partial evaluation on the code attributes * that it visits. * * @author Eric Lafortune */ public class PartialEvaluator extends SimplifiedVisitor implements AttributeVisitor, ExceptionInfoVisitor { //* private static final boolean DEBUG = false; private static final boolean DEBUG_RESULTS = false; /*/ private static boolean DEBUG = true; private static boolean DEBUG_RESULTS = true; //*/ private static final int MAXIMUM_EVALUATION_COUNT = 5; public static final int NONE = -2; public static final int AT_METHOD_ENTRY = -1; public static final int AT_CATCH_ENTRY = -1; private final ValueFactory valueFactory; private final InvocationUnit invocationUnit; private final boolean evaluateAllCode; private InstructionOffsetValue[] branchOriginValues = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH]; private InstructionOffsetValue[] branchTargetValues = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH]; private TracedVariables[] variablesBefore = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH]; private TracedStack[] stacksBefore = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH]; private TracedVariables[] variablesAfter = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH]; private TracedStack[] stacksAfter = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH]; private boolean[] generalizedContexts = new boolean[ClassConstants.TYPICAL_CODE_LENGTH]; private int[] evaluationCounts = new int[ClassConstants.TYPICAL_CODE_LENGTH]; private boolean evaluateExceptions; private final BasicBranchUnit branchUnit; private final BranchTargetFinder branchTargetFinder; private final java.util.Stack callingInstructionBlockStack; private final java.util.Stack instructionBlockStack = new java.util.Stack(); /** * Creates a simple PartialEvaluator. */ public PartialEvaluator() { this(new ValueFactory(), new BasicInvocationUnit(new ValueFactory()), true); } /** * Creates a new PartialEvaluator. * @param valueFactory the value factory that will create all values * during evaluation. * @param invocationUnit the invocation unit that will handle all * communication with other fields and methods. * @param evaluateAllCode a flag that specifies whether all branch targets * and exception handlers should be evaluated, * even if they are unreachable. */ public PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode) { this(valueFactory, invocationUnit, evaluateAllCode, evaluateAllCode ? new BasicBranchUnit() : new TracedBranchUnit(), new BranchTargetFinder(), null); } /** * Creates a new PartialEvaluator, based on an existing one. * @param partialEvaluator the subroutine calling partial evaluator. */ private PartialEvaluator(PartialEvaluator partialEvaluator) { this(partialEvaluator.valueFactory, partialEvaluator.invocationUnit, partialEvaluator.evaluateAllCode, partialEvaluator.branchUnit, partialEvaluator.branchTargetFinder, partialEvaluator.instructionBlockStack); } /** * Creates a new PartialEvaluator. * @param valueFactory the value factory that will create all * values during evaluation. * @param invocationUnit the invocation unit that will handle all * communication with other fields and methods. * @param evaluateAllCode a flag that specifies whether all branch * targets and exception handlers should be * evaluated, even if they are unreachable. * @param branchUnit the branch unit that will handle all * branches. * @param branchTargetFinder the utility class that will find all * branches. */ private PartialEvaluator(ValueFactory valueFactory, InvocationUnit invocationUnit, boolean evaluateAllCode, BasicBranchUnit branchUnit, BranchTargetFinder branchTargetFinder, java.util.Stack callingInstructionBlockStack) { this.valueFactory = valueFactory; this.invocationUnit = invocationUnit; this.evaluateAllCode = evaluateAllCode; this.branchUnit = branchUnit; this.branchTargetFinder = branchTargetFinder; this.callingInstructionBlockStack = callingInstructionBlockStack == null ? this.instructionBlockStack : callingInstructionBlockStack; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = DEBUG_RESULTS = // clazz.getName().equals("abc/Def") && // method.getName(clazz).equals("abc"); // TODO: Remove this when the partial evaluator has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { System.err.println("Unexpected error while performing partial evaluation:"); System.err.println(" Class = ["+clazz.getName()+"]"); System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); //if (DEBUG) { method.accept(clazz, new ClassPrinter()); System.out.println("Evaluation results:"); int offset = 0; do { if (isBranchOrExceptionTarget(offset)) { System.out.println("Branch target from ["+branchOriginValues[offset]+"]:"); if (isTraced(offset)) { System.out.println(" Vars: "+variablesBefore[offset]); System.out.println(" Stack: "+stacksBefore[offset]); } } Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); System.out.println(instruction.toString(offset)); if (isTraced(offset)) { int initializationOffset = branchTargetFinder.initializationOffset(offset); if (initializationOffset != NONE) { System.out.println(" is to be initialized at ["+initializationOffset+"]"); } InstructionOffsetValue branchTargets = branchTargets(offset); if (branchTargets != null) { System.out.println(" has overall been branching to "+branchTargets); } System.out.println(" Vars: "+variablesAfter[offset]); System.out.println(" Stack: "+stacksAfter[offset]); } offset += instruction.length(offset); } while (offset < codeAttribute.u4codeLength); } throw ex; } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Evaluate the instructions, starting at the entry point. if (DEBUG) { System.out.println(); System.out.println("Partial evaluation: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); System.out.println(" Max locals = "+codeAttribute.u2maxLocals); System.out.println(" Max stack = "+codeAttribute.u2maxStack); } // Reuse the existing variables and stack objects, ensuring the right size. TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals); TracedStack stack = new TracedStack(codeAttribute.u2maxStack); // Initialize the reusable arrays and variables. initializeArrays(codeAttribute); initializeParameters(clazz, method, codeAttribute, variables); // Find all instruction offsets,... codeAttribute.accept(clazz, method, branchTargetFinder); // Start executing the first instruction block. evaluateInstructionBlockAndExceptionHandlers(clazz, method, codeAttribute, variables, stack, 0, codeAttribute.u4codeLength); if (DEBUG_RESULTS) { System.out.println("Evaluation results:"); int offset = 0; do { if (isBranchOrExceptionTarget(offset)) { System.out.println("Branch target from ["+branchOriginValues[offset]+"]:"); if (isTraced(offset)) { System.out.println(" Vars: "+variablesBefore[offset]); System.out.println(" Stack: "+stacksBefore[offset]); } } Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); System.out.println(instruction.toString(offset)); if (isTraced(offset)) { int initializationOffset = branchTargetFinder.initializationOffset(offset); if (initializationOffset != NONE) { System.out.println(" is to be initialized at ["+initializationOffset+"]"); } InstructionOffsetValue branchTargets = branchTargets(offset); if (branchTargets != null) { System.out.println(" has overall been branching to "+branchTargets); } System.out.println(" Vars: "+variablesAfter[offset]); System.out.println(" Stack: "+stacksAfter[offset]); } offset += instruction.length(offset); } while (offset < codeAttribute.u4codeLength); } } /** * Returns whether a block of instructions is ever used. */ public boolean isTraced(int startOffset, int endOffset) { for (int index = startOffset; index < endOffset; index++) { if (isTraced(index)) { return true; } } return false; } /** * Returns whether the instruction at the given offset has ever been * executed during the partial evaluation. */ public boolean isTraced(int instructionOffset) { return evaluationCounts[instructionOffset] > 0; } /** * Returns whether there is an instruction at the given offset. */ public boolean isInstruction(int instructionOffset) { return branchTargetFinder.isInstruction(instructionOffset); } /** * Returns whether the instruction at the given offset is the target of a * branch instruction or an exception. */ public boolean isBranchOrExceptionTarget(int instructionOffset) { return branchTargetFinder.isBranchTarget(instructionOffset) || branchTargetFinder.isExceptionHandler(instructionOffset); } /** * Returns whether the instruction at the given offset is the start of a * subroutine. */ public boolean isSubroutineStart(int instructionOffset) { return branchTargetFinder.isSubroutineStart(instructionOffset); } /** * Returns whether the instruction at the given offset is a subroutine * invocation. */ public boolean isSubroutineInvocation(int instructionOffset) { return branchTargetFinder.isSubroutineInvocation(instructionOffset); } /** * Returns whether the instruction at the given offset is part of a * subroutine. */ public boolean isSubroutine(int instructionOffset) { return branchTargetFinder.isSubroutine(instructionOffset); } /** * Returns whether the subroutine at the given offset is ever returning * by means of a regular 'ret' instruction. */ public boolean isSubroutineReturning(int instructionOffset) { return branchTargetFinder.isSubroutineReturning(instructionOffset); } /** * Returns the offset after the subroutine that starts at the given * offset. */ public int subroutineEnd(int instructionOffset) { return branchTargetFinder.subroutineEnd(instructionOffset); } /** * Returns the instruction offset at which the object instance that is * created at the given 'new' instruction offset is initialized, or * <code>NONE</code> if it is not being created. */ public int initializationOffset(int instructionOffset) { return branchTargetFinder.initializationOffset(instructionOffset); } /** * Returns whether the method is an instance initializer. */ public boolean isInitializer() { return branchTargetFinder.isInitializer(); } /** * Returns the instruction offset at which this initializer is calling * the "super" or "this" initializer method, or <code>NONE</code> if it is * not an initializer. */ public int superInitializationOffset() { return branchTargetFinder.superInitializationOffset(); } /** * Returns the offset of the 'new' instruction that corresponds to the * invocation of the instance initializer at the given offset, or * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or * "this" initializer method, , or <code>NONE</code> if it is not a 'new' * instruction. */ public int creationOffset(int offset) { return branchTargetFinder.creationOffset(offset); } /** * Returns the variables before execution of the instruction at the given * offset. */ public TracedVariables getVariablesBefore(int instructionOffset) { return variablesBefore[instructionOffset]; } /** * Returns the variables after execution of the instruction at the given * offset. */ public TracedVariables getVariablesAfter(int instructionOffset) { return variablesAfter[instructionOffset]; } /** * Returns the stack before execution of the instruction at the given * offset. */ public TracedStack getStackBefore(int instructionOffset) { return stacksBefore[instructionOffset]; } /** * Returns the stack after execution of the instruction at the given * offset. */ public TracedStack getStackAfter(int instructionOffset) { return stacksAfter[instructionOffset]; } /** * Returns the instruction offsets that branch to the given instruction * offset. */ public InstructionOffsetValue branchOrigins(int instructionOffset) { return branchOriginValues[instructionOffset]; } /** * Returns the instruction offsets to which the given instruction offset * branches. */ public InstructionOffsetValue branchTargets(int instructionOffset) { return branchTargetValues[instructionOffset]; } // Utility methods to evaluate instruction blocks. /** * Pushes block of instructions to be executed in the calling partial * evaluator. */ private void pushCallingInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset) { callingInstructionBlockStack.push(new MyInstructionBlock(variables, stack, startOffset)); } /** * Pushes block of instructions to be executed in this partial evaluator. */ private void pushInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset) { instructionBlockStack.push(new MyInstructionBlock(variables, stack, startOffset)); } /** * Evaluates the instruction block and the exception handlers covering the * given instruction range in the given code. */ private void evaluateInstructionBlockAndExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset, int endOffset) { evaluateInstructionBlock(clazz, method, codeAttribute, variables, stack, startOffset); evaluateExceptionHandlers(clazz, method, codeAttribute, startOffset, endOffset); } /** * Evaluates a block of instructions, starting at the given offset and ending * at a branch instruction, a return instruction, or a throw instruction. */ private void evaluateInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset) { // Execute the initial instruction block. evaluateSingleInstructionBlock(clazz, method, codeAttribute, variables, stack, startOffset); // Execute all resulting instruction blocks on the execution stack. while (!instructionBlockStack.empty()) { if (DEBUG) System.out.println("Popping alternative branch out of "+instructionBlockStack.size()+" blocks"); MyInstructionBlock instructionBlock = (MyInstructionBlock)instructionBlockStack.pop(); evaluateSingleInstructionBlock(clazz, method, codeAttribute, instructionBlock.variables, instructionBlock.stack, instructionBlock.startOffset); } } /** * Evaluates a block of instructions, starting at the given offset and ending * at a branch instruction, a return instruction, or a throw instruction. * Instruction blocks that are to be evaluated as a result are pushed on * the given stack. */ private void evaluateSingleInstructionBlock(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int startOffset) { byte[] code = codeAttribute.code; if (DEBUG) { System.out.println("Instruction block starting at ["+startOffset+"] in "+ ClassUtil.externalFullMethodDescription(clazz.getName(), 0, method.getName(clazz), method.getDescriptor(clazz))); System.out.println("Init vars: "+variables); System.out.println("Init stack: "+stack); } Processor processor = new Processor(variables, stack, valueFactory, branchUnit, invocationUnit); int instructionOffset = startOffset; int maxOffset = startOffset; // Evaluate the subsequent instructions. while (true) { if (maxOffset < instructionOffset) { maxOffset = instructionOffset; } // Maintain a generalized local variable frame and stack at this // instruction offset, before execution. int evaluationCount = evaluationCounts[instructionOffset]; if (evaluationCount == 0) { // First time we're passing by this instruction. if (variablesBefore[instructionOffset] == null) { // There's not even a context at this index yet. variablesBefore[instructionOffset] = new TracedVariables(variables); stacksBefore[instructionOffset] = new TracedStack(stack); } else { // Reuse the context objects at this index. variablesBefore[instructionOffset].initialize(variables); stacksBefore[instructionOffset].copy(stack); } // We'll execute in the generalized context, because it is // the same as the current context. generalizedContexts[instructionOffset] = true; } else { // Merge in the current context. boolean variablesChanged = variablesBefore[instructionOffset].generalize(variables, true); boolean stackChanged = stacksBefore[instructionOffset].generalize(stack); //System.out.println("GVars: "+variablesBefore[instructionOffset]); //System.out.println("GStack: "+stacksBefore[instructionOffset]); // Bail out if the current context is the same as last time. if (!variablesChanged && !stackChanged && generalizedContexts[instructionOffset]) { if (DEBUG) System.out.println("Repeated variables, stack, and branch targets"); break; } // See if this instruction has been evaluated an excessive number // of times. if (evaluationCount >= MAXIMUM_EVALUATION_COUNT) { if (DEBUG) System.out.println("Generalizing current context after "+evaluationCount+" evaluations"); // Continue, but generalize the current context. // Note that the most recent variable values have to remain // last in the generalizations, for the sake of the ret // instruction. variables.generalize(variablesBefore[instructionOffset], false); stack.generalize(stacksBefore[instructionOffset]); // We'll execute in the generalized context. generalizedContexts[instructionOffset] = true; } else { // We'll execute in the current context. generalizedContexts[instructionOffset] = false; } } // We'll evaluate this instruction. evaluationCounts[instructionOffset]++; // Remember this instruction's offset with any stored value. Value storeValue = new InstructionOffsetValue(instructionOffset); variables.setProducerValue(storeValue); stack.setProducerValue(storeValue); // Reset the trace value. InstructionOffsetValue traceValue = InstructionOffsetValue.EMPTY_VALUE; // Note that the instruction is only volatile. Instruction instruction = InstructionFactory.create(code, instructionOffset); // By default, the next instruction will be the one after this // instruction. int nextInstructionOffset = instructionOffset + instruction.length(instructionOffset); InstructionOffsetValue nextInstructionOffsetValue = new InstructionOffsetValue(nextInstructionOffset); branchUnit.resetCalled(); branchUnit.setTraceBranchTargets(nextInstructionOffsetValue); if (DEBUG) { System.out.println(instruction.toString(instructionOffset)); } try { // Process the instruction. The processor may modify the // variables and the stack, and it may call the branch unit // and the invocation unit. instruction.accept(clazz, method, codeAttribute, instructionOffset, processor); } catch (RuntimeException ex) { System.err.println("Unexpected error while evaluating instruction:"); System.err.println(" Class = ["+clazz.getName()+"]"); System.err.println(" Method = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]"); System.err.println(" Instruction = "+instruction.toString(instructionOffset)); System.err.println(" Exception = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")"); throw ex; } // Collect the branch targets from the branch unit. InstructionOffsetValue branchTargets = branchUnit.getTraceBranchTargets(); int branchTargetCount = branchTargets.instructionOffsetCount(); // Stop tracing. branchUnit.setTraceBranchTargets(traceValue); if (DEBUG) { if (branchUnit.wasCalled()) { System.out.println(" is branching to "+branchTargets); } if (branchTargetValues[instructionOffset] != null) { System.out.println(" has up till now been branching to "+branchTargetValues[instructionOffset]); } System.out.println(" Vars: "+variables); System.out.println(" Stack: "+stack); } // Maintain a generalized local variable frame and stack at this // instruction offset, after execution. if (evaluationCount == 0) { // First time we're passing by this instruction. if (variablesAfter[instructionOffset] == null) { // There's not even a context at this index yet. variablesAfter[instructionOffset] = new TracedVariables(variables); stacksAfter[instructionOffset] = new TracedStack(stack); } else { // Reuse the context objects at this index. variablesAfter[instructionOffset].initialize(variables); stacksAfter[instructionOffset].copy(stack); } } else { // Merge in the current context. variablesAfter[instructionOffset].generalize(variables, true); stacksAfter[instructionOffset].generalize(stack); } // Did the branch unit get called? if (branchUnit.wasCalled()) { // Accumulate the branch targets at this offset. branchTargetValues[instructionOffset] = branchTargetValues[instructionOffset] == null ? branchTargets : branchTargetValues[instructionOffset].generalize(branchTargets).instructionOffsetValue(); // Are there no branch targets at all? if (branchTargetCount == 0) { // Exit from this code block. break; } // Accumulate the branch origins at the branch target offsets. InstructionOffsetValue instructionOffsetValue = new InstructionOffsetValue(instructionOffset); for (int index = 0; index < branchTargetCount; index++) { int branchTarget = branchTargets.instructionOffset(index); branchOriginValues[branchTarget] = branchOriginValues[branchTarget] == null ? instructionOffsetValue: branchOriginValues[branchTarget].generalize(instructionOffsetValue).instructionOffsetValue(); } // Are there multiple branch targets? if (branchTargetCount > 1) { // Push them on the execution stack and exit from this block. for (int index = 0; index < branchTargetCount; index++) { if (DEBUG) System.out.println("Pushing alternative branch #"+index+" out of "+branchTargetCount+", from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(index)+"]"); pushInstructionBlock(new TracedVariables(variables), new TracedStack(stack), branchTargets.instructionOffset(index)); } break; } if (DEBUG) System.out.println("Definite branch from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(0)+"]"); } // Just continue with the next instruction. instructionOffset = branchTargets.instructionOffset(0); // Is this a subroutine invocation? if (instruction.opcode == InstructionConstants.OP_JSR || instruction.opcode == InstructionConstants.OP_JSR_W) { // Evaluate the subroutine, possibly in another partial // evaluator. evaluateSubroutine(clazz, method, codeAttribute, variables, stack, instructionOffset, instructionBlockStack); break; } else if (instruction.opcode == InstructionConstants.OP_RET) { // Let the partial evaluator that has called the subroutine // handle the evaluation after the return. pushCallingInstructionBlock(new TracedVariables(variables), new TracedStack(stack), instructionOffset); break; } } if (DEBUG) System.out.println("Ending processing of instruction block starting at ["+startOffset+"]"); } /** * Evaluates a subroutine and its exception handlers, starting at the given * offset and ending at a subroutine return instruction. */ private void evaluateSubroutine(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables, TracedStack stack, int subroutineStart, java.util.Stack instructionBlockStack) { int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart); if (DEBUG) System.out.println("Evaluating subroutine from "+subroutineStart+" to "+subroutineEnd); PartialEvaluator subroutinePartialEvaluator = this; // Create a temporary partial evaluator if necessary. if (evaluationCounts[subroutineStart] > 0) { if (DEBUG) System.out.println("Creating new partial evaluator for subroutine"); subroutinePartialEvaluator = new PartialEvaluator(this); subroutinePartialEvaluator.initializeArrays(codeAttribute); } // Evaluate the subroutine. subroutinePartialEvaluator.evaluateInstructionBlockAndExceptionHandlers(clazz, method, codeAttribute, variables, stack, subroutineStart, subroutineEnd); // Merge back the temporary partial evaluator if necessary. if (subroutinePartialEvaluator != this) { generalize(subroutinePartialEvaluator, 0, codeAttribute.u4codeLength); } if (DEBUG) System.out.println("Ending subroutine from "+subroutineStart+" to "+subroutineEnd); } /** * Generalizes the results of this partial evaluator with those of another * given partial evaluator, over a given range of instructions. */ private void generalize(PartialEvaluator other, int codeStart, int codeEnd) { if (DEBUG) System.out.println("Generalizing with temporary partial evaluation"); for (int offset = codeStart; offset < codeEnd; offset++) { if (other.branchOriginValues[offset] != null) { branchOriginValues[offset] = branchOriginValues[offset] == null ? other.branchOriginValues[offset] : branchOriginValues[offset].generalize(other.branchOriginValues[offset]).instructionOffsetValue(); } if (other.isTraced(offset)) { if (other.branchTargetValues[offset] != null) { branchTargetValues[offset] = branchTargetValues[offset] == null ? other.branchTargetValues[offset] : branchTargetValues[offset].generalize(other.branchTargetValues[offset]).instructionOffsetValue(); } if (evaluationCounts[offset] == 0) { variablesBefore[offset] = other.variablesBefore[offset]; stacksBefore[offset] = other.stacksBefore[offset]; variablesAfter[offset] = other.variablesAfter[offset]; stacksAfter[offset] = other.stacksAfter[offset]; generalizedContexts[offset] = other.generalizedContexts[offset]; evaluationCounts[offset] = other.evaluationCounts[offset]; } else { variablesBefore[offset].generalize(other.variablesBefore[offset], false); stacksBefore[offset] .generalize(other.stacksBefore[offset]); variablesAfter[offset] .generalize(other.variablesAfter[offset], false); stacksAfter[offset] .generalize(other.stacksAfter[offset]); //generalizedContexts[offset] evaluationCounts[offset] += other.evaluationCounts[offset]; } } } } /** * Evaluates the exception handlers covering and targeting the given * instruction range in the given code. */ private void evaluateExceptionHandlers(Clazz clazz, Method method, CodeAttribute codeAttribute, int startOffset, int endOffset) { if (DEBUG) System.out.println("Evaluating exceptions covering ["+startOffset+" -> "+endOffset+"]:"); ExceptionHandlerFilter exceptionEvaluator = new ExceptionHandlerFilter(startOffset, endOffset, this); // Evaluate the exception catch blocks, until their entry variables // have stabilized. do { // Reset the flag to stop evaluating. evaluateExceptions = false; // Evaluate all relevant exception catch blocks once. codeAttribute.exceptionsAccept(clazz, method, startOffset, endOffset, exceptionEvaluator); } while (evaluateExceptions); } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { int startPC = exceptionInfo.u2startPC; int endPC = exceptionInfo.u2endPC; // Do we have to evaluate this exception catch block? if (isTraced(startPC, endPC)) { int handlerPC = exceptionInfo.u2handlerPC; int catchType = exceptionInfo.u2catchType; if (DEBUG) System.out.println("Evaluating exception ["+startPC +" -> "+endPC +": "+handlerPC+"]:"); // Reuse the existing variables and stack objects, ensuring the // right size. TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals); TracedStack stack = new TracedStack(codeAttribute.u2maxStack); // Initialize the trace values. Value storeValue = new InstructionOffsetValue(AT_CATCH_ENTRY); variables.setProducerValue(storeValue); stack.setProducerValue(storeValue); // Initialize the variables by generalizing the variables of the // try block. Make sure to include the results of the last // instruction for preverification. generalizeVariables(startPC, endPC, evaluateAllCode, variables); // Initialize the the stack. //stack.push(valueFactory.createReference((ClassConstant)((ProgramClass)clazz).getConstant(exceptionInfo.u2catchType), false)); String catchClassName = catchType != 0 ? clazz.getClassName(catchType) : ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE; Clazz catchClass = catchType != 0 ? ((ClassConstant)((ProgramClass)clazz).getConstant(catchType)).referencedClass : null; stack.push(valueFactory.createReferenceValue(catchClassName, catchClass, false)); int evaluationCount = evaluationCounts[handlerPC]; // Evaluate the instructions, starting at the entry point. evaluateInstructionBlock(clazz, method, codeAttribute, variables, stack, handlerPC); // Remember to evaluate all exception handlers once more. if (!evaluateExceptions) { evaluateExceptions = evaluationCount < evaluationCounts[handlerPC]; } } // else if (evaluateAllCode) // { // if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"] yet"); // // // We don't have any information on the try block yet, but we do // // have to evaluate the exception handler. // // Remember to evaluate all exception handlers once more. // evaluateExceptions = true; // } else { if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"]"); } } // Small utility methods. /** * Initializes the data structures for the variables, stack, etc. */ private void initializeArrays(CodeAttribute codeAttribute) { int codeLength = codeAttribute.u4codeLength; // Create new arrays for storing information at each instruction offset. if (variablesAfter.length < codeLength) { // Create new arrays. branchOriginValues = new InstructionOffsetValue[codeLength]; branchTargetValues = new InstructionOffsetValue[codeLength]; variablesBefore = new TracedVariables[codeLength]; stacksBefore = new TracedStack[codeLength]; variablesAfter = new TracedVariables[codeLength]; stacksAfter = new TracedStack[codeLength]; generalizedContexts = new boolean[codeLength]; evaluationCounts = new int[codeLength]; } else { // Reset the arrays. for (int index = 0; index < codeLength; index++) { branchOriginValues[index] = null; branchTargetValues[index] = null; generalizedContexts[index] = false; evaluationCounts[index] = 0; if (variablesBefore[index] != null) { variablesBefore[index].reset(codeAttribute.u2maxLocals); } if (stacksBefore[index] != null) { stacksBefore[index].reset(codeAttribute.u2maxStack); } if (variablesAfter[index] != null) { variablesAfter[index].reset(codeAttribute.u2maxLocals); } if (stacksAfter[index] != null) { stacksAfter[index].reset(codeAttribute.u2maxStack); } } } } /** * Initializes the data structures for the variables, stack, etc. */ private void initializeParameters(Clazz clazz, Method method, CodeAttribute codeAttribute, TracedVariables variables) { // Create the method parameters. TracedVariables parameters = new TracedVariables(codeAttribute.u2maxLocals); // Remember this instruction's offset with any stored value. Value storeValue = new InstructionOffsetValue(AT_METHOD_ENTRY); parameters.setProducerValue(storeValue); // Initialize the method parameters. invocationUnit.enterMethod(clazz, method, parameters); if (DEBUG) { System.out.println(" Params: "+parameters); } // Initialize the variables with the parameters. variables.initialize(parameters); // Set the store value of each parameter variable. InstructionOffsetValue atMethodEntry = new InstructionOffsetValue(AT_METHOD_ENTRY); for (int index = 0; index < parameters.size(); index++) { variables.setProducerValue(index, atMethodEntry); } } /** * Generalize the local variable frames of a block of instructions. */ private void generalizeVariables(int startOffset, int endOffset, boolean includeAfterLastInstruction, TracedVariables generalizedVariables) { boolean first = true; int lastIndex = -1; // Generalize the variables before each of the instructions in the block. for (int index = startOffset; index < endOffset; index++) { if (isTraced(index)) { TracedVariables tracedVariables = variablesBefore[index]; if (first) { // Initialize the variables with the first traced local // variable frame. generalizedVariables.initialize(tracedVariables); first = false; } else { // Generalize the variables with the traced local variable // frame. We can't use the return value, because local // generalization can be different a couple of times, // with the global generalization being the same. generalizedVariables.generalize(tracedVariables, false); } lastIndex = index; } } // Generalize the variables after the last instruction in the block, // if required. if (includeAfterLastInstruction && lastIndex >= 0) { TracedVariables tracedVariables = variablesAfter[lastIndex]; if (first) { // Initialize the variables with the local variable frame. generalizedVariables.initialize(tracedVariables); } else { // Generalize the variables with the local variable frame. generalizedVariables.generalize(tracedVariables, false); } } // Just clear the variables if there aren't any traced instructions // in the block. if (first) { generalizedVariables.reset(generalizedVariables.size()); } } private static class MyInstructionBlock { private TracedVariables variables; private TracedStack stack; private int startOffset; private MyInstructionBlock(TracedVariables variables, TracedStack stack, int startOffset) { this.variables = variables; this.stack = stack; this.startOffset = startOffset; } } }