/* Soot - a J*va Optimization Framework
* Copyright (C) 2005 Antoine Mine
*
* 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.
*/
/**
* Implementation of the paper "A Combined Pointer and Purity Analysis for
* Java Programs" by Alexandru Salcianu and Martin Rinard, within the
* Soot Optimization Framework.
*
* by Antoine Mine, 2005/01/24
*/
package soot.jimple.toolkits.annotation.purity;
import java.util.*;
import java.io.*;
import soot.*;
import soot.util.dot.*;
import soot.jimple.*;
import soot.jimple.toolkits.callgraph.*;
import soot.toolkits.graph.*;
/**
* Inter-procedural iterator skeleton for summary-based analysis
*
* A "summary" is an abstract element associated to each method that
* fully models the effect of calling the method. In a summary-based
* analysis, the summary of a method can be computed using solely
* the summary of all methods it calls: the summary does not depend
* upon the context in which a method is called.
* The inter-procedural analysis interacts with a intra-procedural analysis
* that is able to compute the summary of one method, given the summary
* of all the method it calls. The inter-procedural analysis calls the
* intra-procedural analysis in a reverse topological order of method
* dependencies to resolve unknown summaries. It iterates over recursively
* dependant methods.
*
* Generally, the intra-procedural works by maintaining an abstract
* value that represent the effect of the method from its entry point
* and up to the current point. At the entry point, this value is empty.
* The summary of the method is then the merge of the abstract values
* at all its return points.
*
* You can provide off-the-shelf summaries for methods you do not
* which to analyse. Any method using these "filtered-out" methods will
* use the off-the-shelf summary instead of performing an intra-procedural
* analysis. This is useful for native methods, incremental analysis,
* or when you hand-made summary. Methods that are called solely by
* filtered-out ones will never be analysed, effectively triming the
* call-graph dependencies.
*
* This class tries to use the same abstract methods and data managnment
* policy as regular FlowAnalysis classes.
*/
public abstract class AbstractInterproceduralAnalysis {
public static final boolean doCheck = false;
protected CallGraph cg; // analysed call-graph
protected DirectedGraph dg; // filtered trimed call-graph
protected Map data; // SootMethod -> summary
protected Map<Object,Integer> order; // SootMethod -> topo order
protected Map unanalysed; // SootMethod -> summary
/** Initial summary value for analysed funtions. */
protected abstract Object newInitialSummary();
/**
* Whenever the analyse requires the summary of a method you filtered-out,
* this function is called instead of analyseMethod.
*
* <p> Note: This function is called at most once per filtered-out
* method. It is the equivalent of entryInitialFlow!
*
*/
protected abstract Object summaryOfUnanalysedMethod(SootMethod method);
/**
* Compute the summary for a method by analysing its body.
*
* Will be called only on methods not filtered-out.
*
* @param method is the method to be analysed
* @param dst is where to put the computed method summary
*/
protected abstract void analyseMethod(SootMethod method,
Object dst);
/**
* Interprocedural analysis will call applySummary repeatidly as a
* consequence to analyseCall. Once for each possible target method
* of the callStmt statement, provided with its summary.
*
* @param src summary valid before the call statement
* @param callStmt a statement containing a InvokeStmt or InvokeExpr
* @param summary summary of the possible target of callStmt considered
* here
* @param dst where to put the result
* @see analyseCall
*/
protected abstract void applySummary(Object src,
Stmt callStmt,
Object summary,
Object dst);
/**
* Merge in1 and in2 into out.
*
* <p> Note: in1 or in2 can be aliased to out (e.g., analyseCall).
*/
protected abstract void merge(Object in1, Object in2, Object out);
/** Copy src into dst. */
protected abstract void copy(Object sr, Object dst);
/**
* Called by drawAsOneDot to fill dot subgraph out with the contents
* of summary o.
*
* @param prefix gives you a unique string to prefix your node names
* and avoid name-clash
*/
protected void fillDotGraph(String prefix, Object o, DotGraph out)
{ throw new Error("abstract function AbstractInterproceduralAnalysis.fillDotGraph called but not implemented."); }
/**
* Analyse the call callStmt in the context src, and put the resul into
* dst.
* This will repeatidly calling summaryOfUnanalysedMethod and applySummary,
* and then merging the results using merge.
*
* @see summaryOfUnanalysedMethod
* @see applySummary
*/
protected void analyseCall(Object src,
Stmt callStmt,
Object dst)
{
Object accum = newInitialSummary();
Iterator it = cg.edgesOutOf(callStmt);
copy(accum, dst);
while (it.hasNext()) {
Edge edge = (Edge)it.next();
SootMethod m = edge.tgt();
Object elem;
if (data.containsKey(m)) {
// analysed method
elem = data.get(m);
}
else {
// unanalysed method
if (!unanalysed.containsKey(m))
unanalysed.put(m, summaryOfUnanalysedMethod(m));
elem = unanalysed.get(m);
}
applySummary(src, callStmt, elem, accum);
merge(dst, accum, dst);
}
}
/**
* The constructor performs some preprocessing, but you have to call
* doAnalysis to preform the real stuff.
*/
public AbstractInterproceduralAnalysis(CallGraph cg,
SootMethodFilter filter,
Iterator heads,
boolean verbose)
{
this.cg = cg;
this.dg = new DirectedCallGraph(cg, filter, heads, verbose);
this.data = new HashMap();
this.unanalysed = new HashMap();
// construct reverse pseudo topological order on filtered methods
this.order = new HashMap();
Orderer o = new PseudoTopologicalOrderer();
Iterator it = (o.newList(dg,true)).iterator();
int i = 0;
while (it.hasNext()) {
this.order.put(it.next(), new Integer(i));
i++;
}
}
/**
* Dump the interprocedural analysis result as a graph.
* One node / subgraph for each analysed method that contains the
* method summary, and call-to edges.
*
* <p> Note: this graph does not show filtered-out methods for which a
* conservative summary was asked via summaryOfUnanalysedMethod.
*
* @param name output filename
* @see fillDotGraph
*/
public void drawAsOneDot(String name)
{
DotGraph dot = new DotGraph(name);
dot.setGraphLabel(name);
dot.setGraphAttribute("compound","true");
//dot.setGraphAttribute("rankdir","LR");
int id = 0;
Map<SootMethod, Integer> idmap = new HashMap<SootMethod, Integer>();
// draw sub-graph cluster
Iterator it = dg.iterator();
while (it.hasNext()) {
SootMethod m = (SootMethod)it.next();
DotGraph sub = dot.createSubGraph("cluster"+id);
DotGraphNode label = sub.drawNode("head"+id);
idmap.put(m, new Integer(id));
sub.setGraphLabel("");
label.setLabel("("+order.get(m)+") "+m.toString());
label.setAttribute("fontsize","18");
label.setShape("box");
if (data.containsKey(m))
fillDotGraph("X"+id, data.get(m), sub);
id++;
}
// connect edges
it = dg.iterator();
while (it.hasNext()) {
SootMethod m = (SootMethod)it.next();
Iterator itt = dg.getSuccsOf(m).iterator();
while (itt.hasNext()) {
SootMethod mm = (SootMethod)itt.next();
DotGraphEdge edge = dot.drawEdge("head"+idmap.get(m),
"head"+idmap.get(mm));
edge.setAttribute("ltail","cluster"+idmap.get(m));
edge.setAttribute("lhead","cluster"+idmap.get(mm));
}
}
File f = new File (SourceLocator.v().getOutputDir(),
name+DotGraph.DOT_EXTENSION);
dot.plot(f.getPath());
}
/**
* Dump the each summary computed by the interprocedural analysis as
* a seperate graph.
*
* @param prefix is prepended before method name in output filename
* @param drawUnanalysed do you also want info for the unanalysed methods
* required by the analysis via summaryOfUnanalysedMethod ?
*
* @see fillDotGraph
*/
public void drawAsManyDot(String prefix, boolean drawUnanalysed)
{
Iterator it = data.keySet().iterator();
while (it.hasNext()) {
SootMethod m = (SootMethod)it.next();
DotGraph dot = new DotGraph(m.toString());
dot.setGraphLabel(m.toString());
fillDotGraph("X", data.get(m), dot);
File f = new File (SourceLocator.v().getOutputDir(),
prefix+m.toString()+DotGraph.DOT_EXTENSION);
dot.plot(f.getPath());
}
if (drawUnanalysed) {
it = unanalysed.keySet().iterator();
while (it.hasNext()) {
SootMethod m = (SootMethod)it.next();
DotGraph dot = new DotGraph(m.toString());
dot.setGraphLabel(m.toString()+" (unanalysed)");
fillDotGraph("X", unanalysed.get(m), dot);
File f = new File (SourceLocator.v().getOutputDir(),
prefix+m.toString()+"_u"+
DotGraph.DOT_EXTENSION);
dot.plot(f.getPath());
}
}
}
/**
* Query the analysis result.
*/
public Object getSummaryFor(SootMethod m)
{
if (data.containsKey(m)) return data.get(m);
if (unanalysed.containsKey(m)) return unanalysed.get(m);
return newInitialSummary();
}
/**
* Get an iterator over the list of SootMethod with an associated summary.
* (Does not contain filtered-out or native methods.)
*/
public Iterator getAnalysedMethods()
{ return data.keySet().iterator(); }
/**
* Carry out the analysis.
*
* Call this from your InterproceduralAnalysis constructor,
* just after super(cg).
* Then , you will be able to call drawAsDot, for instance.
*/
protected void doAnalysis(boolean verbose)
{
// queue class
class IntComparator implements Comparator {
public int compare(Object o1, Object o2)
{
Integer v1 = order.get(o1);
Integer v2 = order.get(o2);
return v1.intValue()-v2.intValue();
}
};
SortedSet queue = new TreeSet(new IntComparator());
// init
Iterator it = order.keySet().iterator();
while (it.hasNext()) {
Object o = it.next();
data.put(o, newInitialSummary());
queue.add(o);
}
Map<SootMethod,Integer> nb = new HashMap<SootMethod,Integer>(); // only for debug pretty-printing
// fixpoint iterations
while (!queue.isEmpty()) {
SootMethod m = (SootMethod)queue.first();
queue.remove(m);
Object newSummary = newInitialSummary();
Object oldSummary = data.get(m);
if (nb.containsKey(m)) nb.put(m,new Integer(nb.get(m).intValue()+1));
else nb.put(m,new Integer(1));
if (verbose)
G.v().out.println(" |- processing "+m.toString()+" ("+nb.get(m)+"-st time)");
analyseMethod(m,newSummary);
if (!oldSummary.equals(newSummary)) {
// summary for m changed!
data.put(m,newSummary);
queue.addAll(dg.getPredsOf(m));
}
}
// fixpoint verification
if (doCheck) {
it = order.keySet().iterator();
while (it.hasNext()) {
SootMethod m = (SootMethod)it.next();
Object newSummary = newInitialSummary();
Object oldSummary = data.get(m);
analyseMethod(m,newSummary);
if (!oldSummary.equals(newSummary)) {
G.v().out.println("inter-procedural fixpoint not reached for method "+m.toString());
DotGraph gm = new DotGraph("false_fixpoint");
DotGraph gmm = new DotGraph("next_iterate");
gm.setGraphLabel("false fixpoint: "+m.toString());
gmm.setGraphLabel("fixpoint next iterate: "+m.toString());
fillDotGraph("", oldSummary, gm);
fillDotGraph("", newSummary, gmm);
gm.plot(m.toString()+"_false_fixpoint.dot");
gmm.plot(m.toString()+"_false_fixpoint_next.dot");
throw new Error("AbstractInterproceduralAnalysis sanity check failed!!!");
}
}
}
}
}