/** * */ 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); } }