blob: 5790a3683a89b57ebd987f9031f0759321c38f4d [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2009 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 int[] initializedVariables = 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());
}
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.
initializeVariables(clazz, method, codeAttribute, variables, stack);
// 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 variableIndex = initializedVariable(offset);
if (variableIndex >= 0)
{
System.out.println(" is initializing variable v"+variableIndex);
}
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];
}
/**
* Returns the variable that is initialized at the given instruction offset,
* or <code>NONE</code> if no variable was initialized.
*/
public int initializedVariable(int instructionOffset)
{
return initializedVariables[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;
// Reset the initialization flag.
variables.resetInitialization();
// 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 offsets of the instructions whose results were used.
initializedVariables[instructionOffset] = variables.getInitializationIndex();
// 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.initializeVariables(clazz,
method,
codeAttribute,
variables,
stack);
}
// 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];
initializedVariables[offset] = other.initializedVariables[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];
//initializedVariables[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 initializeVariables(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
TracedVariables variables,
TracedStack stack)
{
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];
initializedVariables = new int[codeLength];
// Reset the arrays.
for (int index = 0; index < codeLength; index++)
{
initializedVariables[index] = NONE;
}
}
else
{
// Reset the arrays.
for (int index = 0; index < codeLength; index++)
{
branchOriginValues[index] = null;
branchTargetValues[index] = null;
generalizedContexts[index] = false;
evaluationCounts[index] = 0;
initializedVariables[index] = NONE;
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);
}
}
}
// 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;
}
}
}