557 lines
18 KiB
Java
557 lines
18 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
package com.ibm.wala.cfg;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
|
|
import com.ibm.wala.classLoader.BytecodeLanguage;
|
|
import com.ibm.wala.classLoader.IBytecodeMethod;
|
|
import com.ibm.wala.classLoader.IClass;
|
|
import com.ibm.wala.classLoader.IClassLoader;
|
|
import com.ibm.wala.classLoader.IMethod;
|
|
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
|
import com.ibm.wala.shrikeBT.ExceptionHandler;
|
|
import com.ibm.wala.shrikeBT.IInstruction;
|
|
import com.ibm.wala.shrikeBT.IInvokeInstruction;
|
|
import com.ibm.wala.shrikeBT.ReturnInstruction;
|
|
import com.ibm.wala.shrikeBT.ThrowInstruction;
|
|
import com.ibm.wala.shrikeCT.InvalidClassFileException;
|
|
import com.ibm.wala.types.ClassLoaderReference;
|
|
import com.ibm.wala.types.MethodReference;
|
|
import com.ibm.wala.types.TypeReference;
|
|
import com.ibm.wala.util.collections.ArrayIterator;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.debug.Assertions;
|
|
import com.ibm.wala.util.graph.impl.NodeWithNumber;
|
|
import com.ibm.wala.util.shrike.ShrikeUtil;
|
|
import com.ibm.wala.util.warnings.Warning;
|
|
import com.ibm.wala.util.warnings.Warnings;
|
|
|
|
/**
|
|
* A graph of basic blocks.
|
|
*/
|
|
public class ShrikeCFG extends AbstractCFG<IInstruction, ShrikeCFG.BasicBlock> implements BytecodeCFG {
|
|
|
|
private static final boolean DEBUG = false;
|
|
|
|
private int[] instruction2Block;
|
|
|
|
private final IBytecodeMethod method;
|
|
|
|
/**
|
|
* Cache this here for efficiency
|
|
*/
|
|
private final int hashBase;
|
|
|
|
/**
|
|
* Set of Shrike {@link ExceptionHandler} objects that cover this method.
|
|
*/
|
|
final private Set<ExceptionHandler> exceptionHandlers = HashSetFactory.make(10);
|
|
|
|
public static ShrikeCFG make(IBytecodeMethod m) {
|
|
return new ShrikeCFG(m);
|
|
}
|
|
|
|
private ShrikeCFG(IBytecodeMethod method) throws IllegalArgumentException {
|
|
super(method);
|
|
if (method == null) {
|
|
throw new IllegalArgumentException("method cannot be null");
|
|
}
|
|
this.method = method;
|
|
this.hashBase = method.hashCode() * 9967;
|
|
makeBasicBlocks();
|
|
init();
|
|
computeI2BMapping();
|
|
computeEdges();
|
|
|
|
if (DEBUG) {
|
|
System.err.println(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public IBytecodeMethod getMethod() {
|
|
return method;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return 9511 * getMethod().hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
return (o instanceof ShrikeCFG) && getMethod().equals(((ShrikeCFG) o).getMethod());
|
|
}
|
|
|
|
@Override
|
|
public IInstruction[] getInstructions() {
|
|
try {
|
|
return method.getInstructions();
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute a mapping from instruction to basic block. Also, compute the blocks that end with a 'normal' return.
|
|
*/
|
|
private void computeI2BMapping() {
|
|
instruction2Block = new int[getInstructions().length];
|
|
for (Iterator it = iterator(); it.hasNext();) {
|
|
final BasicBlock b = (BasicBlock) it.next();
|
|
for (int j = b.getFirstInstructionIndex(); j <= b.getLastInstructionIndex(); j++) {
|
|
instruction2Block[j] = getNumber(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute outgoing edges in the control flow graph.
|
|
*/
|
|
private void computeEdges() {
|
|
for (Iterator it = iterator(); it.hasNext();) {
|
|
BasicBlock b = (BasicBlock) it.next();
|
|
if (b.equals(exit())) {
|
|
continue;
|
|
} else if (b.equals(entry())) {
|
|
BasicBlock bb0 = getBlockForInstruction(0);
|
|
assert bb0 != null;
|
|
addNormalEdge(b, bb0);
|
|
} else {
|
|
b.computeOutgoingEdges();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void makeBasicBlocks() {
|
|
ExceptionHandler[][] handlers;
|
|
try {
|
|
handlers = method.getHandlers();
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
handlers = null;
|
|
}
|
|
|
|
// Compute r so r[i] == true iff instruction i begins a basic block.
|
|
boolean[] r = new boolean[getInstructions().length];
|
|
boolean[] catchers = new boolean[getInstructions().length];
|
|
r[0] = true;
|
|
IInstruction[] instructions = getInstructions();
|
|
for (int i = 0; i < instructions.length; i++) {
|
|
int[] targets = instructions[i].getBranchTargets();
|
|
|
|
// if there are any targets, then break the basic block here.
|
|
// also break the basic block after a return
|
|
if (targets.length > 0 || !instructions[i].isFallThrough()) {
|
|
if (i + 1 < instructions.length && !r[i + 1]) {
|
|
r[i + 1] = true;
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < targets.length; j++) {
|
|
if (!r[targets[j]]) {
|
|
r[targets[j]] = true;
|
|
}
|
|
}
|
|
if (instructions[i].isPEI()) {
|
|
ExceptionHandler[] hs = handlers[i];
|
|
// break the basic block here.
|
|
if (i + 1 < instructions.length && !r[i + 1]) {
|
|
r[i + 1] = true;
|
|
}
|
|
if (hs != null && hs.length > 0) {
|
|
for (int j = 0; j < hs.length; j++) {
|
|
exceptionHandlers.add(hs[j]);
|
|
if (!r[hs[j].getHandler()]) {
|
|
// we have not discovered the catch block yet.
|
|
// form a new basic block
|
|
r[hs[j].getHandler()] = true;
|
|
}
|
|
catchers[hs[j].getHandler()] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BasicBlock entry = new BasicBlock(-1);
|
|
addNode(entry);
|
|
|
|
int j = 1;
|
|
for (int i = 0; i < r.length; i++) {
|
|
if (r[i]) {
|
|
BasicBlock b = new BasicBlock(i);
|
|
addNode(b);
|
|
if (catchers[i]) {
|
|
setCatchBlock(j);
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
BasicBlock exit = new BasicBlock(-1);
|
|
addNode(exit);
|
|
}
|
|
|
|
/**
|
|
* Return an instruction's basic block in the CFG given the index of the instruction in the CFG's instruction array.
|
|
*/
|
|
@Override
|
|
public BasicBlock getBlockForInstruction(int index) {
|
|
return getNode(instruction2Block[index]);
|
|
}
|
|
|
|
public final class BasicBlock extends NodeWithNumber implements IBasicBlock<IInstruction> {
|
|
|
|
/**
|
|
* The number of the ShrikeBT instruction that begins this block.
|
|
*/
|
|
final private int startIndex;
|
|
|
|
public BasicBlock(int startIndex) {
|
|
this.startIndex = startIndex;
|
|
}
|
|
|
|
@Override
|
|
public boolean isCatchBlock() {
|
|
return ShrikeCFG.this.isCatchBlock(getNumber());
|
|
}
|
|
|
|
private void computeOutgoingEdges() {
|
|
if (DEBUG) {
|
|
System.err.println("Block " + this + ": computeOutgoingEdges()");
|
|
}
|
|
|
|
IInstruction last = getInstructions()[getLastInstructionIndex()];
|
|
int[] targets = last.getBranchTargets();
|
|
for (int i = 0; i < targets.length; i++) {
|
|
BasicBlock b = getBlockForInstruction(targets[i]);
|
|
addNormalEdgeTo(b);
|
|
}
|
|
addExceptionalEdges(last);
|
|
if (last.isFallThrough()) {
|
|
BasicBlock next = getNode(getNumber() + 1);
|
|
addNormalEdgeTo(next);
|
|
}
|
|
if (last instanceof ReturnInstruction) {
|
|
// link each return instruction to the exit block.
|
|
BasicBlock exit = exit();
|
|
addNormalEdgeTo(exit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add any exceptional edges generated by the last instruction in a basic block.
|
|
*
|
|
* @param last the last instruction in a basic block.
|
|
*/
|
|
protected void addExceptionalEdges(IInstruction last) {
|
|
IClassHierarchy cha = getMethod().getClassHierarchy();
|
|
if (last.isPEI()) {
|
|
Collection<TypeReference> exceptionTypes = null;
|
|
boolean goToAllHandlers = false;
|
|
|
|
ExceptionHandler[] hs = getExceptionHandlers();
|
|
if (last instanceof ThrowInstruction) {
|
|
// this class does not have the type information needed
|
|
// to determine what the athrow throws. So, add an
|
|
// edge to all reachable handlers. Better information can
|
|
// be obtained later with SSA type propagation.
|
|
// TODO: consider pruning to only the exception types that
|
|
// this method either catches or allocates, since these are
|
|
// the only types that can flow to an athrow.
|
|
goToAllHandlers = true;
|
|
} else {
|
|
if (hs != null && hs.length > 0) {
|
|
IClassLoader loader = getMethod().getDeclaringClass().getClassLoader();
|
|
BytecodeLanguage l = (BytecodeLanguage) loader.getLanguage();
|
|
exceptionTypes = l.getImplicitExceptionTypes(last);
|
|
if (last instanceof IInvokeInstruction) {
|
|
IInvokeInstruction call = (IInvokeInstruction) last;
|
|
exceptionTypes = HashSetFactory.make(exceptionTypes);
|
|
MethodReference target = MethodReference.findOrCreate(l, loader.getReference(), call.getClassType(), call
|
|
.getMethodName(), call.getMethodSignature());
|
|
try {
|
|
exceptionTypes.addAll(l.inferInvokeExceptions(target, cha));
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hs != null && hs.length > 0) {
|
|
// found a handler for this PEI
|
|
|
|
// create a mutable copy
|
|
if (!goToAllHandlers) {
|
|
exceptionTypes = HashSetFactory.make(exceptionTypes);
|
|
}
|
|
|
|
// this var gets set to false if goToAllHandlers is true but some enclosing exception handler catches all
|
|
// exceptions. in such a case, we need not add an exceptional edge to the method exit
|
|
boolean needEdgeToExitForAllHandlers = true;
|
|
for (int j = 0; j < hs.length; j++) {
|
|
if (DEBUG) {
|
|
System.err.println(" handler " + hs[j]);
|
|
}
|
|
BasicBlock b = getBlockForInstruction(hs[j].getHandler());
|
|
if (DEBUG) {
|
|
System.err.println(" target " + b);
|
|
}
|
|
if (goToAllHandlers) {
|
|
// add an edge to the catch block.
|
|
if (DEBUG) {
|
|
System.err.println(" gotoAllHandlers " + b);
|
|
}
|
|
addExceptionalEdgeTo(b);
|
|
// if the handler catches all exceptions, we don't need to add an edge to the exit or any other handlers
|
|
if (hs[j].getCatchClass() == null) {
|
|
needEdgeToExitForAllHandlers = false;
|
|
break;
|
|
}
|
|
} else {
|
|
TypeReference caughtException = null;
|
|
if (hs[j].getCatchClass() != null) {
|
|
ClassLoaderReference loader = ShrikeCFG.this.getMethod().getDeclaringClass().getReference().getClassLoader();
|
|
caughtException = ShrikeUtil.makeTypeReference(loader, hs[j].getCatchClass());
|
|
if (DEBUG) {
|
|
System.err.println(" caughtException " + caughtException);
|
|
}
|
|
IClass caughtClass = cha.lookupClass(caughtException);
|
|
if (caughtClass == null) {
|
|
// conservatively add the edge, and raise a warning
|
|
addExceptionalEdgeTo(b);
|
|
Warnings.add(FailedExceptionResolutionWarning.create(caughtException));
|
|
// null out caughtException, to avoid attempting to process it
|
|
caughtException = null;
|
|
}
|
|
} else {
|
|
if (DEBUG) {
|
|
System.err.println(" catchClass() == null");
|
|
}
|
|
// hs[j].getCatchClass() == null.
|
|
// this means that the handler catches all exceptions.
|
|
// add the edge and null out all types
|
|
if (!exceptionTypes.isEmpty()) {
|
|
addExceptionalEdgeTo(b);
|
|
exceptionTypes.clear();
|
|
caughtException = null;
|
|
}
|
|
}
|
|
if (caughtException != null) {
|
|
IClass caughtClass = cha.lookupClass(caughtException);
|
|
// the set "caught" should be the set of exceptions that MUST
|
|
// have been caught by the handlers in scope
|
|
ArrayList<TypeReference> caught = new ArrayList<TypeReference>(exceptionTypes.size());
|
|
// check if we should add an edge to the catch block.
|
|
for (TypeReference t : exceptionTypes) {
|
|
if (t != null) {
|
|
IClass klass = cha.lookupClass(t);
|
|
if (klass == null) {
|
|
Warnings.add(FailedExceptionResolutionWarning.create(caughtException));
|
|
// conservatively add an edge
|
|
addExceptionalEdgeTo(b);
|
|
} else {
|
|
boolean subtype1 = cha.isSubclassOf(klass, caughtClass);
|
|
if (subtype1 || cha.isSubclassOf(caughtClass, klass)) {
|
|
// add the edge and null out the type from the array
|
|
addExceptionalEdgeTo(b);
|
|
if (subtype1) {
|
|
caught.add(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exceptionTypes.removeAll(caught);
|
|
}
|
|
}
|
|
}
|
|
// if needed, add an edge to the exit block.
|
|
if ((exceptionTypes == null && needEdgeToExitForAllHandlers) || (exceptionTypes != null && !exceptionTypes.isEmpty())) {
|
|
BasicBlock exit = exit();
|
|
addExceptionalEdgeTo(exit);
|
|
}
|
|
} else {
|
|
// found no handler for this PEI ... link to the exit block.
|
|
BasicBlock exit = exit();
|
|
addExceptionalEdgeTo(exit);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ExceptionHandler[] getExceptionHandlers() {
|
|
ExceptionHandler[][] handlers;
|
|
try {
|
|
handlers = method.getHandlers();
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
handlers = null;
|
|
}
|
|
ExceptionHandler[] hs = handlers[getLastInstructionIndex()];
|
|
return hs;
|
|
}
|
|
|
|
private void addNormalEdgeTo(BasicBlock b) {
|
|
addNormalEdge(this, b);
|
|
}
|
|
|
|
private void addExceptionalEdgeTo(BasicBlock b) {
|
|
addExceptionalEdge(this, b);
|
|
}
|
|
|
|
@Override
|
|
public int getLastInstructionIndex() {
|
|
if (this == entry() || this == exit()) {
|
|
// these are the special end blocks
|
|
return -2;
|
|
}
|
|
if (getNumber() == (getMaxNumber() - 1)) {
|
|
// this is the last non-exit block
|
|
return getInstructions().length - 1;
|
|
} else {
|
|
int i = 1;
|
|
BasicBlock next;
|
|
do {
|
|
next = getNode(getNumber() + i);
|
|
} while (next == null);
|
|
return next.getFirstInstructionIndex() - 1;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getFirstInstructionIndex() {
|
|
return startIndex;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "BB[Shrike]" + getNumber() + " - " + method.getDeclaringClass().getReference().getName() + "." + method.getName();
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.cfg.BasicBlock#isExitBlock()
|
|
*/
|
|
@Override
|
|
public boolean isExitBlock() {
|
|
return this == ShrikeCFG.this.exit();
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.cfg.BasicBlock#isEntryBlock()
|
|
*/
|
|
@Override
|
|
public boolean isEntryBlock() {
|
|
return this == ShrikeCFG.this.entry();
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.cfg.BasicBlock#getMethod()
|
|
*/
|
|
@Override
|
|
public IMethod getMethod() {
|
|
return ShrikeCFG.this.getMethod();
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return hashBase + getNumber();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
return (o instanceof BasicBlock) && ((BasicBlock) o).getMethod().equals(getMethod())
|
|
&& ((BasicBlock) o).getNumber() == getNumber();
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.cfg.BasicBlock#getNumber()
|
|
*/
|
|
@Override
|
|
public int getNumber() {
|
|
return getGraphNodeId();
|
|
}
|
|
|
|
@Override
|
|
public Iterator<IInstruction> iterator() {
|
|
return new ArrayIterator<IInstruction>(getInstructions(), getFirstInstructionIndex(), getLastInstructionIndex());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuffer s = new StringBuffer("");
|
|
for (Iterator it = iterator(); it.hasNext();) {
|
|
BasicBlock bb = (BasicBlock) it.next();
|
|
s.append("BB").append(getNumber(bb)).append("\n");
|
|
for (int j = bb.getFirstInstructionIndex(); j <= bb.getLastInstructionIndex(); j++) {
|
|
s.append(" ").append(j).append(" ").append(getInstructions()[j]).append("\n");
|
|
}
|
|
|
|
Iterator<BasicBlock> succNodes = getSuccNodes(bb);
|
|
while (succNodes.hasNext()) {
|
|
s.append(" -> BB").append(getNumber(succNodes.next())).append("\n");
|
|
}
|
|
}
|
|
return s.toString();
|
|
}
|
|
|
|
@Override
|
|
public Set<ExceptionHandler> getExceptionHandlers() {
|
|
return exceptionHandlers;
|
|
}
|
|
|
|
/*
|
|
* @see com.ibm.wala.cfg.ControlFlowGraph#getProgramCounter(int)
|
|
*/
|
|
@Override
|
|
public int getProgramCounter(int index) {
|
|
try {
|
|
return method.getBytecodeIndex(index);
|
|
} catch (InvalidClassFileException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A warning when we fail to resolve the type of an exception
|
|
*/
|
|
private static class FailedExceptionResolutionWarning extends Warning {
|
|
|
|
final TypeReference T;
|
|
|
|
FailedExceptionResolutionWarning(TypeReference T) {
|
|
super(Warning.MODERATE);
|
|
this.T = T;
|
|
}
|
|
|
|
@Override
|
|
public String getMsg() {
|
|
return getClass().toString() + " : " + T;
|
|
}
|
|
|
|
public static FailedExceptionResolutionWarning create(TypeReference T) {
|
|
return new FailedExceptionResolutionWarning(T);
|
|
}
|
|
}
|
|
}
|