WALA/com.ibm.wala.core/src/com/ibm/wala/cfg/InducedCFG.java

604 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.Collections;
import java.util.Iterator;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ssa.SSAArrayLengthInstruction;
import com.ibm.wala.ssa.SSAArrayLoadInstruction;
import com.ibm.wala.ssa.SSAArrayStoreInstruction;
import com.ibm.wala.ssa.SSACheckCastInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAGotoInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInstructionFactory;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPiInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.ssa.SSAReturnInstruction;
import com.ibm.wala.ssa.SSASwitchInstruction;
import com.ibm.wala.ssa.SSAThrowInstruction;
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.GraphIntegrity;
import com.ibm.wala.util.graph.GraphIntegrity.UnsoundGraphException;
import com.ibm.wala.util.graph.impl.NodeWithNumber;
/**
* A {@link ControlFlowGraph} computed from a set of {@link SSAInstruction} instructions.
*
* This is a funny CFG ... we assume that there are always fallthru edges, even from throws and returns. It is extremely fragile and
* unsuited for flow-sensitive analysis. Someday this should be nuked.
*/
public class InducedCFG extends AbstractCFG<SSAInstruction, InducedCFG.BasicBlock> {
private static final boolean DEBUG = false;
/**
* A partial map from Instruction -> BasicBlock
*/
private final BasicBlock[] i2block;
private final Context context;
private final SSAInstruction[] instructions;
/**
* TODO: we do not yet support induced CFGS with exception handlers.
*
* NOTE: SIDE EFFECT!!! ... nulls out phi instructions and pi instructions in the instruction array!
*
* @throws IllegalArgumentException if instructions is null
*/
public InducedCFG(SSAInstruction[] instructions, IMethod method, Context context) {
super(method);
if (instructions == null) {
throw new IllegalArgumentException("instructions is null");
}
if (method == null) {
throw new IllegalArgumentException("method is null");
}
this.context = context;
this.instructions = instructions;
if (DEBUG) {
System.err.println(("compute InducedCFG: " + method));
}
i2block = new BasicBlock[instructions.length];
if (instructions.length == 0) {
makeEmptyBlocks();
} else {
makeBasicBlocks();
}
init();
computeEdges();
if (DEBUG) {
try {
GraphIntegrity.check(this);
} catch (UnsoundGraphException e) {
e.printStackTrace();
Assertions.UNREACHABLE();
}
}
}
@Override
public int hashCode() {
return context.hashCode() ^ getMethod().hashCode();
}
@Override
public boolean equals(Object o) {
return (o instanceof InducedCFG) && getMethod().equals(((InducedCFG) o).getMethod())
&& context.equals(((InducedCFG) o).context);
}
public SSAInstruction[] getInstructions() {
return instructions;
}
/**
* 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;
b.computeOutgoingEdges();
}
clearPis(getInstructions());
}
private void clearPis(SSAInstruction[] instructions) {
for (int i = 0; i < instructions.length; i++) {
if (instructions[i] instanceof SSAPiInstruction) {
instructions[i] = null;
}
}
}
/**
* Create basic blocks for an empty method
*/
private void makeEmptyBlocks() {
BasicBlock b = new BasicBlock(-1);
addNode(b);
}
protected BranchVisitor makeBranchVisitor(boolean[] r) {
return new BranchVisitor(r);
}
protected PEIVisitor makePEIVisitor(boolean[] r) {
return new PEIVisitor(r);
}
/**
* Walk through the instructions and compute basic block boundaries.
*/
private void makeBasicBlocks() {
SSAInstruction[] instructions = getInstructions();
final boolean[] r = new boolean[instructions.length];
// Compute r so r[i] == true iff instruction i begins a basic block.
// While doing so count the number of blocks.
r[0] = true;
BranchVisitor branchVisitor = makeBranchVisitor(r);
PEIVisitor peiVisitor = makePEIVisitor(r);
for (int i = 0; i < instructions.length; i++) {
if (instructions[i] != null) {
branchVisitor.setIndex(i);
instructions[i].visit(branchVisitor);
// TODO: deal with exception handlers
peiVisitor.setIndex(i);
instructions[i].visit(peiVisitor);
}
}
BasicBlock b = null;
for (int i = 0; i < r.length; i++) {
if (r[i]) {
b = new BasicBlock(i);
addNode(b);
int j = i;
while (instructions[j] instanceof SSAPhiInstruction) {
b.addPhi((SSAPhiInstruction) instructions[j]);
j++;
}
if (DEBUG) {
System.err.println(("Add basic block " + b));
}
}
if (instructions[i] instanceof SSAPiInstruction) {
// add it to the current basic block
b.addPi((SSAPiInstruction) instructions[i]);
}
i2block[i] = b;
}
// allocate the exit block
BasicBlock exit = new BasicBlock(-1);
if (DEBUG) {
System.err.println(("Add exit block " + exit));
}
addNode(exit);
clearPhis(instructions);
}
/**
* set to null any slots in the array with phi instructions
*/
private void clearPhis(SSAInstruction[] instructions) {
for (int i = 0; i < instructions.length; i++) {
if (instructions[i] instanceof SSAPhiInstruction) {
instructions[i] = null;
}
}
}
/**
* This visitor identifies basic block boundaries induced by branch instructions.
*/
public class BranchVisitor extends SSAInstruction.Visitor {
final private boolean[] r;
protected BranchVisitor(boolean[] r) {
this.r = r;
}
int index = 0;
void setIndex(int i) {
index = i;
}
@Override
public void visitGoto(SSAGotoInstruction instruction) {
Assertions.UNREACHABLE("haven't implemented logic for goto yet.");
breakBasicBlock(index);
}
@Override
public void visitConditionalBranch(SSAConditionalBranchInstruction instruction) {
Assertions.UNREACHABLE("haven't implemented logic for cbranch yet.");
breakBasicBlock(index);
}
@Override
public void visitSwitch(SSASwitchInstruction instruction) {
Assertions.UNREACHABLE("haven't implemented logic for switch yet.");
// breakBasicBlock();
// int[] targets = instruction.getTargets();
// for (int i = 0; i < targets.length; i++) {
// r[targets[i]] = true;
// }
}
@Override
public void visitPhi(SSAPhiInstruction instruction) {
// we can have more than one phi instruction in a row. break the basic block
// only before the first one.
if (!(instructions[index - 1] instanceof SSAPhiInstruction)) {
breakBasicBlock(index - 1);
}
}
@Override
public void visitReturn(SSAReturnInstruction instruction) {
breakBasicBlock(index);
}
@Override
public void visitThrow(SSAThrowInstruction instruction) {
breakBasicBlock(index);
}
/**
* introduce a basic block boundary immediately after instruction number 'index' if it is not followed by pi instructions, or
* after the pi instructions otherwise
*/
protected void breakBasicBlock(int index) {
int j = index + 1;
while (j < instructions.length && instructions[j] instanceof SSAPiInstruction) {
j++;
}
if (j < instructions.length && !r[j]) {
r[j] = true;
}
}
}
// TODO: extend the following to deal with catch blocks. Right now
// it simply breaks basic blocks at PEIs.
public class PEIVisitor extends SSAInstruction.Visitor {
final private boolean[] r;
protected PEIVisitor(boolean[] r) {
this.r = r;
}
int index = 0;
void setIndex(int i) {
index = i;
}
protected void breakBasicBlock() {
int j = index + 1;
while (j < instructions.length && instructions[j] instanceof SSAPiInstruction) {
j++;
}
if (j < instructions.length && !r[j]) {
r[j] = true;
}
}
@Override
public void visitArrayLength(SSAArrayLengthInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitArrayLoad(SSAArrayLoadInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitArrayStore(SSAArrayStoreInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitCheckCast(SSACheckCastInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitGet(SSAGetInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitInvoke(SSAInvokeInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitMonitor(SSAMonitorInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitNew(SSANewInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitPut(SSAPutInstruction instruction) {
breakBasicBlock();
}
@Override
public void visitThrow(com.ibm.wala.ssa.SSAThrowInstruction instruction) {
breakBasicBlock();
}
}
public BasicBlock getBlockForInstruction(int index) {
if (i2block[index] == null) {
Assertions.productionAssertion(false, "unexpected null for " + index);
}
return i2block[index];
}
// TODO: share some common parts of this implementation with the ShrikeCFG
// implementation! right now it's clone-and-owned :(
public class BasicBlock extends NodeWithNumber implements IBasicBlock<SSAInstruction> {
private Collection<SSAPhiInstruction> phis;
public Collection<SSAPhiInstruction> getPhis() {
return phis == null ? Collections.<SSAPhiInstruction> emptyList() : Collections.unmodifiableCollection(phis);
}
public void addPhi(SSAPhiInstruction phiInstruction) {
if (phis == null) {
phis = new ArrayList<SSAPhiInstruction>(1);
}
phis.add(phiInstruction);
}
private ArrayList<SSAPiInstruction> pis;
public Collection<SSAPiInstruction> getPis() {
return pis == null ? Collections.<SSAPiInstruction> emptyList() : Collections.unmodifiableCollection(pis);
}
public void addPi(SSAPiInstruction piInstruction) {
if (pis == null) {
pis = new ArrayList<SSAPiInstruction>(1);
}
pis.add(piInstruction);
}
@Override
public boolean equals(Object arg0) {
if (arg0 != null && getClass().equals(arg0.getClass())) {
BasicBlock other = (BasicBlock) arg0;
return start == other.start && getMethod().equals(other.getMethod());
} else {
return false;
}
}
private final int start;
BasicBlock(int start) {
this.start = start;
}
/**
* Add any exceptional edges generated by the last instruction in a basic block.
*
* @param last the last instruction in a basic block.
*/
private void addExceptionalEdges(SSAInstruction last) {
if (last.isPEI()) {
// we don't currently model catch blocks here ... instead just link
// to the exit block
addExceptionalEdgeTo(exit());
}
}
private void addNormalEdgeTo(BasicBlock b) {
addNormalEdge(this, b);
}
private void addExceptionalEdgeTo(BasicBlock b) {
addExceptionalEdge(this, b);
}
private void computeOutgoingEdges() {
if (DEBUG) {
System.err.println(("Block " + this + ": computeOutgoingEdges()"));
}
// TODO: we don't currently model branches
SSAInstruction last = getInstructions()[getLastInstructionIndex()];
addExceptionalEdges(last);
// this CFG is odd in that we assume fallthru might always
// happen .. this is because I'm too lazy to code control
// flow in all method summaries yet.
int normalSuccNodeNumber = getGraphNodeId() + 1;
if (true) {
// if (last.isFallThrough()) {
if (DEBUG) {
System.err.println(("Add fallthru to " + getNode(getGraphNodeId() + 1)));
}
addNormalEdgeTo(getNode(normalSuccNodeNumber));
}
if (pis != null) {
updatePiInstrs(normalSuccNodeNumber);
}
if (last instanceof SSAReturnInstruction) {
// link each return instrution to the exit block.
BasicBlock exit = exit();
addNormalEdgeTo(exit);
}
}
/**
* correct pi instructions with appropriate basic block numbers. we assume for now that pi instructions are always associated
* with the normal "fall-thru" exit edge.
*/
private void updatePiInstrs(int normalSuccNodeNumber) {
for (int i = 0; i < pis.size(); i++) {
SSAPiInstruction pi = pis.get(i);
SSAInstructionFactory insts = getMethod().getDeclaringClass().getClassLoader().getInstructionFactory();
pis.set(i, insts.PiInstruction(SSAInstruction.NO_INDEX, pi.getDef(), pi.getVal(), getGraphNodeId(), normalSuccNodeNumber, pi.getCause()));
}
}
public int getFirstInstructionIndex() {
return start;
}
public int getLastInstructionIndex() {
int exitNumber = InducedCFG.this.getNumber(exit());
if (getGraphNodeId() == exitNumber) {
// this is the exit block
return -2;
}
if (getGraphNodeId() == (exitNumber - 1)) {
// this is the last non-exit block
return getInstructions().length - 1;
} else {
BasicBlock next = getNode(getGraphNodeId() + 1);
return next.getFirstInstructionIndex() - 1;
}
}
public boolean isCatchBlock() {
// TODO: support induced CFG with catch blocks.
return false;
}
@Override
public int hashCode() {
return 1153 * getGraphNodeId() + getMethod().hashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "BB[Induced]" + getNumber() + " - " + getMethod().getSignature();
}
/*
* @see com.ibm.wala.cfg.IBasicBlock#isExitBlock()
*/
public boolean isExitBlock() {
return getLastInstructionIndex() == -2;
}
/*
* @see com.ibm.wala.cfg.IBasicBlock#isEntryBlock()
*/
public boolean isEntryBlock() {
return getNumber() == 0;
}
/*
* @see com.ibm.wala.cfg.IBasicBlock#getMethod()
*/
public IMethod getMethod() {
return InducedCFG.this.getMethod();
}
public boolean endsInPEI() {
return getInstructions()[getLastInstructionIndex()].isPEI();
}
public boolean endsInReturn() {
return getInstructions()[getLastInstructionIndex()] instanceof SSAReturnInstruction;
}
/*
* @see com.ibm.wala.cfg.IBasicBlock#getNumber()
*/
public int getNumber() {
return InducedCFG.this.getNumber(this);
}
public Iterator<SSAInstruction> iterator() {
return new ArrayIterator<SSAInstruction>(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();
}
/**
* Since this CFG is synthetic, for now we assume the instruction index is the same as the program counter
*
* @see com.ibm.wala.cfg.ControlFlowGraph#getProgramCounter(int)
*/
public int getProgramCounter(int index) {
if (getInstructions().length <= index) {
throw new IllegalArgumentException("invalid index " + index + " " + getInstructions().length);
}
if (getInstructions()[index] instanceof SSAInvokeInstruction) {
return ((SSAInvokeInstruction) getInstructions()[index]).getCallSite().getProgramCounter();
} else {
return index;
}
}
public Collection<SSAPhiInstruction> getAllPhiInstructions() {
Collection<SSAPhiInstruction> result = HashSetFactory.make();
for (Iterator<BasicBlock> it = iterator(); it.hasNext();) {
BasicBlock b = it.next();
result.addAll(b.getPhis());
}
return result;
}
}