soot.jimple.toolkits.annotation.purity.PurityGraph Maven / Gradle / Ivy
package soot.jimple.toolkits.annotation.purity;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2005 Antoine Mine
* %%
* This program 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 program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import soot.Local;
import soot.RefLikeType;
import soot.SootMethod;
import soot.Value;
import soot.jimple.Stmt;
import soot.util.HashMultiMap;
import soot.util.MultiMap;
import soot.util.dot.DotGraph;
import soot.util.dot.DotGraphEdge;
import soot.util.dot.DotGraphNode;
/**
* Purity graphs are mutable structures that are updated in-place.
* You can safely hash graphs. Equality comparison means isomorphism
* (equal nodes, equal edges).
*/
/**
* Modifications with respect to the article:
*
* - "unanalizable call" are treated by first constructing a conservative calee graph where all parameters escape globally
* and return points to the global node, and then applying the standard analysable call construction
*
* - unanalysable calls add a mutation on the global node; the "field" is named "outside-world" and models the mutation of
* any static field, but also side-effects by native methods, such as I/O, that make methods impure (see below).
*
* - Whenever a method mutates the global node, it is marked as "impure" (this can be due to a side-effect or static field
* mutation), even if the global node is not rechable from parameter nodes through outside edges. It seems to me it was a
* defect from the article ? TODO: see if we must take the global node into account also when stating whether a parameter is
* read-only or safe.
*
* - "simplifyXXX" functions are experimental... they may be unsound, and thus, not used now.
*
*
*
*
* NOTE: A lot of precision degradation comes from sequences of the form this.field = y; z = this.field in initialisers: the
* second statement creates a load node because, as a parameter, this may have escaped and this.field may be externally
* modified in-between the two instructions. I am not sure this can actually happend in an initialiser... in a a function
* called directly and only by initialisers.
*
* For the moment, summary of unanalised methods are either pure, completely impure (modify args & side-effects) or partially
* impure (modify args but not the gloal node). We should really be able to specify more precisely which arguments are r/o or
* safe within this methods. E.g., the analysis java.lang.String: void getChars(int,int,char [],int) imprecisely finds that
* this is not safe (because of the internal call to System.arraycopy that, in general, may introduce aliases) => it pollutes
* many things (e.g., StringBuffer append(String), and thus, exception constructors, etc.)
*
*/
public class PurityGraph {
private static final Logger logger = LoggerFactory.getLogger(PurityGraph.class);
public static final boolean doCheck = false;
protected Set nodes; // all nodes
protected Set paramNodes; // only parameter & this nodes
protected MultiMap edges; // source node -> edges
protected MultiMap locals; // local -> nodes
protected Set ret; // return -> nodes
protected Set globEscape; // nodes escaping globally
protected MultiMap backEdges; // target node -> edges
protected MultiMap backLocals; // target node -> local node sources
protected MultiMap mutated; // node -> field such that (node,field) is mutated
/**
* Initially empty graph.
*/
PurityGraph() {
// nodes & paramNodes are added lazily
nodes = new HashSet();
paramNodes = new HashSet();
edges = new HashMultiMap();
locals = new HashMultiMap();
ret = new HashSet();
globEscape = new HashSet();
backEdges = new HashMultiMap();
backLocals = new HashMultiMap();
mutated = new HashMultiMap();
if (doCheck) {
sanityCheck();
}
}
/**
* Copy constructor.
*/
PurityGraph(PurityGraph x) {
nodes = new HashSet(x.nodes);
paramNodes = new HashSet(x.paramNodes);
edges = new HashMultiMap(x.edges);
locals = new HashMultiMap(x.locals);
ret = new HashSet(x.ret);
globEscape = new HashSet(x.globEscape);
backEdges = new HashMultiMap(x.backEdges);
backLocals = new HashMultiMap(x.backLocals);
mutated = new HashMultiMap(x.mutated);
if (doCheck) {
sanityCheck();
}
}
public int hashCode() {
return nodes.hashCode()
// + paramNodes.hashCode() // redundant info
+ edges.hashCode() + locals.hashCode() + ret.hashCode() + globEscape.hashCode()
// + backEdges.hashCode() // redundant info
// + backLocals.hashCode() // redundant info
+ mutated.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof PurityGraph)) {
return false;
}
PurityGraph g = (PurityGraph) o;
return nodes.equals(g.nodes)
// && paramNodes.equals(g.paramNodes) // redundant info
&& edges.equals(g.edges) && locals.equals(g.locals) && ret.equals(g.ret) && globEscape.equals(g.globEscape)
// && backEdges.equals(g.backEdges) // redundant info
// && backLocals.equals(g.backLocals) // redundant info
&& mutated.equals(g.mutated);
}
/**
* Caching: this semm to actually improve both speed and memory consumption!
*/
private static final Map nodeCache = new HashMap();
private static final Map edgeCache = new HashMap();
private static PurityNode cacheNode(PurityNode p) {
if (!nodeCache.containsKey(p)) {
nodeCache.put(p, p);
}
return nodeCache.get(p);
}
private static PurityEdge cacheEdge(PurityEdge e) {
if (!edgeCache.containsKey(e)) {
edgeCache.put(e, e);
}
return edgeCache.get(e);
}
/**
* Conservative constructor for unanalysable calls.
*
*
* Note: this gives a valid summary for all native methods, including Thread.start().
*
* @param withEffect
* add a mutated abstract field for the global node to account for side-effects in the environment (I/O, etc.).
*/
public static PurityGraph conservativeGraph(SootMethod m, boolean withEffect) {
PurityGraph g = new PurityGraph();
PurityNode glob = PurityGlobalNode.node;
g.nodes.add(glob);
// parameters & this escape globally
Iterator it = m.getParameterTypes().iterator();
int i = 0;
while (it.hasNext()) {
if (it.next() instanceof RefLikeType) {
PurityNode n = cacheNode(new PurityParamNode(i));
g.globEscape.add(n);
g.nodes.add(n);
g.paramNodes.add(n);
}
i++;
}
// return value escapes globally
if (m.getReturnType() instanceof RefLikeType) {
g.ret.add(glob);
}
// add a side-effect on the environment
// added by [AM]
if (withEffect) {
g.mutated.put(glob, "outside-world");
}
if (doCheck) {
g.sanityCheck();
}
return g;
}
/**
* Special constructor for "pure" methods returning a fresh object. (or simply pure if returns void or primitive).
*/
public static PurityGraph freshGraph(SootMethod m) {
PurityGraph g = new PurityGraph();
if (m.getReturnType() instanceof RefLikeType) {
PurityNode n = cacheNode(new PurityMethodNode(m));
g.ret.add(n);
g.nodes.add(n);
}
if (doCheck) {
g.sanityCheck();
}
return g;
}
/**
* Replace the current graph with its union with arg. arg is not modified.
*/
void union(PurityGraph arg) {
nodes.addAll(arg.nodes);
paramNodes.addAll(arg.paramNodes);
edges.putAll(arg.edges);
locals.putAll(arg.locals);
ret.addAll(arg.ret);
globEscape.addAll(arg.globEscape);
backEdges.putAll(arg.backEdges);
backLocals.putAll(arg.backLocals);
mutated.putAll(arg.mutated);
if (doCheck) {
sanityCheck();
}
}
/**
* Sanity check. Used internally for debugging!
*/
protected void sanityCheck() {
boolean err = false;
Iterator it = edges.keySet().iterator();
while (it.hasNext()) {
PurityNode src = (PurityNode) it.next();
Iterator itt = edges.get(src).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
if (!src.equals(e.getSource())) {
logger.debug("invalid edge source " + e + ", should be " + src);
err = true;
}
if (!nodes.contains(e.getSource())) {
logger.debug("nodes does not contain edge source " + e);
err = true;
}
if (!nodes.contains(e.getTarget())) {
logger.debug("nodes does not contain edge target " + e);
err = true;
}
if (!backEdges.get(e.getTarget()).contains(e)) {
logger.debug("backEdges does not contain edge " + e);
err = true;
}
if (!e.isInside() && !e.getTarget().isLoad()) {
logger.debug("target of outside edge is not a load node " + e);
err = true;
}
}
}
it = backEdges.keySet().iterator();
while (it.hasNext()) {
PurityNode dst = (PurityNode) it.next();
Iterator itt = backEdges.get(dst).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
if (!dst.equals(e.getTarget())) {
logger.debug("invalid backEdge dest " + e + ", should be " + dst);
err = true;
}
if (!edges.get(e.getSource()).contains(e)) {
logger.debug("backEdge not in edges " + e);
err = true;
}
}
}
it = nodes.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (n.isParam() && !paramNodes.contains(n)) {
logger.debug("paramNode not in paramNodes " + n);
err = true;
}
}
it = paramNodes.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!n.isParam()) {
logger.debug("paramNode contains a non-param node " + n);
err = true;
}
if (!nodes.contains(n)) {
logger.debug("paramNode not in nodes " + n);
err = true;
}
}
it = globEscape.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!nodes.contains(n)) {
logger.debug("globEscape not in nodes " + n);
err = true;
}
}
it = locals.keySet().iterator();
while (it.hasNext()) {
Local l = (Local) it.next();
Iterator itt = locals.get(l).iterator();
while (itt.hasNext()) {
PurityNode n = (PurityNode) itt.next();
if (!nodes.contains(n)) {
logger.debug("target of local node in nodes " + l + " / " + n);
err = true;
}
if (!backLocals.get(n).contains(l)) {
logger.debug("backLocals does contain local " + l + " / " + n);
err = true;
}
}
}
it = backLocals.keySet().iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
Iterator itt = backLocals.get(n).iterator();
while (itt.hasNext()) {
Local l = (Local) itt.next();
if (!nodes.contains(n)) {
logger.debug("backLocal node not in in nodes " + l + " / " + n);
err = true;
}
if (!locals.get(l).contains(n)) {
logger.debug("locals does contain backLocal " + l + " / " + n);
err = true;
}
}
}
it = ret.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!nodes.contains(n)) {
logger.debug("target of ret not in nodes " + n);
err = true;
}
}
it = mutated.keySet().iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!nodes.contains(n)) {
logger.debug("mutated node not in nodes " + n);
err = true;
}
}
if (err) {
dump();
DotGraph dot = new DotGraph("sanityCheckFailure");
fillDotGraph("chk", dot);
dot.plot("sanityCheckFailure.dot");
throw new Error("PurityGraph sanity check failed!!!");
}
}
////////////////////////
// ESCAPE INFORMATION //
////////////////////////
protected void internalPassEdges(Set toColor, Set dest, boolean consider_inside) {
Iterator it = toColor.iterator();
while (it.hasNext()) {
PurityEdge edge = (PurityEdge) it.next();
if (consider_inside || !edge.isInside()) {
PurityNode node = edge.getTarget();
if (!dest.contains(node)) {
dest.add(node);
internalPassEdges(edges.get(node), dest, consider_inside);
}
}
}
}
protected void internalPassNode(PurityNode node, Set dest, boolean consider_inside) {
if (!dest.contains(node)) {
dest.add(node);
internalPassEdges(edges.get(node), dest, consider_inside);
}
}
protected void internalPassNodes(Set toColor, Set dest, boolean consider_inside) {
Iterator it = toColor.iterator();
while (it.hasNext()) {
internalPassNode((PurityNode) it.next(), dest, consider_inside);
}
}
protected Set getEscaping() {
Set escaping = new HashSet();
internalPassNodes(ret, escaping, true);
internalPassNodes(globEscape, escaping, true);
internalPassNode(PurityGlobalNode.node, escaping, true);
internalPassNodes(paramNodes, escaping, true);
return escaping;
}
/**
* Call this on the merge of graphs at all return points of a method to know whether the method is pure.
*/
public boolean isPure() {
if (!mutated.get(PurityGlobalNode.node).isEmpty()) {
return false;
}
Set A = new HashSet();
Set B = new HashSet();
internalPassNodes(paramNodes, A, false);
internalPassNodes(globEscape, B, true);
internalPassNode(PurityGlobalNode.node, B, true);
Iterator it = A.iterator();
while (it.hasNext()) {
PurityNode n = it.next();
if (B.contains(n) || !mutated.get(n).isEmpty()) {
return false;
}
}
return true;
}
/**
* We use a less restrictive notion of purity for constructors: pure constructors can mutate fields of this.
*
* @see isPure
*/
public boolean isPureConstructor() {
if (!mutated.get(PurityGlobalNode.node).isEmpty()) {
return false;
}
Set A = new HashSet();
Set B = new HashSet();
internalPassNodes(paramNodes, A, false);
internalPassNodes(globEscape, B, true);
internalPassNode(PurityGlobalNode.node, B, true);
PurityNode th = PurityThisNode.node;
Iterator it = A.iterator();
while (it.hasNext()) {
PurityNode n = it.next();
if (B.contains(n) || (!n.equals(th) && !mutated.get(n).isEmpty())) {
return false;
}
}
return true;
}
/**
* A parameter (or this) can be: - read and write - read only - safe (read only & no externally visible alias is created)
*/
static final int PARAM_RW = 0;
static final int PARAM_RO = 1;
static final int PARAM_SAFE = 2;
protected int internalParamStatus(PurityNode p) {
if (!paramNodes.contains(p)) {
return PARAM_RW;
}
Set S1 = new HashSet();
internalPassNode(p, S1, false);
Iterator it = S1.iterator();
while (it.hasNext()) {
PurityNode n = it.next();
if (n.isLoad() || n.equals(p)) {
if (!mutated.get(n).isEmpty() || globEscape.contains(n)) {
return PARAM_RW;
}
}
}
Set S2 = new HashSet();
internalPassNodes(ret, S2, true);
internalPassNodes(paramNodes, S2, true);
it = S2.iterator();
while (it.hasNext()) {
Iterator itt = edges.get(it.next()).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
if (e.isInside() && S1.contains(e.getTarget())) {
return PARAM_RO;
}
}
}
return PARAM_SAFE;
}
/**
* Call this on the merge of graphs at all return points of a method to know whether an object passed as method parameter
* is read only (PARAM_RO), read write (PARAM_RW), or safe (PARAM_SAFE). Returns PARAM_RW for primitive-type parameters.
*/
public int paramStatus(int param) {
return internalParamStatus(cacheNode(new PurityParamNode(param)));
}
/**
* @see isParamReadOnly
*/
public int thisStatus() {
return internalParamStatus(PurityThisNode.node);
}
/////////////////////////
// GRAPH MANUPULATIONS //
/////////////////////////
public Object clone() {
return new PurityGraph(this);
}
// utility functions to update local / backLocals constitently
protected final boolean localsRemove(Local local) {
Iterator it = locals.get(local).iterator();
while (it.hasNext()) {
Object node = it.next();
backLocals.remove(node, local);
}
return locals.remove(local);
}
protected final boolean localsPut(Local local, PurityNode node) {
backLocals.put(node, local);
return locals.put(local, node);
}
protected final boolean localsPutAll(Local local, Set nodes) {
Iterator it = nodes.iterator();
while (it.hasNext()) {
Object node = it.next();
backLocals.put(node, local);
}
return locals.putAll(local, nodes);
}
/** Utility function to remove a node & all adjacent edges */
protected final void removeNode(PurityNode n) {
Iterator it = edges.get(n).iterator();
while (it.hasNext()) {
PurityEdge e = (PurityEdge) it.next();
backEdges.remove(e.getTarget(), e);
}
it = backEdges.get(n).iterator();
while (it.hasNext()) {
PurityEdge e = (PurityEdge) it.next();
edges.remove(e.getSource(), e);
}
it = backLocals.get(n).iterator();
while (it.hasNext()) {
Local l = (Local) it.next();
locals.remove(l, n);
}
ret.remove(n);
edges.remove(n);
backEdges.remove(n);
backLocals.remove(n);
nodes.remove(n);
paramNodes.remove(n);
globEscape.remove(n);
mutated.remove(n);
}
/** Utility function to merge node src into dst; src is removed */
protected final void mergeNodes(PurityNode src, PurityNode dst) {
Iterator it = (new LinkedList(edges.get(src))).iterator();
while (it.hasNext()) {
PurityEdge e = (PurityEdge) it.next();
PurityNode n = e.getTarget();
if (n.equals(src)) {
n = dst;
}
PurityEdge ee = cacheEdge(new PurityEdge(dst, e.getField(), n, e.isInside()));
edges.remove(src, e);
edges.put(dst, ee);
backEdges.remove(n, e);
backEdges.put(n, ee);
}
it = (new LinkedList(backEdges.get(src))).iterator();
while (it.hasNext()) {
PurityEdge e = (PurityEdge) it.next();
PurityNode n = e.getSource();
if (n.equals(src)) {
n = dst;
}
PurityEdge ee = cacheEdge(new PurityEdge(n, e.getField(), dst, e.isInside()));
edges.remove(n, e);
edges.put(n, ee);
backEdges.remove(src, e);
backEdges.put(dst, ee);
}
it = (new LinkedList(backLocals.get(src))).iterator();
while (it.hasNext()) {
Local l = (Local) it.next();
locals.remove(l, src);
backLocals.remove(src, l);
locals.put(l, dst);
backLocals.put(dst, l);
}
{
Set m = mutated.get(src);
mutated.remove(src);
mutated.putAll(dst, m);
}
if (ret.contains(src)) {
ret.remove(src);
ret.add(dst);
}
if (globEscape.contains(src)) {
globEscape.remove(src);
globEscape.add(dst);
}
nodes.remove(src);
nodes.add(dst);
paramNodes.remove(src);
if (dst.isParam()) {
paramNodes.add(dst);
}
}
/** Experimental simplification: merge redundant load nodes. */
void simplifyLoad() {
Iterator it = (new LinkedList(nodes)).iterator();
while (it.hasNext()) {
PurityNode p = (PurityNode) it.next();
Map fmap = new HashMap();
Iterator itt = (new LinkedList(edges.get(p))).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
PurityNode tgt = e.getTarget();
if (!e.isInside() && !tgt.equals(p)) {
String f = e.getField();
if (fmap.containsKey(f) && nodes.contains(fmap.get(f))) {
mergeNodes(tgt, fmap.get(f));
} else {
fmap.put(f, tgt);
}
}
}
}
if (doCheck) {
sanityCheck();
}
}
/**
* Experimental simplification: remove inside nodes not reachable from escaping nodes (params, ret, globEscape) or load
* nodes.
*/
void simplifyInside() {
Set r = new HashSet();
internalPassNodes(paramNodes, r, true);
internalPassNodes(ret, r, true);
internalPassNodes(globEscape, r, true);
internalPassNode(PurityGlobalNode.node, r, true);
Iterator it = nodes.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (n.isLoad()) {
internalPassNode(n, r, true);
}
}
it = (new LinkedList(nodes)).iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (n.isInside() && !r.contains(n)) {
removeNode(n);
}
}
if (doCheck) {
sanityCheck();
}
}
/**
* Remove all local bindings (except ret). This info is indeed superfluous on summary purity graphs representing the effect
* of a method. This saves a little memory, but also, simplify summary graph drawings a lot!
*
* DO NOT USE DURING INTRA-PROCEDURAL ANALYSIS!
*/
void removeLocals() {
locals = new HashMultiMap();
backLocals = new HashMultiMap();
}
/** Copy assignment left = right. */
void assignParamToLocal(int right, Local left) {
// strong update on local
PurityNode node = cacheNode(new PurityParamNode(right));
localsRemove(left);
localsPut(left, node);
nodes.add(node);
paramNodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/** Copy assignment left = this. */
void assignThisToLocal(Local left) {
// strong update on local
PurityNode node = PurityThisNode.node;
localsRemove(left);
localsPut(left, node);
nodes.add(node);
paramNodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/** Copy assignment left = right. */
void assignLocalToLocal(Local right, Local left) {
// strong update on local
localsRemove(left);
localsPutAll(left, locals.get(right));
if (doCheck) {
sanityCheck();
}
}
/** return right statement . */
void returnLocal(Local right) {
// strong update on ret
ret.clear();
ret.addAll(locals.get(right));
if (doCheck) {
sanityCheck();
}
}
/**
* Load non-static: left = right.field, or left = right[?] if field is [].
*/
void assignFieldToLocal(Stmt stmt, Local right, String field, Local left) {
Set esc = new HashSet();
Set escaping = getEscaping();
// strong update on local
localsRemove(left);
Iterator itRight = locals.get(right).iterator();
while (itRight.hasNext()) {
PurityNode nodeRight = (PurityNode) itRight.next();
Iterator itEdges = edges.get(nodeRight).iterator();
while (itEdges.hasNext()) {
PurityEdge edge = (PurityEdge) itEdges.next();
if (edge.isInside() && edge.getField().equals(field)) {
localsPut(left, edge.getTarget());
}
}
if (escaping.contains(nodeRight)) {
esc.add(nodeRight);
}
}
if (!esc.isEmpty()) {
// right can escape
// we add a label load node & outside edges
PurityNode loadNode = cacheNode(new PurityStmtNode(stmt, false));
nodes.add(loadNode);
Iterator itEsc = esc.iterator();
while (itEsc.hasNext()) {
PurityNode node = itEsc.next();
PurityEdge edge = cacheEdge(new PurityEdge(node, field, loadNode, false));
if (edges.put(node, edge)) {
backEdges.put(loadNode, edge);
}
}
localsPut(left, loadNode);
}
if (doCheck) {
sanityCheck();
}
}
/**
* Store non-static: left.field = right, or left[?] = right if field is [].
*/
void assignLocalToField(Local right, Local left, String field) {
// weak update on inside edges
Iterator itLeft = locals.get(left).iterator();
while (itLeft.hasNext()) {
PurityNode nodeLeft = (PurityNode) itLeft.next();
Iterator itRight = locals.get(right).iterator();
while (itRight.hasNext()) {
PurityNode nodeRight = (PurityNode) itRight.next();
PurityEdge edge = cacheEdge(new PurityEdge(nodeLeft, field, nodeRight, true));
if (edges.put(nodeLeft, edge)) {
backEdges.put(nodeRight, edge);
}
}
if (!nodeLeft.isInside()) {
mutated.put(nodeLeft, field);
}
}
if (doCheck) {
sanityCheck();
}
}
/** Allocation: left = new or left = new[?]. */
void assignNewToLocal(Stmt stmt, Local left) {
// strong update on local
// we add a label inside node
PurityNode node = cacheNode(new PurityStmtNode(stmt, true));
localsRemove(left);
localsPut(left, node);
nodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/** A local variable is used in an unknown construct. */
void localEscapes(Local l) {
// nodes escape globally
globEscape.addAll(locals.get(l));
if (doCheck) {
sanityCheck();
}
}
/** A local variable is assigned to some outside value. */
void localIsUnknown(Local l) {
// strong update on local
PurityNode node = PurityGlobalNode.node;
localsRemove(l);
localsPut(l, node);
nodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/**
* Store static: C.field = right.
*/
void assignLocalToStaticField(Local right, String field) {
PurityNode node = PurityGlobalNode.node;
localEscapes(right);
mutated.put(node, field);
nodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/**
* Store a primitive type into a non-static field left.field = v
*/
void mutateField(Local left, String field) {
Iterator it = locals.get(left).iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!n.isInside()) {
mutated.put(n, field);
}
}
if (doCheck) {
sanityCheck();
}
}
/**
* Store a primitive type into a static field left.field = v
*/
void mutateStaticField(String field) {
PurityNode node = PurityGlobalNode.node;
mutated.put(node, field);
nodes.add(node);
if (doCheck) {
sanityCheck();
}
}
/**
* Method call left = right.method(args).
*
* @param g
* is method's summary PurityGraph
* @param left
* can be null (no return value)
* @param right
* can be null (static call)
* @param args
* is a list of Value
*/
void methodCall(PurityGraph g, Local right, List args, Local left) {
MultiMap mu = new HashMultiMap();
// compute mapping relation g -> this
/////////////////////////////////////
Iterator it = args.iterator(); // (1) rule
int nb = 0;
while (it.hasNext()) {
Value arg = (Value) it.next();
if (arg instanceof Local && ((Local) arg).getType() instanceof RefLikeType) {
mu.putAll(cacheNode(new PurityParamNode(nb)), locals.get(arg));
}
nb++;
}
if (right != null) {
mu.putAll(PurityThisNode.node, locals.get(right));
}
// COULD BE OPTIMIZED!
// many times, we need to copy sets cause we mutate them within iterators
boolean hasChanged = true;
while (hasChanged) { // (2) & (3) rules fixpoint
hasChanged = false;
// (2)
it = (new LinkedList(mu.keySet())).iterator();
while (it.hasNext()) {
PurityNode n1 = (PurityNode) it.next();
Iterator it3 = (new LinkedList(mu.get(n1))).iterator();
while (it3.hasNext()) {
PurityNode n3 = (PurityNode) it3.next();
Iterator it12 = g.edges.get(n1).iterator();
while (it12.hasNext()) {
PurityEdge e12 = (PurityEdge) it12.next();
if (!e12.isInside()) {
Iterator it34 = edges.get(n3).iterator();
while (it34.hasNext()) {
PurityEdge e34 = (PurityEdge) it34.next();
if (e34.isInside() && e12.getField().equals(e34.getField())) {
if (mu.put(e12.getTarget(), e34.getTarget())) {
hasChanged = true;
}
}
}
}
}
}
}
// (3)
it = g.edges.keySet().iterator();
while (it.hasNext()) {
PurityNode n1 = (PurityNode) it.next();
Iterator it3 = g.edges.keySet().iterator();
while (it3.hasNext()) {
PurityNode n3 = (PurityNode) it3.next();
// ((mu(n1) U {n1}) inter (mu(n3) U {n3})) not empty
Set mu1 = new HashSet(mu.get(n1));
Set mu3 = new HashSet(mu.get(n3));
boolean cond = n1.equals(n3) || mu1.contains(n3) || mu3.contains(n1);
Iterator itt = mu1.iterator();
while (!cond && itt.hasNext()) {
cond = cond || mu3.contains(itt.next());
}
// add (mu(n4) U ({n4} inter PNodes)) to mu(n2)
if (cond && (!n1.equals(n3) || n1.isLoad())) {
Iterator it12 = g.edges.get(n1).iterator();
while (it12.hasNext()) {
PurityEdge e12 = (PurityEdge) it12.next();
if (!e12.isInside()) {
Iterator it34 = g.edges.get(n3).iterator();
while (it34.hasNext()) {
PurityEdge e34 = (PurityEdge) it34.next();
if (e34.isInside()) {
if (e12.getField().equals(e34.getField())) {
PurityNode n2 = e12.getTarget();
PurityNode n4 = e34.getTarget();
// add n4 (if not param node) to mu(n2)
if (!n4.isParam() && mu.put(n2, n4)) {
hasChanged = true;
}
// add mu(n4) to mu(n2)
if (mu.putAll(n2, mu.get(n4))) {
hasChanged = true;
}
}
}
}
}
}
}
}
}
}
// extend mu into mu'
it = g.nodes.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!n.isParam()) {
mu.put(n, n);
nodes.add(n);
}
}
// combine g into this
//////////////////////
// project edges
it = g.edges.keySet().iterator();
while (it.hasNext()) {
PurityNode n1 = (PurityNode) it.next();
Iterator it12 = g.edges.get(n1).iterator();
while (it12.hasNext()) {
PurityEdge e12 = (PurityEdge) it12.next();
String f = e12.getField();
PurityNode n2 = e12.getTarget();
Iterator itm1 = mu.get(n1).iterator();
while (itm1.hasNext()) {
PurityNode mu1 = (PurityNode) itm1.next();
if (e12.isInside()) {
Iterator itm2 = mu.get(n2).iterator();
while (itm2.hasNext()) {
PurityNode mu2 = (PurityNode) itm2.next();
PurityEdge edge = cacheEdge(new PurityEdge(mu1, f, mu2, true));
edges.put(mu1, edge);
backEdges.put(mu2, edge);
}
} else {
PurityEdge edge = cacheEdge(new PurityEdge(mu1, f, n2, false));
edges.put(mu1, edge);
backEdges.put(n2, edge);
}
}
}
}
// return value
if (left != null) {
// strong update on locals
localsRemove(left);
it = g.ret.iterator();
while (it.hasNext()) {
localsPutAll(left, mu.get(it.next()));
}
}
// global escape
it = g.globEscape.iterator();
while (it.hasNext()) {
globEscape.addAll(mu.get(it.next()));
}
if (doCheck) {
sanityCheck();
}
// simplification
/////////////////
Set escaping = getEscaping();
it = (new LinkedList(nodes)).iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (!escaping.contains(n)) {
if (n.isLoad()) {
// remove captured load nodes
removeNode(n);
} else {
// ... and outside edges from captured nodes
Iterator itt = (new LinkedList(edges.get(n))).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
if (!e.isInside()) {
edges.remove(n, e);
backEdges.remove(e.getTarget(), e);
}
}
}
}
}
// update mutated
/////////////////
it = g.mutated.keySet().iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
Iterator itt = mu.get(n).iterator();
while (itt.hasNext()) {
PurityNode nn = (PurityNode) itt.next();
if (nodes.contains(nn) && !nn.isInside()) {
Iterator ittt = g.mutated.get(n).iterator();
while (ittt.hasNext()) {
String f = (String) ittt.next();
mutated.put(nn, f);
}
}
}
}
if (doCheck) {
sanityCheck();
}
}
/////////////
// DRAWING //
/////////////
/**
* Fills a dot graph or subgraph with the graphical representation of the purity graph.
*
* @param prefix
* is used to prefix all dot node and edge names. Use it to avoid collision when several subgraphs are laid in the
* same dot file!
*
* @param out
* is a newly created dot graph or subgraph where to put the result.
*
*
* Note: outside edges, param and load nodes are gray dashed, while inside edges and nodes are solid black.
* Globally escaping nodes have a red label.
*/
void fillDotGraph(String prefix, DotGraph out) {
Map nodeId = new HashMap();
int id = 0;
// add nodes
Iterator it = nodes.iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
String label = "N" + prefix + "_" + id;
DotGraphNode node = out.drawNode(label);
node.setLabel(n.toString());
if (!n.isInside()) {
node.setStyle("dashed");
node.setAttribute("color", "gray50");
}
if (globEscape.contains(n)) {
node.setAttribute("fontcolor", "red");
}
nodeId.put(n, label);
id++;
}
// add edges
it = edges.keySet().iterator();
while (it.hasNext()) {
PurityNode src = (PurityNode) it.next();
Iterator itt = edges.get(src).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
DotGraphEdge edge = out.drawEdge(nodeId.get(e.getSource()), nodeId.get(e.getTarget()));
edge.setLabel(e.getField());
if (!e.isInside()) {
edge.setStyle("dashed");
edge.setAttribute("color", "gray50");
edge.setAttribute("fontcolor", "gray40");
}
}
}
// add locals
it = locals.keySet().iterator();
while (it.hasNext()) {
Local local = (Local) it.next();
if (!locals.get(local).isEmpty()) {
String label = "L" + prefix + "_" + id;
DotGraphNode node = out.drawNode(label);
node.setLabel(local.toString());
node.setShape("plaintext");
Iterator itt = locals.get(local).iterator();
while (itt.hasNext()) {
PurityNode dst = (PurityNode) itt.next();
out.drawEdge(label, nodeId.get(dst));
}
id++;
}
}
// ret
if (!ret.isEmpty()) {
DotGraphNode node = out.drawNode("ret_" + prefix);
node.setLabel("ret");
node.setShape("plaintext");
Iterator itt = ret.iterator();
while (itt.hasNext()) {
PurityNode dst = (PurityNode) itt.next();
out.drawEdge("ret_" + prefix, nodeId.get(dst));
}
}
// add mutated
it = mutated.keySet().iterator();
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
Iterator itt = mutated.get(n).iterator();
while (itt.hasNext()) {
String f = (String) itt.next();
String label = "M" + prefix + "_" + id;
DotGraphNode node = out.drawNode(label);
node.setLabel("");
node.setShape("plaintext");
DotGraphEdge edge = out.drawEdge(nodeId.get(n), label);
edge.setLabel(f);
id++;
}
}
}
/** Debugging... */
static private void dumpSet(String name, Set s) {
logger.debug("" + name);
Iterator it = s.iterator();
while (it.hasNext()) {
logger.debug(" " + it.next().toString());
}
}
static private void dumpMultiMap(String name, MultiMap s) {
logger.debug("" + name);
Iterator it = s.keySet().iterator();
while (it.hasNext()) {
Object o = it.next();
logger.debug(" " + o.toString());
Iterator itt = s.get(o).iterator();
while (itt.hasNext()) {
logger.debug(" " + itt.next().toString());
}
}
}
void dump() {
dumpSet("nodes Set:", nodes);
dumpSet("paramNodes Set:", paramNodes);
dumpMultiMap("edges MultiMap:", edges);
dumpMultiMap("locals MultiMap:", locals);
dumpSet("ret Set:", ret);
dumpSet("globEscape Set:", globEscape);
dumpMultiMap("backEdges MultiMap:", backEdges);
dumpMultiMap("backLocals MultiMap:", backLocals);
dumpMultiMap("mutated MultiMap:", mutated);
logger.debug("");
}
/** Simple statistics on maximal graph sizes. */
static private int maxInsideNodes = 0;
static private int maxLoadNodes = 0;
static private int maxInsideEdges = 0;
static private int maxOutsideEdges = 0;
static private int maxMutated = 0;
void dumpStat() {
logger.debug("Stat: " + maxInsideNodes + " inNodes, " + maxLoadNodes + " loadNodes, " + maxInsideEdges + " inEdges, "
+ maxOutsideEdges + " outEdges, " + maxMutated + " mutated.");
}
void updateStat() {
Iterator it = nodes.iterator();
int insideNodes = 0;
int loadNodes = 0;
while (it.hasNext()) {
PurityNode n = (PurityNode) it.next();
if (n.isInside()) {
insideNodes++;
} else if (n.isLoad()) {
loadNodes++;
}
}
int insideEdges = 0;
int outsideEdges = 0;
it = edges.keySet().iterator();
while (it.hasNext()) {
Iterator itt = edges.get(it.next()).iterator();
while (itt.hasNext()) {
PurityEdge e = (PurityEdge) itt.next();
if (e.isInside()) {
insideEdges++;
} else {
outsideEdges++;
}
}
}
int mutatedFields = 0;
it = mutated.keySet().iterator();
while (it.hasNext()) {
mutatedFields += mutated.get(it.next()).size();
}
boolean changed = false;
if (insideNodes > maxInsideNodes) {
maxInsideNodes = insideNodes;
changed = true;
}
if (loadNodes > maxLoadNodes) {
maxLoadNodes = loadNodes;
changed = true;
}
if (insideEdges > maxInsideEdges) {
maxInsideEdges = insideEdges;
changed = true;
}
if (outsideEdges > maxOutsideEdges) {
maxOutsideEdges = outsideEdges;
changed = true;
}
if (mutatedFields > maxMutated) {
maxMutated = mutatedFields;
changed = true;
}
if (changed) {
dumpStat();
}
}
}