/**
*
*/
package soottocfg.soot.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import com.google.common.base.Preconditions;
import soot.Body;
import soot.Hierarchy;
import soot.Local;
import soot.Scene;
import soot.SootClass;
import soot.Trap;
import soot.Unit;
import soot.Value;
import soot.jimple.BinopExpr;
import soot.jimple.DefinitionStmt;
import soot.jimple.GotoStmt;
import soot.jimple.IdentityStmt;
import soot.jimple.IfStmt;
import soot.jimple.InstanceFieldRef;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.SwitchStmt;
import soot.jimple.ThrowStmt;
import soot.jimple.UnopExpr;
import soot.toolkits.graph.CompleteUnitGraph;
import soot.toolkits.graph.UnitGraph;
/**
* @author schaef TODO: this class is only a test and should be moved to an
* appropriate location once it works.
*/
public class DuplicatedCatchDetection {
/**
* Java finally blocks get inlined in bytecode. That is, one Java statement
* can correspond to multiple bytecode instructions that are almost
* identical but the Locals may be different. These duplications can confuse
* inconsistent code detection if one instance is inconsistent but another
* one is not. This procedure tries to identify for each statement in a
* finally block the set of statements it corresponds to.
*
* @param body
* @return
*/
public Map<Unit, Set<Unit>> identifiedDuplicatedUnitsFromFinallyBlocks(Body body) {
Hierarchy hierarchy = Scene.v().getActiveHierarchy();
SootClass throwableClass = Scene.v().getSootClass("java.lang.Throwable");
Map<Trap, List<Unit>> catchBlocks = new HashMap<Trap, List<Unit>>();
// first collect all monitor traps.
Set<Unit> usedHandlers = new HashSet<Unit>();
for (Trap t : body.getTraps()) {
if (t.getException() == throwableClass) {
if (!usedHandlers.contains(t.getHandlerUnit())) {
// collect the catch block of this trap
List<Unit> catchUnits = collectCatchBlock(body, t.getHandlerUnit());
// if its a proper catch block
if (tryRemoveCatchBlockBoilderplate(catchUnits)) {
// do not add trivial catch blocks.
if (!catchUnits.isEmpty()) {
catchBlocks.put(t, catchUnits);
usedHandlers.add(t.getHandlerUnit());
}
}
}
} else {
Preconditions.checkArgument(hierarchy.isClassSubclassOfIncluding(t.getException(), throwableClass),
"Unexpected type " + t.getException().getJavaStyleName());
}
}
Map<Unit, Set<Unit>> duplicatedUnits = new HashMap<Unit, Set<Unit>>();
if (!catchBlocks.isEmpty()) {
UnitGraph graph = new CompleteUnitGraph(body);
Iterator<Unit> iterator = graph.iterator();
while (iterator.hasNext()) {
Unit u = iterator.next();
for (Entry<Trap, List<Unit>> entry : catchBlocks.entrySet()) {
List<Unit> finallyCopy = new LinkedList<Unit>(entry.getValue());
Map<Unit, Unit> dupl = findDuplicates(graph, u, finallyCopy);
if (dupl.size() > 0) {
// StringBuilder sb = new StringBuilder();
for (Entry<Unit, Unit> en : dupl.entrySet()) {
if (!duplicatedUnits.containsKey(en.getKey())) {
duplicatedUnits.put(en.getKey(), new HashSet<Unit>());
}
duplicatedUnits.get(en.getKey()).add(en.getValue());
// sb.append(en.getKey() +
// "\t"+en.getValue());
// sb.append("\n");
}
// sb.append("***\n");
// System.err.println(sb.toString());
}
}
}
}
return duplicatedUnits;
}
/**
* DFS through the graph to find the largest copy of the finally block.
*
* @param g
* @param u
* @param finallyBlock
* @return
*/
private Map<Unit, Unit> findDuplicates(UnitGraph g, Unit u, List<Unit> finallyBlock) {
Map<Unit, Unit> duplicates = new LinkedHashMap<Unit, Unit>();
if (!finallyBlock.isEmpty() && isDuplicateButNotSame(u, finallyBlock.get(0))) {
duplicates.put(finallyBlock.get(0), u);
finallyBlock.remove(0);
for (Unit s : g.getSuccsOf(u)) {
if (finallyBlock.isEmpty()) {
// we're done
break;
}
if (isDuplicateButNotSame(s, finallyBlock.get(0))) {
/*
* TODO: For try-with-resources (i.e., try (bla = new blub)
* { ... }) there are duplicated CaughtExceptionRefs with
* non-identical line numbers. I believe this is ok, but
* further testing may be needed.
*/
// if
// (s.getJavaSourceStartLineNumber()!=finallyBlock.get(0).getJavaSourceStartLineNumber())
// {
// int la = s.getJavaSourceStartLineNumber();
// int lb =
// finallyBlock.get(0).getJavaSourceStartLineNumber();
// System.err.println("line a "+ la + ", line b " +lb);
// System.err.println("a "+ s + ", b "
// +finallyBlock.get(0));
// }
duplicates.putAll(findDuplicates(g, s, finallyBlock));
}
}
}
return duplicates;
}
/**
* Returns true if a does the same as b but returns false if a==b.
*
* @param a
* @param b
* @return
*/
private boolean isDuplicateButNotSame(Unit a, Unit b) {
if (a == b) {
// make sure that we do not mark a unit as its own duplicate.
return false;
}
// NOTE: DO NOT USE getJavaSourceStartLineNumber because it is
// non-deterministic
// if
// (a.getJavaSourceStartLineNumber()!=b.getJavaSourceStartLineNumber())
// {
// return false;
// }
if (a instanceof DefinitionStmt && b instanceof DefinitionStmt) {
return shallowCompareDefinitionStatements((DefinitionStmt) a, (DefinitionStmt) b);
} else if (a instanceof InvokeStmt && b instanceof InvokeStmt) {
return shallowCompareValue(((InvokeStmt) a).getInvokeExpr(), ((InvokeStmt) b).getInvokeExpr());
} else if (a instanceof IfStmt && b instanceof IfStmt) {
IfStmt ia = (IfStmt) a;
IfStmt ib = (IfStmt) b;
// TODO: also consider the case that one condition might include
// a double negation.
return shallowCompareValue(ia.getCondition(), ib.getCondition());
} else if (a instanceof IfStmt && b instanceof IfStmt) {
} else if (a instanceof SwitchStmt && b instanceof SwitchStmt) {
SwitchStmt sa = (SwitchStmt) a;
SwitchStmt sb = (SwitchStmt) b;
return shallowCompareValue(sa.getKey(), sb.getKey());
}
// we do not care if goto, return, monitor, throw , or noop statements
// are clones of other statemens.
return false;
}
private boolean shallowCompareDefinitionStatements(DefinitionStmt a, DefinitionStmt b) {
return shallowCompareValue(a.getLeftOp(), b.getLeftOp()) && shallowCompareValue(a.getRightOp(), b.getRightOp());
}
private boolean shallowCompareValue(Value a, Value b) {
if (a instanceof Local && b instanceof Local) {
return a.getType().equals(b.getType());
} else if (a instanceof InstanceFieldRef && b instanceof InstanceFieldRef) {
InstanceFieldRef ia = (InstanceFieldRef) a;
InstanceFieldRef ib = (InstanceFieldRef) b;
if (ia.getField() == ib.getField()) {
return shallowCompareValue(ia.getBase(), ib.getBase());
}
return false;
} else if (a instanceof InvokeExpr && b instanceof InvokeExpr) {
InvokeExpr ia = (InvokeExpr) a;
InvokeExpr ib = (InvokeExpr) b;
if (ia.getMethod() == ib.getMethod()) {
for (int i = 0; i < ia.getArgCount(); i++) {
if (!shallowCompareValue(ia.getArg(i), ib.getArg(i))) {
return false;
}
}
} else {
return false;
}
if (a instanceof InstanceInvokeExpr && b instanceof InstanceInvokeExpr) {
InstanceInvokeExpr iia = (InstanceInvokeExpr) a;
InstanceInvokeExpr iib = (InstanceInvokeExpr) b;
return shallowCompareValue(iia.getBase(), iib.getBase());
} else {
return true;
}
} else if (a instanceof UnopExpr && b instanceof UnopExpr) {
return shallowCompareUnopExpr((UnopExpr) a, (UnopExpr) b);
} else if (a instanceof BinopExpr && b instanceof BinopExpr) {
return shallowCompareBinopExpr((BinopExpr) a, (BinopExpr) b);
} else {
return a.toString().equals(b.toString());
}
}
private boolean shallowCompareUnopExpr(UnopExpr a, UnopExpr b) {
// TODO add stuff to handle double negation.
return a.getClass() == b.getClass() && shallowCompareValue(a.getOp(), b.getOp());
}
private boolean shallowCompareBinopExpr(BinopExpr a, BinopExpr b) {
if (!a.getSymbol().equals(b.getSymbol())) {
return false;
}
return shallowCompareValue(a.getOp1(), b.getOp1()) && shallowCompareValue(a.getOp2(), b.getOp2());
}
/**
* Gets the handler unit of a trap and collects all units that are only
* reachable via (or dominated by) this handler unit.
*
* @param body
* Body of the procedure.
* @param catchEntry
* the first statement in a catch-block (i.e., the handler unit
* in a Trap).
* @return List of Units that constitute a catch-block.
*/
private List<Unit> collectCatchBlock(Body body, Unit catchEntry) {
// TODO: check if there is a function to compute this on the UnitGraph
// in soot.
UnitGraph graph = new CompleteUnitGraph(body);
// collect all blocks reachable from catchEntry
Queue<Unit> todo = new LinkedList<Unit>();
List<Unit> done = new LinkedList<Unit>();
todo.add(catchEntry);
while (!todo.isEmpty()) {
Unit u = todo.poll();
done.add(u);
for (Unit next : graph.getSuccsOf(u)) {
if (done.containsAll(graph.getPredsOf(next))) {
// if we have seen all predecessors, we're done.
if (!todo.contains(next) && !done.contains(next)) {
todo.add(next);
}
} else {
// skip next, because it will be added later,
// or skipped if it has a predecessor that is not reachable
// from catchEntry.
}
}
}
return done;
}
/**
* Tries to remove boilerplate stuff from catch blocks like $r6
* := @caughtexception r2 = $r6 ... throw r2; and returns true if successful
* and false otherwise.
*
* @param catchStmts
* @return
*/
private boolean tryRemoveCatchBlockBoilderplate(List<Unit> catchStmts) {
if (catchStmts.size() >= 2) {
Unit first = catchStmts.get(0);
Unit second = catchStmts.get(1);
Unit last = catchStmts.get(catchStmts.size() - 1);
/*
* Remove the default catch block statements: $r6
* := @caughtexception r2 = $r6 ... throw r2
*/
DefinitionStmt handler = (IdentityStmt) first;
catchStmts.remove(handler);
Local l = (Local) handler.getLeftOp();
if (second instanceof DefinitionStmt) {
DefinitionStmt exRename = (DefinitionStmt) second;
if (exRename.getRightOp() == l) {
// if there is a throw statement in the end, remove it.
catchStmts.remove(exRename);
if (last instanceof ThrowStmt) {
ThrowStmt thr = (ThrowStmt) last;
if (thr.getOp() == exRename.getLeftOp()) {
catchStmts.remove(thr);
return true;
}
}
}
} else {
catchStmts.remove(handler);
return true;
}
if (last instanceof ReturnStmt || last instanceof GotoStmt) {
catchStmts.remove(last);
return true;
}
}
return false;
}
}