/**
*
*/
package soottocfg.cfg.method;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.BellmanFordShortestPath;
import org.jgrapht.ext.DOTExporter;
import org.jgrapht.ext.StringNameProvider;
import org.jgrapht.graph.AbstractBaseGraph;
import org.jgrapht.graph.ClassBasedEdgeFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import soot.RefType;
import soot.SootMethod;
import soottocfg.cfg.LiveVars;
import soottocfg.cfg.Node;
import soottocfg.cfg.Program;
import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.Variable;
import soottocfg.soot.transformers.ArrayTransformer;
import soottocfg.soot.util.SootTranslationHelpers;
import soottocfg.util.SetOperations;
/**
* @author schaef extends DefaultDirectedGraph<Statement, DefaultEdge>
*/
public class Method extends AbstractBaseGraph<CfgBlock, CfgEdge> implements Node, DirectedGraph<CfgBlock, CfgEdge> {
/**
*
*/
private static final long serialVersionUID = 3367382274895641548L;
private final transient SourceLocation location; // TODO remove the
// transient
private final String methodName;
private final List<Type> returnTypes;
private Variable thisVariable;
private List<Variable> returnVariables;
private final List<Variable> parameterList;
private Set<Variable> locals;
private CfgBlock source, sink;
private boolean isProgramEntry = false;
private transient BellmanFordShortestPath<CfgBlock, CfgEdge> sourceBF;
private transient Map<CfgBlock, BellmanFordShortestPath<CfgBlock, CfgEdge>> sinkBF;
public static Method createMethodInProgram(Program p, String uniqueName, List<Variable> params, List<Type> outTypes,
SourceLocation sourceLocation) {
Preconditions.checkArgument(p.lookupMethod(uniqueName) == null,
"Method with name " + uniqueName + " already exists");
// add the exceptional return type to all methods that are generated.
List<Type> returnTypes = new LinkedList<Type>();
RefType reftype = RefType.v("java.lang.Throwable");
returnTypes.add(SootTranslationHelpers.v().getMemoryModel().lookupType(reftype));
returnTypes.addAll(outTypes);
Method m = new Method(sourceLocation, uniqueName, params, returnTypes);
p.addMethod(m);
return m;
}
/**
* @return returns the this-variable if there is one and null otherwise.
*/
public Variable getThisVariable() {
return this.thisVariable;
}
/**
* ONLY USE THIS METHOD DURING TESTING
* @param p
* @param uniqueName
* @param params
* @param outTypes
* @param sourceLocation
* @return
*/
public static Method createMethodForTestingOnly(Program p, String uniqueName, List<Variable> params, List<Type> outTypes,
SourceLocation sourceLocation) {
Preconditions.checkArgument(p.lookupMethod(uniqueName) == null,
"Method with name " + uniqueName + " already exists");
// add the exceptional return type to all methods that are generated.
List<Type> returnTypes = new LinkedList<Type>();
returnTypes.addAll(outTypes);
Method m = new Method(sourceLocation, uniqueName, params, returnTypes);
p.addMethod(m);
return m;
}
private Method(SourceLocation loc, String uniqueName, List<Variable> params, List<Type> outTypes) {
super(new ClassBasedEdgeFactory<CfgBlock, CfgEdge>(CfgEdge.class), true, true);
location = loc;
methodName = uniqueName;
returnTypes = outTypes;
locals = new LinkedHashSet<Variable>();
this.parameterList = new LinkedList<Variable>(params);
}
public Method createMethodFromSubgraph(DirectedGraph<CfgBlock, CfgEdge> subgraph, String newMethodName) {
Preconditions.checkArgument(vertexSet().containsAll(subgraph.vertexSet()),
"Method does not contain all nodes from subgraph.");
Method subgraphMethod = new Method(location, newMethodName, this.parameterList, this.returnTypes);
for (CfgBlock v : subgraph.vertexSet()) {
subgraphMethod.addVertex(v);
}
for (CfgEdge e : subgraph.edgeSet()) {
subgraphMethod.addEdge(subgraph.getEdgeSource(e), subgraph.getEdgeTarget(e));
}
subgraphMethod.initialize(thisVariable, returnVariables, locals, source, isProgramEntry);
return subgraphMethod;
}
public String getMethodName() {
return this.methodName;
}
public SourceLocation getLocation() {
return location;
}
public boolean isConstructor() {
return this.methodName.contains(SootMethod.constructorName);
}
public boolean isStaticInitializer() {
return this.methodName.contains(SootMethod.staticInitializerName);
}
@Override
public boolean removeVertex(CfgBlock v) {
if (this.source == v) {
this.source = null;
}
if (this.sink == v) {
this.sink = null;
}
return super.removeVertex(v);
}
public void initialize(Variable thisVariable, List<Variable> returnVariables, Collection<Variable> locals,
CfgBlock source, boolean isEntryPoint) {
Preconditions.checkNotNull(parameterList, "Parameter list must not be null");
Preconditions.checkNotNull(source);
if (returnVariables != null) {
Verify.verify(returnVariables.size() == this.returnTypes.size());
}
this.thisVariable = thisVariable;
this.returnVariables = returnVariables;
this.locals = new LinkedHashSet<Variable>(locals);
this.source = source;
this.isProgramEntry = isEntryPoint;
SourceLocation loc = null; // TODO
// return var 0 is the exceptional return variable.
// first statement has to assign the exception local to null
if (this.returnVariables != null) {
this.source.addStatement(0,
new AssignStatement(loc, new IdentifierExpression(loc, this.returnVariables.get(0)),
SootTranslationHelpers.v().getMemoryModel().mkNullConstant()));
} else {
Verify.verify(false, "didn't expect that.");
}
if (this.thisVariable != null) {
if (!this.locals.contains(this.thisVariable)) {
this.locals.add(this.thisVariable);
}
// then the first parameter must be the reference to the current
// instance
// and we have to add an assignment to the source block.
Verify.verify(!this.parameterList.isEmpty());
Verify.verifyNotNull(this.source);
AssignStatement thisAssign = new AssignStatement(loc, new IdentifierExpression(loc, this.thisVariable),
new IdentifierExpression(loc, this.parameterList.get(0)));
this.source.addStatement(0, thisAssign);
}
}
/**
* Adds a guard expression as label to an edge. The label must not be null
*
* @param edge
* Existing edge in this Method.
* @param label
* Non-null guard expression.
*/
public void setEdgeLabel(CfgEdge edge, Expression label) {
edge.setLabel(label);
}
public boolean isProgramEntryPoint() {
return this.isProgramEntry;
}
public void isProgramEntryPoint(boolean b) {
this.isProgramEntry = b;
}
public void setSource(CfgBlock b) {
Preconditions.checkArgument(this.vertexSet().contains(b));
Preconditions.checkArgument(this.inDegreeOf(b) == 0);
this.source = b;
}
public void setSink(CfgBlock b) {
Preconditions.checkArgument(this.vertexSet().contains(b));
Preconditions.checkArgument(this.outDegreeOf(b) == 0);
this.sink = b;
}
public CfgBlock getSource() {
if (source == null) {
for (CfgBlock b : vertexSet()) {
if (inDegreeOf(b) == 0) {
Verify.verify(source == null, "More than one source in graph!");
source = b;
}
}
}
// Verify.verify(source != null, "No source in graph!");
return source;
}
public CfgBlock getSink() {
if (sink == null) {
for (CfgBlock b : vertexSet()) {
if (outDegreeOf(b) == 0) {
Verify.verify(sink == null, "More than one sink in graph for "+this.methodName);
sink = b;
}
}
}
// Verify.verify(sink != null, "No sink in graph!");
return sink;
}
/**
* Checks if the graph has a unique sink vertex and returns it. If more than
* one such vertex exists, it collects all sink vertices and connects them
* to a new unique sink.
*
* @return The unique sink vertex of the graph.
*/
public CfgBlock findOrCreateUniqueSink() {
if (sink == null) {
Set<CfgBlock> currentSinks = new HashSet<CfgBlock>();
for (CfgBlock b : this.vertexSet()) {
if (this.outDegreeOf(b) == 0) {
currentSinks.add(b);
}
}
if (currentSinks.isEmpty()) {
sink = null;
} else if (currentSinks.size() == 1) {
sink = currentSinks.iterator().next();
} else {
CfgBlock newSink = new CfgBlock(this);
for (CfgBlock b : currentSinks) {
this.addEdge(b, newSink);
}
sink = newSink;
}
}
return sink;
}
public Set<CfgBlock> getExitBlocks() {
Set<CfgBlock> ret = new HashSet<CfgBlock>();
for (CfgBlock b : this.vertexSet()) {
if (this.outDegreeOf(b) == 0) {
ret.add(b);
}
}
return ret;
}
/**
* Returns the in parameter at position {@literal pos}. Throws
* an exception it {@literal pos} is not a legal index.
*
* @param pos
* @return The parameter at position pos.
* @throws IndexOutOfBoundsException
*/
public Variable getInParam(int pos) {
return this.parameterList.get(pos);
}
/**
* Returns list of parameters.
*
* @return list of parameters.
*/
public List<Variable> getInParams() {
return this.parameterList;
}
/**
* Returns an {@link Optional} Variable of the return variables of the
* current Method.
*
* @return Optional return variable.
*/
public List<Variable> getOutParams() {
if (this.returnVariables == null) {
return new LinkedList<Variable>();
}
return this.returnVariables;
}
public void setOutParam(List<Variable> returnVariables) {
// TODO verify that the types actually match.
Verify.verify(returnVariables.size() == this.returnTypes.size(), "Wrong number of vars.");
this.returnVariables = returnVariables;
}
/**
* Returns an {@link Optional} return type of the method.
* I.e., either a type or None if the method returns void
*
* @return Optional return type.
*/
public List<Type> getReturnType() {
return this.returnTypes;
}
/**
* TODO:
* Add a local variable. This method should not be used. Its only
* use is in the SSA computation to create a non-deterministic assignment.
* Hopefully we find a better way todo this.
*
* @param local
*/
public void addLocalVariable(Variable local) {
Verify.verify(!locals.contains(local));
locals.add(local);
}
/**
* Returns the set of local variables.
*
* @return set of local variables.
*/
public Collection<Variable> getLocals() {
return locals;
}
public void toDot(File dotFile) {
try (FileOutputStream fileStream = new FileOutputStream(dotFile);
OutputStreamWriter writer = new OutputStreamWriter(fileStream, "UTF-8");) {
DOTExporter<CfgBlock, CfgEdge> dot = new DOTExporter<CfgBlock, CfgEdge>(new StringNameProvider<CfgBlock>(),
null, null);
dot.export(writer, this);
} catch (IOException e) {
e.printStackTrace();
}
}
public void toSimpleDot(File dotFile) {
try (FileOutputStream fileStream = new FileOutputStream(dotFile);
OutputStreamWriter writer = new OutputStreamWriter(fileStream, "UTF-8");) {
DOTExporter<CfgBlock, CfgEdge> dot = new DOTExporter<CfgBlock, CfgEdge>(new StringNameProvider<CfgBlock>() {
@Override
public String getVertexName(CfgBlock vertex) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
sb.append(vertex.getLabel());
sb.append("\"");
return sb.toString();
}
}, null, null);
dot.export(writer, this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Method ");
sb.append(this.methodName);
String comma = "";
sb.append("(");
for (Variable v : this.parameterList) {
sb.append(comma);
sb.append(v.getName());
comma = ", ";
}
sb.append(")\n");
if (this.returnTypes.size() > 0) {
sb.append(" Return types: ");
comma = "";
for (Type t : this.returnTypes) {
sb.append(comma);
sb.append(t.toString());
comma = ", ";
}
sb.append("\n");
}
if (this.returnVariables != null) {
sb.append(" Returns: ");
comma = "";
for (Variable v : this.returnVariables) {
sb.append(comma);
sb.append(v.toString());
comma = ", ";
}
sb.append("\n");
sb.append("\n");
}
comma = "";
if (this.locals != null && !this.locals.isEmpty()) {
sb.append(" locals:\n");
for (Variable v : this.locals) {
sb.append(" ");
sb.append(v.getName());
sb.append(": ");
sb.append(v.getType());
sb.append("\n");
}
}
for (CfgBlock b : this.vertexSet()) {
if (this.source == b) {
sb.append("Root ->");
}
sb.append(b);
}
return sb.toString();
}
@Override
public Set<Variable> getUseVariables() {
Set<Variable> used = new HashSet<Variable>();
for (CfgBlock b : this.vertexSet()) {
used.addAll(b.getUseVariables());
}
return used;
}
@Override
public Set<Variable> getDefVariables() {
Set<Variable> rval = new HashSet<Variable>();
for (CfgBlock b : this.vertexSet()) {
rval.addAll(b.getDefVariables());
}
return rval;
}
/**
* Return the set of live variable at the entry of each block. A variable is
* live between its first and last use. Following the algorithm on p610 of
* the dragon book, 2nd ed.
*
* @return
*/
public LiveVars<CfgBlock> computeBlockLiveVariables() {
Set<CfgBlock> cfg = this.vertexSet();
// Reserve the necessary size in the hashmap
Map<CfgBlock, Set<Variable>> in = new HashMap<CfgBlock, Set<Variable>>(cfg.size());
Map<CfgBlock, Set<Variable>> out = new HashMap<CfgBlock, Set<Variable>>(cfg.size());
// cache these to save time
Map<CfgBlock, Set<Variable>> use = new HashMap<CfgBlock, Set<Variable>>(cfg.size());
Map<CfgBlock, Set<Variable>> def = new HashMap<CfgBlock, Set<Variable>>(cfg.size());
// Start by initializing in to empty. The book does this separately for
// exit and non exit blocks, but that's not necessary
// TODO can exit blocks have variables? E.g. can they return values? In
// which case we should actually recurse over all blocks!
for (CfgBlock b : cfg) {
in.put(b, new HashSet<Variable>());
use.put(b, b.getUseVariables());
def.put(b, b.getDefVariables());
}
boolean changed = false;
do {
changed = false;
for (CfgBlock b : cfg) {
out.put(b, b.computeLiveOut(in));
Set<Variable> newIn = SetOperations.union(use.get(b), SetOperations.minus(out.get(b), def.get(b)));
if (!newIn.equals(in.get(b))) {
changed = true;
in.put(b, newIn);
}
}
} while (changed);
return new LiveVars<CfgBlock>(in, out);
}
public int distanceToSource(CfgBlock b) {
if (sourceBF == null)
sourceBF = new BellmanFordShortestPath<CfgBlock, CfgEdge>(this, getSource());
if (b.equals(getSource()))
return 0;
else
return (int) sourceBF.getCost(b);
}
public int distanceToSink(CfgBlock b) {
if (b.equals(getSink()))
return 0;
if (sinkBF == null)
sinkBF = new HashMap<CfgBlock, BellmanFordShortestPath<CfgBlock, CfgEdge>>();
BellmanFordShortestPath<CfgBlock, CfgEdge> toSink = sinkBF.get(b);
if (toSink == null) {
toSink = new BellmanFordShortestPath<CfgBlock, CfgEdge>(this, b);
sinkBF.put(b, toSink);
}
return (int) toSink.getCost(getSink());
}
/**
* TODO need better way to check all this...
*/
public boolean isArraySet() {
return thisVariable != null &&
thisVariable.getType().toString().contains(ArrayTransformer.arrayTypeName) &&
this.methodName.contains(ArrayTransformer.arraySetName);
}
public boolean isArrayGet() {
return thisVariable != null &&
thisVariable.getType().toString().contains(ArrayTransformer.arrayTypeName) &&
this.methodName.contains(ArrayTransformer.arrayGetName);
}
public boolean isArrayConstructor() {
return thisVariable != null &&
thisVariable.getType().toString().contains(ArrayTransformer.arrayTypeName) &&
this.isConstructor();
}
public boolean isArrayMethod() {
return thisVariable != null &&
thisVariable.getType().toString().contains(ArrayTransformer.arrayTypeName);
}
}