/*
* Bytecode Analysis Framework
* Copyright (C) 2003-2005 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba.type;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.LocalVariableTypeTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ExceptionThrower;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.Analyze;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.AnalysisFeatures;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.Dataflow;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.FrameDataflowAnalysis;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.ObjectTypeFactory;
import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser;
import edu.umd.cs.findbugs.ba.generic.GenericUtilities;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
/**
* A forward dataflow analysis to determine the types of all values in the Java
* stack frame at all points in a Java method. The values include local
* variables and values on the Java operand stack.
* <p/>
* <p>
* As a side effect, the analysis computes the exception set throwable on each
* exception edge in the CFG. This information can be used to prune infeasible
* exception edges, and mark exception edges which propagate only implicit
* exceptions.
*
* @author David Hovemeyer
* @see Dataflow
* @see edu.umd.cs.findbugs.ba.DataflowAnalysis
* @see TypeFrame
*/
public class TypeAnalysis extends FrameDataflowAnalysis<Type, TypeFrame> implements EdgeTypes {
public static final boolean DEBUG = SystemProperties.getBoolean("ta.debug");
/**
* Force computation of accurate exceptions.
*/
public static final boolean FORCE_ACCURATE_EXCEPTIONS = SystemProperties.getBoolean("ta.accurateExceptions");
/**
* Repository of information about thrown exceptions computed for a basic
* block and its outgoing exception edges. It contains a result TypeFrame,
* which is used to detect when the exception information needs to be
* recomputed for the block.
*/
private class CachedExceptionSet {
private final TypeFrame result;
private final ExceptionSet exceptionSet;
private final Map<Edge, ExceptionSet> edgeExceptionMap;
public CachedExceptionSet(TypeFrame result, ExceptionSet exceptionSet) {
this.result = result;
this.exceptionSet = exceptionSet;
this.edgeExceptionMap = new HashMap<Edge, ExceptionSet>();
}
public boolean isUpToDate(TypeFrame result) {
return this.result.equals(result);
}
public ExceptionSet getExceptionSet() {
return exceptionSet;
}
public void setEdgeExceptionSet(Edge edge, ExceptionSet exceptionSet) {
edgeExceptionMap.put(edge, exceptionSet);
}
public ExceptionSet getEdgeExceptionSet(Edge edge) {
ExceptionSet edgeExceptionSet = edgeExceptionMap.get(edge);
if (edgeExceptionSet == null) {
edgeExceptionSet = exceptionSetFactory.createExceptionSet();
edgeExceptionMap.put(edge, edgeExceptionSet);
}
return edgeExceptionSet;
}
}
/**
* Cached information about an instanceof check.
*/
static class InstanceOfCheck {
final ValueNumber valueNumber;
final Type type;
InstanceOfCheck(ValueNumber valueNumber, Type type) {
this.valueNumber = valueNumber;
this.type = type;
}
/**
* @return Returns the valueNumber.
*/
public ValueNumber getValueNumber() {
return valueNumber;
}
/**
* @return Returns the type.
*/
public Type getType() {
return type;
}
}
protected MethodGen methodGen;
private final Method method;
protected CFG cfg;
private final TypeMerger typeMerger;
private final TypeFrameModelingVisitor visitor;
private final Map<BasicBlock, CachedExceptionSet> thrownExceptionSetMap;
private final RepositoryLookupFailureCallback lookupFailureCallback;
private final ExceptionSetFactory exceptionSetFactory;
private ValueNumberDataflow valueNumberDataflow;
private final Map<BasicBlock, InstanceOfCheck> instanceOfCheckMap;
/**
* Constructor.
*
* @param method
* TODO
* @param methodGen
* the MethodGen whose CFG we'll be analyzing
* @param cfg
* the control flow graph
* @param dfs
* DepthFirstSearch of the method
* @param typeMerger
* object to merge types
* @param visitor
* a TypeFrameModelingVisitor to use to model the effect of
* instructions
* @param lookupFailureCallback
* lookup failure callback
* @param exceptionSetFactory
* factory for creating ExceptionSet objects
*/
public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs, TypeMerger typeMerger,
TypeFrameModelingVisitor visitor, RepositoryLookupFailureCallback lookupFailureCallback,
ExceptionSetFactory exceptionSetFactory) {
super(dfs);
this.method = method;
Code code = method.getCode();
if (code == null) {
throw new IllegalArgumentException(method.getName() + " has no code");
}
for (Attribute a : code.getAttributes()) {
if (a instanceof LocalVariableTypeTable) {
visitor.setLocalTypeTable((LocalVariableTypeTable) a);
}
}
this.methodGen = methodGen;
this.cfg = cfg;
this.typeMerger = typeMerger;
this.visitor = visitor;
this.thrownExceptionSetMap = new HashMap<BasicBlock, CachedExceptionSet>();
this.lookupFailureCallback = lookupFailureCallback;
this.exceptionSetFactory = exceptionSetFactory;
this.instanceOfCheckMap = new HashMap<BasicBlock, InstanceOfCheck>();
if (DEBUG) {
System.out.println("\n\nAnalyzing " + methodGen);
}
}
/**
* Constructor.
*
* @param method
* TODO
* @param methodGen
* the MethodGen whose CFG we'll be analyzing
* @param cfg
* the control flow graph
* @param dfs
* DepthFirstSearch of the method
* @param typeMerger
* object to merge types
* @param lookupFailureCallback
* lookup failure callback
* @param exceptionSetFactory
* factory for creating ExceptionSet objects
*/
public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs, TypeMerger typeMerger,
RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
this(method, methodGen, cfg, dfs, typeMerger, new TypeFrameModelingVisitor(methodGen.getConstantPool(), typeMerger),
lookupFailureCallback, exceptionSetFactory);
if (TypeFrameModelingVisitor.DEBUG) {
System.out.println(methodGen.getClassName() + "." + methodGen.getName() + " " + methodGen.getSignature());
}
}
/**
* Constructor which uses StandardTypeMerger.
*
* @param method
* TODO
* @param methodGen
* the MethodGen whose CFG we'll be analyzing
* @param cfg
* the control flow graph
* @param dfs
* DepthFirstSearch of the method
* @param lookupFailureCallback
* callback for Repository lookup failures
* @param exceptionSetFactory
* factory for creating ExceptionSet objects
*/
public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg, DepthFirstSearch dfs,
RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
this(method, methodGen, cfg, dfs, new StandardTypeMerger(lookupFailureCallback, exceptionSetFactory),
lookupFailureCallback, exceptionSetFactory);
}
/**
* Set the ValueNumberDataflow for the method being analyzed. This is
* optional; if set, it will be used to make instanceof instructions more
* precise.
*
* @param valueNumberDataflow
* the ValueNumberDataflow
*/
public void setValueNumberDataflow(ValueNumberDataflow valueNumberDataflow) {
this.valueNumberDataflow = valueNumberDataflow;
this.visitor.setValueNumberDataflow(valueNumberDataflow);
}
/**
* Set the FieldStoreTypeDatabase. This can be used to get more accurate
* types for values loaded from fields.
*
* @param database
* the FieldStoreTypeDatabase
*/
public void setFieldStoreTypeDatabase(FieldStoreTypeDatabase database) {
visitor.setFieldStoreTypeDatabase(database);
}
/**
* Get the set of exceptions that can be thrown on given edge. This should
* only be called after the analysis completes.
*
* @param edge
* the Edge
* @return the ExceptionSet
*/
public ExceptionSet getEdgeExceptionSet(Edge edge) {
CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(edge.getSource());
return cachedExceptionSet.getEdgeExceptionSet(edge);
}
@Override
public TypeFrame createFact() {
return new TypeFrame(methodGen.getMaxLocals());
}
@Override
public void initEntryFact(TypeFrame result) {
// Make the frame valid
result.setValid();
int slot = 0;
// Clear the stack slots in the frame
result.clearStack();
// Add local for "this" pointer, if present
if (!methodGen.isStatic()) {
result.setValue(slot++, ObjectTypeFactory.getInstance(methodGen.getClassName()));
}
// [Added: Support for Generics]
// Get a parser that reads the generic signature of the method and
// can be used to get the correct GenericObjectType if an argument
// has a class type
Iterator<String> iter = GenericSignatureParser.getGenericSignatureIterator(method);
// Add locals for parameters.
// Note that long and double parameters need to be handled
// specially because they occupy two locals.
Type[] argumentTypes = methodGen.getArgumentTypes();
for (Type argType : argumentTypes) {
// Add special "extra" type for long or double params.
// These occupy the slot before the "plain" type.
if (argType.getType() == Constants.T_LONG) {
result.setValue(slot++, TypeFrame.getLongExtraType());
} else if (argType.getType() == Constants.T_DOUBLE) {
result.setValue(slot++, TypeFrame.getDoubleExtraType());
}
// [Added: Support for Generics]
String s = (iter == null || !iter.hasNext()) ? null : iter.next();
if (s != null && (argType instanceof ObjectType || argType instanceof ArrayType)
&& !(argType instanceof ExceptionObjectType)) {
// replace with a generic version of the type
try {
Type t = GenericUtilities.getType(s);
if (t != null) {
argType = t;
}
} catch (RuntimeException e) {
} // degrade gracefully
}
// Add the plain parameter type.
result.setValue(slot++, argType);
}
// Set remaining locals to BOTTOM; this will cause any
// uses of them to be flagged
while (slot < methodGen.getMaxLocals()) {
result.setValue(slot++, TypeFrame.getBottomType());
}
}
@Override
public void copy(TypeFrame source, TypeFrame dest) {
dest.copyFrom(source);
}
@Override
public void makeFactTop(TypeFrame fact) {
fact.setTop();
}
@Override
public boolean isFactValid(TypeFrame fact) {
return fact.isValid();
}
@Override
public boolean same(TypeFrame fact1, TypeFrame fact2) {
return fact1.sameAs(fact2);
}
@Override
public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, TypeFrame fact)
throws DataflowAnalysisException {
visitor.setFrameAndLocation(fact, new Location(handle, basicBlock));
visitor.analyzeInstruction(handle.getInstruction());
}
@Override
public void transfer(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame start, TypeFrame result)
throws DataflowAnalysisException {
visitor.startBasicBlock();
super.transfer(basicBlock, end, start, result);
// Compute thrown exception types
computeThrownExceptionTypes(basicBlock, end, result);
if (DEBUG) {
System.out.println("After " + basicBlock.getFirstInstruction() + " -> " + basicBlock.getLastInstruction());
System.out.println(" frame: " + result);
}
// If this block ends with an instanceof check,
// update the cached information about it.
instanceOfCheckMap.remove(basicBlock);
if (visitor.isInstanceOfFollowedByBranch()) {
InstanceOfCheck check = new InstanceOfCheck(visitor.getInstanceOfValueNumber(), visitor.getInstanceOfType());
instanceOfCheckMap.put(basicBlock, check);
}
}
private void computeThrownExceptionTypes(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame result)
throws DataflowAnalysisException {
// Do nothing if we're not computing propagated exceptions
if (!(FORCE_ACCURATE_EXCEPTIONS || AnalysisContext.currentAnalysisContext().getBoolProperty(
AnalysisFeatures.ACCURATE_EXCEPTIONS))) {
return;
}
// Also, nothing to do if the block is not an exception thrower
if (!basicBlock.isExceptionThrower()) {
return;
}
// If cached results are up to date, don't recompute.
CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(basicBlock);
if (cachedExceptionSet.isUpToDate(result)) {
return;
}
// Figure out what exceptions can be thrown out
// of the basic block, and mark each exception edge
// with the set of exceptions which can be propagated
// along the edge.
int exceptionEdgeCount = 0;
Edge lastExceptionEdge = null;
for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
Edge e = i.next();
if (e.isExceptionEdge()) {
exceptionEdgeCount++;
lastExceptionEdge = e;
}
}
if (exceptionEdgeCount == 0) {
// System.out.println("Shouldn't all blocks have an exception edge");
return;
}
// Compute exceptions that can be thrown by the
// basic block.
cachedExceptionSet = computeBlockExceptionSet(basicBlock, result);
if (exceptionEdgeCount == 1) {
cachedExceptionSet.setEdgeExceptionSet(lastExceptionEdge, cachedExceptionSet.getExceptionSet());
return;
}
// For each outgoing exception edge, compute exceptions
// that can be thrown. This assumes that the exception
// edges are enumerated in decreasing order of priority.
// In the process, this will remove exceptions from
// the thrown exception set.
ExceptionSet thrownExceptionSet = cachedExceptionSet.getExceptionSet();
if (!thrownExceptionSet.isEmpty()) {
thrownExceptionSet = thrownExceptionSet.duplicate();
}
for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
Edge edge = i.next();
if (edge.isExceptionEdge()) {
cachedExceptionSet.setEdgeExceptionSet(edge, computeEdgeExceptionSet(edge, thrownExceptionSet));
}
}
}
@Override
public void meetInto(TypeFrame fact, Edge edge, TypeFrame result) throws DataflowAnalysisException {
BasicBlock basicBlock = edge.getTarget();
if (fact.isValid()) {
TypeFrame tmpFact = null;
// Handling an exception?
if (basicBlock.isExceptionHandler()) {
tmpFact = modifyFrame(fact, null);
// Special case: when merging predecessor facts for entry to
// an exception handler, we clear the stack and push a
// single entry for the exception object. That way, the locals
// can still be merged.
CodeExceptionGen exceptionGen = basicBlock.getExceptionGen();
tmpFact.clearStack();
// Determine the type of exception(s) caught.
Type catchType = null;
if (FORCE_ACCURATE_EXCEPTIONS
|| AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.ACCURATE_EXCEPTIONS)) {
try {
// Ideally, the exceptions that can be propagated
// on this edge has already been computed.
CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(edge.getSource());
ExceptionSet edgeExceptionSet = cachedExceptionSet.getEdgeExceptionSet(edge);
if (!edgeExceptionSet.isEmpty()) {
// System.out.println("Using computed edge exception set!");
catchType = ExceptionObjectType.fromExceptionSet(edgeExceptionSet);
}
} catch (ClassNotFoundException e) {
lookupFailureCallback.reportMissingClass(e);
}
}
if (catchType == null) {
// No information about propagated exceptions, so
// pick a type conservatively using the handler catch type.
catchType = exceptionGen.getCatchType();
if (catchType == null)
{
catchType = Type.THROWABLE; // handle catches anything
// throwable
}
}
tmpFact.pushValue(catchType);
}
// See if we can make some types more precise due to
// a successful instanceof check in the source block.
if (valueNumberDataflow != null) {
tmpFact = handleInstanceOfBranch(fact, tmpFact, edge);
}
if (tmpFact != null) {
fact = tmpFact;
}
}
mergeInto(fact, result);
}
private TypeFrame handleInstanceOfBranch(TypeFrame fact, TypeFrame tmpFact, Edge edge) {
InstanceOfCheck check = instanceOfCheckMap.get(edge.getSource());
if (check == null) {
// System.out.println("no instanceof check for block " +
// edge.getSource().getId());
return tmpFact;
}
if (check.getValueNumber() == null) {
// System.out.println("instanceof check for block " +
// edge.getSource().getId() + " has no value number");
return tmpFact;
}
ValueNumber instanceOfValueNumber = check.getValueNumber();
ValueNumberFrame vnaFrame = valueNumberDataflow.getStartFact(edge.getTarget());
if (!vnaFrame.isValid()) {
return tmpFact;
}
Type instanceOfType = check.getType();
if (!(instanceOfType instanceof ReferenceType || instanceOfType instanceof NullType)) {
return tmpFact;
}
short branchOpcode = edge.getSource().getLastInstruction().getInstruction().getOpcode();
int edgeType = edge.getType();
int numSlots = Math.min(fact.getNumSlots(), vnaFrame.getNumSlots());
if ((edgeType == EdgeTypes.IFCMP_EDGE && (branchOpcode == Constants.IFNE || branchOpcode == Constants.IFGT || branchOpcode == Constants.IFNULL))
|| (edgeType == EdgeTypes.FALL_THROUGH_EDGE && (branchOpcode == Constants.IFEQ || branchOpcode == Constants.IFLE || branchOpcode == Constants.IFNONNULL))) {
// System.out.println("Successful check on edge " + edge);
// Successful instanceof check.
for (int i = 0; i < numSlots; ++i) {
if (!vnaFrame.getValue(i).equals(instanceOfValueNumber)) {
continue;
}
Type checkedType = fact.getValue(i);
if (!(checkedType instanceof ReferenceType)) {
continue;
}
// Only refine the type if the cast is feasible: i.e., a
// downcast.
// Otherwise, just set it to TOP.
try {
boolean guaranteed = Hierarchy.isSubtype((ReferenceType) checkedType, (ReferenceType) instanceOfType);
if (guaranteed) {
continue;
}
boolean feasibleCheck = instanceOfType.equals(NullType.instance())
|| Hierarchy.isSubtype((ReferenceType) instanceOfType, (ReferenceType) checkedType);
if (!feasibleCheck && instanceOfType instanceof ObjectType && checkedType instanceof ObjectType) {
double v = Analyze.deepInstanceOf(((ObjectType) instanceOfType).getClassName(),
((ObjectType) checkedType).getClassName());
if (v > 0.0) {
feasibleCheck = true;
}
}
tmpFact = modifyFrame(fact, tmpFact);
if (feasibleCheck) {
tmpFact.setValue(i, instanceOfType);
} else {
tmpFact.setTop();
return tmpFact;
}
} catch (ClassNotFoundException e) {
lookupFailureCallback.reportMissingClass(e);
return tmpFact;
}
}
} else if (!instanceOfType.equals(NullType.instance())) {
for (int i = 0; i < numSlots; ++i) {
if (!vnaFrame.getValue(i).equals(instanceOfValueNumber)) {
continue;
}
Type checkedType = fact.getValue(i);
if (!(checkedType instanceof ReferenceType)) {
continue;
}
try {
boolean guaranteed = Hierarchy.isSubtype((ReferenceType) checkedType, (ReferenceType) instanceOfType);
if (!guaranteed) {
continue;
}
tmpFact = modifyFrame(fact, tmpFact);
tmpFact.setTop();
return tmpFact;
} catch (ClassNotFoundException e) {
lookupFailureCallback.reportMissingClass(e);
return tmpFact;
}
}
}
return tmpFact;
}
@Override
protected void mergeValues(TypeFrame otherFrame, TypeFrame resultFrame, int slot) throws DataflowAnalysisException {
Type type2 = resultFrame.getValue(slot);
Type type1 = otherFrame.getValue(slot);
Type value = typeMerger.mergeTypes(type2, type1);
resultFrame.setValue(slot, value);
// Result type is exact IFF types are identical and both are exact
boolean typesAreIdentical = type1.equals(type2);
boolean bothExact = resultFrame.isExact(slot) && otherFrame.isExact(slot);
resultFrame.setExact(slot, typesAreIdentical && bothExact);
}
/**
* Get the cached set of exceptions that can be thrown from given basic
* block. If this information hasn't been computed yet, then an empty
* exception set is returned.
*
* @param basicBlock
* the block to get the cached exception set for
* @return the CachedExceptionSet for the block
*/
private CachedExceptionSet getCachedExceptionSet(BasicBlock basicBlock) {
CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(basicBlock);
if (cachedExceptionSet == null) {
// When creating the cached exception type set for the first time:
// - the block result is set to TOP, so it won't match
// any block result that has actually been computed
// using the analysis transfer function
// - the exception set is created as empty (which makes it
// return TOP as its common superclass)
TypeFrame top = createFact();
makeFactTop(top);
cachedExceptionSet = new CachedExceptionSet(top, exceptionSetFactory.createExceptionSet());
thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);
}
return cachedExceptionSet;
}
/**
* Compute the set of exceptions that can be thrown from the given basic
* block. This should only be called if the existing cached exception set is
* out of date.
*
* @param basicBlock
* the basic block
* @param result
* the result fact for the block; this is used to determine
* whether or not the cached exception set is up to date
* @return the cached exception set for the block
*/
private CachedExceptionSet computeBlockExceptionSet(BasicBlock basicBlock, TypeFrame result) throws DataflowAnalysisException {
ExceptionSet exceptionSet = computeThrownExceptionTypes(basicBlock);
TypeFrame copyOfResult = createFact();
copy(result, copyOfResult);
CachedExceptionSet cachedExceptionSet = new CachedExceptionSet(copyOfResult, exceptionSet);
thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);
return cachedExceptionSet;
}
/**
* Based on the set of exceptions that can be thrown from the source basic
* block, compute the set of exceptions that can propagate along given
* exception edge. This method should be called for each outgoing exception
* edge in sequence, so the caught exceptions can be removed from the thrown
* exception set as needed.
*
* @param edge
* the exception edge
* @param thrownExceptionSet
* current set of exceptions that can be thrown, taking earlier
* (higher priority) exception edges into account
* @return the set of exceptions that can propagate along this edge
*/
private ExceptionSet computeEdgeExceptionSet(Edge edge, ExceptionSet thrownExceptionSet) {
if (thrownExceptionSet.isEmpty()) {
return thrownExceptionSet;
}
ExceptionSet result = exceptionSetFactory.createExceptionSet();
if (edge.getType() == UNHANDLED_EXCEPTION_EDGE) {
// The unhandled exception edge always comes
// after all of the handled exception edges.
result.addAll(thrownExceptionSet);
thrownExceptionSet.clear();
return result;
}
BasicBlock handlerBlock = edge.getTarget();
CodeExceptionGen handler = handlerBlock.getExceptionGen();
ObjectType catchType = handler.getCatchType();
if (Hierarchy.isUniversalExceptionHandler(catchType)) {
result.addAll(thrownExceptionSet);
thrownExceptionSet.clear();
} else {
// Go through the set of thrown exceptions.
// Any that will DEFINITELY be caught be this handler, remove.
// Any that MIGHT be caught, but won't definitely be caught,
// remain.
for (ExceptionSet.ThrownExceptionIterator i = thrownExceptionSet.iterator(); i.hasNext();) {
// ThrownException thrownException = i.next();
ObjectType thrownType = i.next();
boolean explicit = i.isExplicit();
if (DEBUG) {
System.out.println("\texception type " + thrownType + ", catch type " + catchType);
}
try {
if (Hierarchy.isSubtype(thrownType, catchType)) {
// Exception can be thrown along this edge
result.add(thrownType, explicit);
// And it will definitely be caught
i.remove();
if (DEBUG) {
System.out.println("\tException is subtype of catch type: " + "will definitely catch");
}
} else if (Hierarchy.isSubtype(catchType, thrownType)) {
// Exception possibly thrown along this edge
result.add(thrownType, explicit);
if (DEBUG) {
System.out.println("\tException is supertype of catch type: " + "might catch");
}
}
} catch (ClassNotFoundException e) {
// As a special case, if a class hierarchy lookup
// fails, then we will conservatively assume that the
// exception in question CAN, but WON'T NECESSARILY
// be caught by the handler.
AnalysisContext.reportMissingClass(e);
result.add(thrownType, explicit);
}
}
}
return result;
}
/**
* Compute the set of exception types that can be thrown by given basic
* block.
*
* @param basicBlock
* the basic block
* @return the set of exceptions that can be thrown by the block
*/
private ExceptionSet computeThrownExceptionTypes(BasicBlock basicBlock) throws
DataflowAnalysisException {
ExceptionSet exceptionTypeSet = exceptionSetFactory.createExceptionSet();
InstructionHandle pei = basicBlock.getExceptionThrower();
Instruction ins = pei.getInstruction();
// Get the exceptions that BCEL knows about.
// Note that all of these are unchecked.
ExceptionThrower exceptionThrower = (ExceptionThrower) ins;
Class<?>[] exceptionList = exceptionThrower.getExceptions();
for (Class<?> aExceptionList : exceptionList) {
exceptionTypeSet.addImplicit(ObjectTypeFactory.getInstance(aExceptionList.getName()));
}
// Assume that an Error may be thrown by any instruction.
exceptionTypeSet.addImplicit(Hierarchy.ERROR_TYPE);
if (ins instanceof ATHROW) {
// For ATHROW instructions, we generate *two* blocks
// for which the ATHROW is an exception thrower.
//
// - The first, empty basic block, does the null check
// - The second block, which actually contains the ATHROW,
// throws the object on the top of the operand stack
//
// We make a special case of the block containing the ATHROW,
// by removing all of the implicit exceptions,
// and using type information to figure out what is thrown.
if (basicBlock.containsInstruction(pei)) {
// This is the actual ATHROW, not the null check
// and implicit exceptions.
exceptionTypeSet.clear();
// The frame containing the thrown value is the start fact
// for the block, because ATHROW is guaranteed to be
// the only instruction in the block.
TypeFrame frame = getStartFact(basicBlock);
// Check whether or not the frame is valid.
// Sun's javac sometimes emits unreachable code.
// For example, it will emit code that follows a JSR
// subroutine call that never returns.
// If the frame is invalid, then we can just make
// a conservative assumption that anything could be
// thrown at this ATHROW.
if (!frame.isValid()) {
exceptionTypeSet.addExplicit(Type.THROWABLE);
} else if (frame.getStackDepth() == 0) {
throw new IllegalStateException("empty stack " + " thrown by " + pei + " in "
+ SignatureConverter.convertMethodSignature(methodGen));
} else {
Type throwType = frame.getTopValue();
if (throwType instanceof ObjectType) {
exceptionTypeSet.addExplicit((ObjectType) throwType);
} else if (throwType instanceof ExceptionObjectType) {
exceptionTypeSet.addAll(((ExceptionObjectType) throwType).getExceptionSet());
} else {
// Not sure what is being thrown here.
// Be conservative.
if (DEBUG) {
System.out.println("Non object type " + throwType + " thrown by " + pei + " in "
+ SignatureConverter.convertMethodSignature(methodGen));
}
exceptionTypeSet.addExplicit(Type.THROWABLE);
}
}
}
}
// If it's an InvokeInstruction, add declared exceptions and
// RuntimeException
if (ins instanceof InvokeInstruction) {
ConstantPoolGen cpg = methodGen.getConstantPool();
InvokeInstruction inv = (InvokeInstruction) ins;
ObjectType[] declaredExceptionList = Hierarchy2.findDeclaredExceptions(inv, cpg);
if (declaredExceptionList == null) {
// Couldn't find declared exceptions,
// so conservatively assume it could thrown any checked
// exception.
if (DEBUG) {
System.out.println("Couldn't find declared exceptions for "
+ SignatureConverter.convertMethodSignature(inv, cpg));
}
exceptionTypeSet.addExplicit(Hierarchy.EXCEPTION_TYPE);
} else {
for (ObjectType aDeclaredExceptionList : declaredExceptionList) {
exceptionTypeSet.addExplicit(aDeclaredExceptionList);
}
}
exceptionTypeSet.addImplicit(Hierarchy.RUNTIME_EXCEPTION_TYPE);
}
if (DEBUG) {
System.out.println(pei + " can throw " + exceptionTypeSet);
}
return exceptionTypeSet;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "(" + methodGen.getClassName() + "." + methodGen.getMethod().getName()
+ methodGen.getMethod().getSignature() + ")";
}
public boolean isImpliedByGenericTypes(ReferenceType t) {
return visitor.isImpliedByGenericTypes(t);
}
// public static void main(String[] argv) throws Exception {
// if (argv.length != 1) {
// System.err.println("Usage: " + TypeAnalysis.class.getName() +
// " <class file>");
// System.exit(1);
// }
//
// DataflowTestDriver<TypeFrame, TypeAnalysis> driver = new
// DataflowTestDriver<TypeFrame, TypeAnalysis>() {
// @Override
// public Dataflow<TypeFrame, TypeAnalysis> createDataflow(ClassContext
// classContext, Method method)
// throws CFGBuilderException, DataflowAnalysisException {
// return classContext.getTypeDataflow(method);
// }
// };
//
// driver.execute(argv[0]);
// }
}