/*
* Bytecode Analysis Framework
* Copyright (C) 2003-2007 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;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.MethodGen;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefAnalysis;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefDataflow;
import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefSet;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
/**
* Perform dataflow analysis on a method using a control flow graph. Both
* forward and backward analyses can be performed.
* <ul>
* <li>The "start" point of each block is the entry (forward analyses) or the
* exit (backward analyses).
* <li>The "result" point of each block is the exit (forward analyses) or the
* entry (backward analyses).
* </ul>
* The analysis's transfer function is applied to transform the meet of the
* results of the block's logical predecessors (the block's start facts) into
* the block's result facts.
*
* @author David Hovemeyer
* @see CFG
* @see DataflowAnalysis
*/
public class Dataflow<Fact, AnalysisType extends DataflowAnalysis<Fact>> {
private CFG cfg;
private AnalysisType analysis;
private BlockOrder blockOrder;
private boolean isForwards;
private int numIterations;
public static boolean DEBUG = SystemProperties.getBoolean("dataflow.debug");
/**
* Constructor.
*
* @param cfg
* the control flow graph
* @param analysis
* the DataflowAnalysis to be run
*/
public Dataflow(CFG cfg, AnalysisType analysis) {
this.cfg = cfg;
this.analysis = analysis;
blockOrder = analysis.getBlockOrder(cfg);
isForwards = analysis.isForwards();
numIterations = 0;
// Initialize result facts
Iterator<BasicBlock> i = cfg.blockIterator();
while (i.hasNext()) {
BasicBlock block = i.next();
Fact result = analysis.getResultFact(block);
if (block == logicalEntryBlock()) {
try {
// Entry block: set to entry fact
analysis.initEntryFact(result);
} catch (DataflowAnalysisException e) {
analysis.makeFactTop(result);
}
} else {
// Set to top
analysis.makeFactTop(result);
}
}
}
// Maximum number of iterations before we assume there is a bug and give up.
private static final int MAX_ITERS = SystemProperties.getInt("dataflow.maxiters", 97);
private String getFullyQualifiedMethodName() {
String methodName;
MethodGen methodGen = cfg.getMethodGen();
if (methodGen == null)
methodName = cfg.getMethodName();
else
methodName = SignatureConverter.convertMethodSignature(methodGen);
return methodName;
}
static class ForwardProgramOrder implements Comparator<BasicBlock>, Serializable {
public int compare(BasicBlock o1, BasicBlock o2) {
int p1 = o1.getLabel();
int p2 = o2.getLabel();
return p1 - p2;
}
}
static class BackwardProgramOrder extends ForwardProgramOrder {
@Override
public int compare(BasicBlock o1, BasicBlock o2) {
return super.compare(o2, o1);
}
}
/**
* Run the algorithm. Afterwards, caller can use the getStartFact() and
* getResultFact() methods to to get dataflow facts at start and result
* points of each block.
*/
public void execute() throws DataflowAnalysisException {
boolean change;
boolean debugWas = DEBUG;
if (DEBUG) {
reportAnalysis("Executing");
}
int timestamp = 0;
boolean firstTime = true;
do {
change = false;
boolean sawBackEdge = false;
++numIterations;
if (numIterations > MAX_ITERS && !DEBUG) {
DEBUG = true;
reportAnalysis("Too many iterations");
System.out.println(this.getClass().getName());
if (this.getClass() == UnconditionalValueDerefDataflow.class || this.getClass() == LiveLocalStoreDataflow.class) {
try {
ClassContext cc = Global.getAnalysisCache().getClassAnalysis(ClassContext.class,
DescriptorFactory.createClassDescriptorFromDottedClassName(cfg.getMethodGen().getClassName()));
System.out.println("Forwards cfg");
CFGPrinter printer = new CFGPrinter(cfg);
printer.setIsForwards(true);
printer.print(System.out);
System.out.println("Backwards cfg");
printer = new CFGPrinter(cfg);
printer.setIsForwards(false);
printer.print(System.out);
cc.dumpSimpleDataflowInformation(cfg.getMethodGen().getMethod());
} catch (CheckedAnalysisException e) {
e.printStackTrace(System.out);
}
}
}
if (DEBUG) {
System.out.println("----------------------------------------------------------------------");
System.out.println(this.getClass().getName() + " iteration: " + numIterations + ", timestamp: " + timestamp);
MethodGen mg = cfg.getMethodGen();
System.out.println(mg.getClassName() + "." + mg.getName() + mg.getSignature());
System.out.println("----------------------------------------------------------------------");
}
if (numIterations >= MAX_ITERS + 9) {
throw new DataflowAnalysisException("Too many iterations (" + numIterations + ") in dataflow when analyzing "
+ getFullyQualifiedMethodName());
}
analysis.startIteration();
if (DEBUG && firstTime && blockOrder instanceof ReverseDFSOrder) {
ReverseDFSOrder rBlockOrder = (ReverseDFSOrder) blockOrder;
System.out.println("Entry point is: " + logicalEntryBlock());
System.out.println("Basic block order: ");
Iterator<BasicBlock> i = blockOrder.blockIterator();
while (i.hasNext()) {
BasicBlock block = i.next();
debug(block, "rBlockOrder " + rBlockOrder.rdfs.getDiscoveryTime(block) + "\n");
}
}
Iterator<BasicBlock> i = blockOrder.blockIterator();
if (numIterations > 3 && numIterations % 2 == 0 && blockOrder instanceof ReverseDFSOrder) {
if (DEBUG)
System.out.println("Trying program order");
TreeSet<BasicBlock> bb = new TreeSet<BasicBlock>(new BackwardProgramOrder());
Iterator<BasicBlock> j = blockOrder.blockIterator();
while (j.hasNext()) {
BasicBlock block = j.next();
bb.add(block);
}
if (DEBUG)
for (BasicBlock block : bb) {
debug(block, "\n");
}
i = bb.iterator();
}
if (DEBUG)
dumpDataflow(analysis);
// For each block in CFG...
while (i.hasNext()) {
BasicBlock block = i.next();
// Get start fact for block.
Fact start = analysis.getStartFact(block);
assert start != null;
boolean needToRecompute = false;
// Get result facts for block,
Fact result = analysis.getResultFact(block);
assert result != null;
int originalResultTimestamp = analysis.getLastUpdateTimestamp(result);
// Meet all of the logical predecessor results into this block's
// start.
// Special case: if the block is the logical entry, then it gets
// the special "entry fact".
if (block == logicalEntryBlock()) {
analysis.makeFactTop(start);
analysis.initEntryFact(start);
if (DEBUG)
debug(block, "Init entry fact ==> " + analysis.factToString(start) + "\n");
needToRecompute = true;
} else {
int lastCalculated = analysis.getLastUpdateTimestamp(start);
Iterator<Edge> predEdgeIter = logicalPredecessorEdgeIterator(block);
int predCount = 0;
int rawPredCount = 0;
while (predEdgeIter.hasNext()) {
Edge edge = predEdgeIter.next();
rawPredCount++;
if (needToRecompute) {
// don't need to check to see if we need to recompute.
if (firstTime && !sawBackEdge) {
// may need to se sawBackEdge
} else {
continue;
}
}
BasicBlock logicalPred = isForwards ? edge.getSource() : edge.getTarget();
int direction = blockOrder.compare(block, logicalPred);
if (DEBUG)
debug(block, "direction " + direction + " for " + blockId(logicalPred) + "\n");
if (direction < 0)
sawBackEdge = true;
// Get the predecessor result fact
Fact predFact = analysis.getResultFact(logicalPred);
int predLastUpdated = analysis.getLastUpdateTimestamp(predFact);
if (!analysis.isTop(predFact)) {
predCount++;
if (predLastUpdated >= lastCalculated) {
needToRecompute = true;
if (DEBUG) {
debug(block, "\n Need to recompute. My timestamp = " + lastCalculated + ", pred timestamp = "
+ predLastUpdated + ",\n pred fact = " + predFact + "\n");
}
// break;
}
}
}
if (predCount == 0)
needToRecompute = true;
if (!needToRecompute) {
continue;
}
if (needToRecompute) {
analysis.makeFactTop(start);
predEdgeIter = logicalPredecessorEdgeIterator(block);
while (predEdgeIter.hasNext()) {
Edge edge = predEdgeIter.next();
BasicBlock logicalPred = isForwards ? edge.getSource() : edge.getTarget();
// Get the predecessor result fact
Fact predFact = analysis.getResultFact(logicalPred);
// Apply the edge transfer function.
Fact edgeFact = analysis.createFact();
analysis.copy(predFact, edgeFact);
analysis.edgeTransfer(edge, edgeFact);
if (DEBUG && !analysis.same(edgeFact, predFact)) {
debug(block, logicalPred, edge, "Edge transfer " + analysis.factToString(predFact) + " ==> "
+ analysis.factToString(edgeFact));
}
// Merge the predecessor fact (possibly transformed
// by the edge transfer function)
// into the block's start fact.
if (DEBUG) {
if (analysis.isTop(start))
debug(block, logicalPred, edge, "\n First pred is " + analysis.factToString(edgeFact)
+ "\n last updated at " + analysis.getLastUpdateTimestamp(predFact) + "\n");
else
debug(block, logicalPred, edge, "\n Meet " + analysis.factToString(start) + "\n with "
+ analysis.factToString(edgeFact)
+ "\n pred last updated at " + analysis.getLastUpdateTimestamp(predFact) + "\n");
}
if (analysis instanceof UnconditionalValueDerefAnalysis) {
((UnconditionalValueDerefAnalysis) analysis).meetInto((UnconditionalValueDerefSet) edgeFact,
edge, (UnconditionalValueDerefSet) start, rawPredCount == 1);
} else
analysis.meetInto(edgeFact, edge, start);
analysis.setLastUpdateTimestamp(start, timestamp);
int pos = -1;
if (block.getFirstInstruction() != null)
pos = block.getFirstInstruction().getPosition();
if (DEBUG)
System.out.println(" [" + pos + "]==> " + analysis.factToString(start) + " @ " + timestamp
+ " \n");
}
}
}
if (DEBUG)
debug(block, "start fact is " + analysis.factToString(start) + "\n");
// making a copy of result facts (so we can detect if it
// changed).
boolean resultWasTop = analysis.isTop(result);
Fact origResult = null;
if (!resultWasTop) {
origResult = analysis.createFact();
analysis.copy(result, origResult);
}
if (true || analysis.isTop(start)) {
// Apply the transfer function.
analysis.transfer(block, null, start, result);
} else {
analysis.copy(start, result);
}
if (DEBUG && SystemProperties.getBoolean("dataflow.blockdebug")) {
debug(block, "Dumping flow values for block:\n");
Iterator<org.apache.bcel.generic.InstructionHandle> ii = block.instructionIterator();
while (ii.hasNext()) {
org.apache.bcel.generic.InstructionHandle handle = ii.next();
Fact tmpResult = analysis.createFact();
analysis.transfer(block, handle, start, tmpResult);
System.out.println("\t" + handle + " " + analysis.factToString(tmpResult));
}
}
// See if the result changed.
if (DEBUG)
debug(block, "orig result is " + (origResult == null ? "TOP" : analysis.factToString(origResult)) + "\n");
boolean thisResultChanged = false;
if (resultWasTop)
thisResultChanged = !analysis.isTop(result);
else
thisResultChanged = !analysis.same(result, origResult);
if (thisResultChanged) {
timestamp++;
if (DEBUG)
debug(block, "result changed at timestamp " + timestamp + "\n");
if (DEBUG && !needToRecompute) {
System.out.println("I thought I didn't need to recompute");
}
change = true;
analysis.setLastUpdateTimestamp(result, timestamp);
} else
analysis.setLastUpdateTimestamp(result, originalResultTimestamp);
if (DEBUG)
debug(block,
"result is " + analysis.factToString(result) + " @ timestamp "
+ analysis.getLastUpdateTimestamp(result) + "\n");
}
analysis.finishIteration();
if (!sawBackEdge) break;
} while (change);
if (DEBUG) {
System.out.println("-- Quiescence achieved-------------------------------------------------");
System.out.println(this.getClass().getName() + " iteration: " + numIterations + ", timestamp: " + timestamp);
MethodGen mg = cfg.getMethodGen();
System.out.println(mg.getClassName() + "." + mg.getName() + mg.getSignature());
new RuntimeException("Quiescence achieved----------------------------------------------------------------")
.printStackTrace(System.out);
}
DEBUG = debugWas;
}
/**
* @param msg
* TODO
*
*/
private void reportAnalysis(String msg) {
String shortAnalysisName = analysis.getClass().getName();
int pkgEnd = shortAnalysisName.lastIndexOf('.');
if (pkgEnd >= 0) {
shortAnalysisName = shortAnalysisName.substring(pkgEnd + 1);
}
System.out.println(msg + " " + shortAnalysisName + " on " + getFullyQualifiedMethodName());
}
private static String blockId(BasicBlock bb) {
InstructionHandle handle = bb.getFirstInstruction();
if (handle == null)
return "" + bb.getLabel();
return bb.getLabel() + ":" + handle.getPosition() + " " + handle.getInstruction();
}
private static void debug(BasicBlock bb, String msg) {
System.out.print("Dataflow (block " + blockId(bb) + "): " + msg);
}
private static void debug(BasicBlock bb, BasicBlock pred, Edge edge, String msg) {
System.out.print("Dataflow (block " + blockId(bb) + ", predecessor " + blockId(pred) + " ["
+ Edge.edgeTypeToString(edge.getType()) + "]): " + msg);
}
/**
* Return the number of iterations of the main execution loop.
*/
public int getNumIterations() {
return numIterations;
}
/**
* Get dataflow facts for start of given block.
*/
public Fact getStartFact(BasicBlock block) {
return analysis.getStartFact(block);
}
/**
* Get dataflow facts for end of given block.
*/
public Fact getResultFact(BasicBlock block) {
return analysis.getResultFact(block);
}
/**
* Get dataflow fact at (just before) given Location. Note "before" is meant
* in the logical sense, so for backward analyses, before means after the
* location in the control flow sense.
*
* @param location
* the Location
* @return the dataflow value at given Location
* @throws DataflowAnalysisException
*/
public/* final */Fact getFactAtLocation(Location location) throws DataflowAnalysisException {
return analysis.getFactAtLocation(location);
}
/**
* Get the dataflow fact representing the point just after given Location.
* Note "after" is meant in the logical sense, so for backward analyses,
* after means before the location in the control flow sense.
*
* @param location
* the Location
* @return the dataflow value after given Location
* @throws DataflowAnalysisException
*/
public/* final */Fact getFactAfterLocation(Location location) throws DataflowAnalysisException {
return analysis.getFactAfterLocation(location);
}
/**
* Get the fact that is true on the given control edge.
*
* @param edge
* the edge
* @return the fact that is true on the edge
* @throws DataflowAnalysisException
*/
public Fact getFactOnEdge(Edge edge) throws DataflowAnalysisException {
return analysis.getFactOnEdge(edge);
}
/**
* Get the analysis object.
*/
public AnalysisType getAnalysis() {
return analysis;
}
/**
* Get the CFG object.
*/
public CFG getCFG() {
return cfg;
}
/**
* Return an Iterator over edges that connect given block to its logical
* predecessors. For forward analyses, this is the incoming edges. For
* backward analyses, this is the outgoing edges.
*/
private Iterator<Edge> logicalPredecessorEdgeIterator(BasicBlock block) {
return isForwards ? cfg.incomingEdgeIterator(block) : cfg.outgoingEdgeIterator(block);
}
/**
* Get the "logical" entry block of the CFG. For forward analyses, this is
* the entry block. For backward analyses, this is the exit block.
*/
private BasicBlock logicalEntryBlock() {
return isForwards ? cfg.getEntry() : cfg.getExit();
}
public void dumpDataflow(AnalysisType analysis) {
System.out.println(this.getClass().getName() + " analysis for " + getCFG().getMethodName() + getCFG().getMethodSig()
+ " { ");
try {
for (Location loc : getCFG().orderedLocations()) {
System.out.println("\nBefore: " + analysis.factToString(getFactAtLocation(loc)));
System.out.println("Location: " + loc);
System.out.println("After: " + analysis.factToString(getFactAfterLocation(loc)));
}
} catch (DataflowAnalysisException e) {
AnalysisContext.logError("error dumping dataflow analysis", e);
System.out.println(e);
}
System.out.println("}");
}
}
// vim:ts=4