diff --git a/sootup.java.bytecode/src/main/java/sootup/java/bytecode/interceptors/LocalSplitter.java b/sootup.java.bytecode/src/main/java/sootup/java/bytecode/interceptors/LocalSplitter.java
index 99bc4fcafe4..f860a9621fc 100644
--- a/sootup.java.bytecode/src/main/java/sootup/java/bytecode/interceptors/LocalSplitter.java
+++ b/sootup.java.bytecode/src/main/java/sootup/java/bytecode/interceptors/LocalSplitter.java
@@ -23,20 +23,15 @@
*/
import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import sootup.core.graph.StmtGraph;
-import sootup.core.jimple.basic.LValue;
+import sootup.core.graph.*;
import sootup.core.jimple.basic.Local;
-import sootup.core.jimple.basic.Value;
-import sootup.core.jimple.common.ref.JCaughtExceptionRef;
import sootup.core.jimple.common.stmt.AbstractDefinitionStmt;
-import sootup.core.jimple.common.stmt.JIdentityStmt;
import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.Body;
-import sootup.core.model.Body.BodyBuilder;
import sootup.core.transform.BodyInterceptor;
-import sootup.core.types.ClassType;
import sootup.core.views.View;
/**
@@ -58,349 +53,237 @@
*
*
* l0 := @this Test
- * l1#1 = 0
- * l2#2 = 1
- * l1#3 = l1#1 + 1
- * l2#4 = l2#2 + 1
+ * l1#0 = 0
+ * l2#0 = 1
+ * l1#1 = l1#0 + 1
+ * l2#1 = l2#0 + 1
* return
*
- *
- * @author Zun Wang
*/
public class LocalSplitter implements BodyInterceptor {
- // FIXME: [ms] assumes that names of Locals do not contain a '#' already -> could lead to problems
- // TODO: [ms] check equivTo()'s - I guess they can be equals()'s - or even: '=='s
+
+ /**
+ * Contains disjoint sets of nodes which are implemented as trees. Every set is represented by a
+ * tree in the forest. Each set is identified by the root node of its tree, also known as its
+ * representative.
+ *
+ * Disjoint-set data
+ * structure
+ */
+ static class DisjointSetForest {
+ /** Every node points to its parent in its tree. Roots of trees point to themselves. */
+ private final Map parent = new HashMap<>();
+
+ /** Stores the size of a tree under the key. Only updated for roots of trees. */
+ private final Map sizes = new HashMap<>();
+
+ /**
+ * Creates a new set that only contains the {@code node}. Does nothing when the forest already
+ * contains the {@code node}.
+ */
+ void add(T node) {
+ if (parent.containsKey(node)) return;
+
+ parent.put(node, node);
+ sizes.put(node, 1);
+ }
+
+ /** Finds the representative of the set that contains the {@code node}. */
+ T find(T node) {
+ if (!parent.containsKey(node))
+ throw new IllegalArgumentException("The DisjointSetForest does not contain the node.");
+
+ while (parent.get(node) != node) {
+ // Path Halving to get amortized constant operations
+ T grandparent = parent.get(parent.get(node));
+ parent.put(node, grandparent);
+
+ node = grandparent;
+ }
+ return node;
+ }
+
+ /**
+ * Combines the sets of {@code first} and {@code second}. Returns the representative of the
+ * combined set.
+ */
+ void union(T first, T second) {
+ first = find(first);
+ second = find(second);
+
+ if (first == second) return;
+
+ T smaller = (sizes.get(first) > sizes.get(second)) ? second : first;
+ T larger = (smaller == first) ? second : first;
+
+ // adding the smaller subtree to the larger tree keeps the tree flatter
+ parent.put(smaller, larger);
+ sizes.put(larger, sizes.get(smaller) + sizes.get(larger));
+ sizes.remove(smaller);
+ }
+
+ int getSetCount() {
+ // `sizes` only contains values for the root of the trees,
+ // so its size matches the total number of sets
+ return sizes.size();
+ }
+ }
+
+ static class PartialStmt {
+ @Nonnull final Stmt inner;
+
+ /**
+ * Whether the partial statement refers to only the definitions of the inner statement or only
+ * to the uses.
+ */
+ final boolean isDef;
+
+ PartialStmt(@Nonnull Stmt inner, boolean isDef) {
+ this.inner = inner;
+ this.isDef = isDef;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PartialStmt that = (PartialStmt) o;
+
+ if (isDef != that.isDef) return false;
+ return inner.equals(that.inner);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = inner.hashCode();
+ result = 31 * result + (isDef ? 1 : 0);
+ return result;
+ }
+ }
@Override
public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) {
+ MutableStmtGraph graph = builder.getStmtGraph();
- // Find all Locals that must be split
- // If a local as a definition appears two or more times, then this local must be split
- List stmts = builder.getStmts();
- Set visitedLocals = new LinkedHashSet<>();
- Set toSplitLocals = new LinkedHashSet<>();
- for (Stmt stmt : stmts) {
- final List defs = stmt.getDefs();
- if (!defs.isEmpty()) {
- Value def = defs.get(0);
- if (def instanceof Local) {
- if (visitedLocals.contains(def)) {
- toSplitLocals.add((Local) def);
- }
- visitedLocals.add((Local) def);
- }
+ // `#` is used as a special character for splitting the locals,
+ // so it can't be in any of the original local names since it might cause name collisions
+ assert builder.getLocals().stream().noneMatch(local -> local.getName().contains("#"));
+
+ // Cache the statements to not have to retrieve them for every local
+ List statements = new ArrayList<>(graph.getStmts());
+ // Maps every local to its assignment statements.
+ // Contains indices to the above list to reduce bookkeeping when modifying statements.
+ Map> assignmentsByLocal = groupAssignmentsByLocal(statements);
+
+ Set newLocals = new HashSet<>();
+
+ for (Local local : builder.getLocals()) {
+ // Use a disjoint set while walking the statement graph to union all uses of the local that
+ // can be reached from each definition. This will automatically union definitions that have
+ // overlapping uses and therefore can't be split.
+ // It uses a `PartialStmt` instead of `Stmt` because a statement might contain both a
+ // definition and use of a local, and they need to be processed separately.
+ DisjointSetForest disjointSet = new DisjointSetForest<>();
+
+ List assignments =
+ assignmentsByLocal.getOrDefault(local, Collections.emptyList()).stream()
+ .map(i -> (AbstractDefinitionStmt) statements.get(i))
+ .collect(Collectors.toList());
+
+ if (assignments.size() <= 1) {
+ // There is only a single assignment to the local, so no splitting is necessary
+ newLocals.add(local);
+ continue;
}
- }
- StmtGraph> graph = builder.getStmtGraph();
-
- // Create a new Local-Set for the modified new body.
- Set newLocals = new LinkedHashSet<>(builder.getLocals());
- int localIndex = 1;
-
- // iterate stmts
- while (!stmts.isEmpty()) {
- Stmt currentStmt = stmts.remove(0);
- // At first Check the definition(left side) of the currentStmt is a local which must be split:
- final List defs = currentStmt.getDefs();
- if (!defs.isEmpty() && defs.get(0) instanceof Local && toSplitLocals.contains(defs.get(0))) {
- // then assign a new name to the oriLocal to get a new local which is called newLocal
- Local oriLocal = (Local) defs.get(0);
- Local newLocal = oriLocal.withName(oriLocal.getName() + "#" + localIndex);
- newLocals.add(newLocal);
- localIndex++;
-
- // create newStmt whose definition is replaced with the newLocal,
- Stmt newStmt = ((AbstractDefinitionStmt) currentStmt).withNewDef(newLocal);
- // replace corresponding oldStmt with newStmt in builder
- replaceStmtInBuilder(builder, stmts, currentStmt, newStmt);
-
- // Build the forwardsQueue which is used to iterate all Stmts before the orilocal is defined
- // again.
- // The direction of iteration is from root of the StmtGraph to leafs. So the successors of
- // the newStmt are added into the forwardsQueue.
- Deque forwardsQueue = new ArrayDeque<>(graph.successors(newStmt));
- // Create the visitedStmt to store the visited Stmts for the forwardsQueue, to avoid, a
- // Stmt is added twice into the forwardQueue.
- Set visitedStmts = new HashSet<>();
-
- while (!forwardsQueue.isEmpty()) {
- Stmt head = forwardsQueue.remove();
- visitedStmts.add(head);
-
- // 1.case: if useList of head contains oriLocal, then replace the oriLocal with
- // newLocal.
- if (head.getUses().contains(oriLocal)) {
- Stmt newHead = head.withNewUse(oriLocal, newLocal);
- replaceStmtInBuilder(builder, stmts, head, newHead);
-
- // if head doesn't define the the oriLocal again, then add all successors which are
- // not in forwardsQueue and visitedUsesStmt, into the forwardsQueue.
- if (newHead.getDefs().isEmpty() || !newHead.getDefs().get(0).equivTo(oriLocal)) {
- for (Stmt succ : graph.successors(newHead)) {
- if (!visitedStmts.contains(succ) && !forwardsQueue.contains(succ)) {
- forwardsQueue.addLast(succ);
- }
- }
- }
- }
+ // Walk the statement graph starting from every definition and union all uses until a
+ // different definition is encountered.
+ for (AbstractDefinitionStmt assignment : assignments) {
+ PartialStmt defStmt = new PartialStmt(assignment, true);
+ disjointSet.add(defStmt);
+
+ List stack = new ArrayList<>(graph.successors(assignment));
+ stack.addAll(graph.exceptionalSuccessors(assignment).values());
+ Set visited = new HashSet<>();
- // 2.case: if uses of head contains the modified orilocal, so a conflict maybe arise,
- // then trace the StmtGraph backwards to resolve the conflict.
- else if (hasModifiedUse(head, oriLocal)) {
-
- Local modifiedLocal = getModifiedUse(head, oriLocal);
- if (modifiedLocal == null) {
- throw new IllegalStateException("Modified Use is not found.");
- }
-
- // if modifed name is not same as the newLocal's name then -> conflict arises -> trace
- // backwards
- if (!modifiedLocal.getName().equals(newLocal.getName())) {
- localIndex--;
-
- // Build the backwardsQueue which is used to iterate all Stmts between head and the
- // Stmts which define the oriLocal in last time.
- // The direction of iteration is from leave of the StmtGraph to the root. So the
- // predecessors of head are added into the BackwardsQueue.
- Deque backwardsQueue = new ArrayDeque<>(graph.predecessors(head));
-
- while (!backwardsQueue.isEmpty()) {
- // Remove the first Stmt of backwardQueue, and name it as backStmt.
- Stmt backStmt = backwardsQueue.remove();
-
- // 2.1 case: if backStmt's definition is the modified and has a higher
- // local-name-index than the modifiedLocal of head
- // then replace the definition of backStmt with the modifiedLocal of head, and
- // remove the corresponding Local(definition of backStmt) from the set: newLocals
- if (hasModifiedDef(backStmt, oriLocal)) {
- if (hasHigherLocalName((Local) backStmt.getDefs().get(0), modifiedLocal)) {
- Stmt newBackStmt =
- ((AbstractDefinitionStmt) backStmt).withNewDef(modifiedLocal);
- replaceStmtInBuilder(builder, stmts, backStmt, newBackStmt);
- newLocals.remove(newLocal);
- }
- }
- // 2.2 case: if backStmt's uses contains the modified oriLocal, and this
- // modified oriLocal has a higher local-name-index that the modifiedLocal of head
- // then replace the corresponding use of backStmt with modifiedLocal of head, and
- // add all predecessors of the backStmt into the backwardsQueue.
- else if (hasModifiedUse(backStmt, oriLocal)) {
- Local modifiedUse = getModifiedUse(backStmt, oriLocal);
- if (hasHigherLocalName(modifiedUse, modifiedLocal)) {
- Stmt newBackStmt = backStmt.withNewUse(modifiedUse, modifiedLocal);
- replaceStmtInBuilder(builder, stmts, backStmt, newBackStmt);
- backwardsQueue.addAll(graph.predecessors(newBackStmt));
- }
- }
- // 2.3 case: if there's no relationship between backStmt's defs/uses and
- // oriLocal, then add all predecessors of the backStmt into the backwardsQueue.
- else {
- backwardsQueue.addAll(graph.predecessors(backStmt));
- }
- }
- }
+ while (!stack.isEmpty()) {
+ Stmt stmt = stack.remove(stack.size() - 1);
+ if (!visited.add(stmt)) {
+ continue;
}
- // 3.case: if uses of head contains neither orilocal nor the modified orilocal,
- // then add all successors of head which are not in forwardsQueue and visitedStmts,
- // into the forwardsQueue.
- else {
- final List headDefs = head.getDefs();
- if (headDefs.isEmpty() || !headDefs.get(0).equivTo(oriLocal)) {
- for (Stmt succ : graph.successors(head)) {
- if (!visitedStmts.contains(succ) && !forwardsQueue.contains(succ)) {
- forwardsQueue.addLast(succ);
- }
- }
- }
+
+ if (stmt.getUses().contains(local)) {
+ PartialStmt useStmt = new PartialStmt(stmt, false);
+ disjointSet.add(useStmt);
+ disjointSet.union(defStmt, useStmt);
}
- }
- // Then check the uses of currentStmt:
- } else {
- // For each Local(oriL) which is to be split, check whether it is used in currentStmt
- // without definition before.
- // We define a StmtGraph consists of a mainStmtGraph and none or more trapStmtGraphs.
- // This situation could arise just in a trapStmtGraph.
- for (Local oriLocal : toSplitLocals) {
- // If so:
- // 1.step: find out all trapStmtGraphs' root(handlerStmts) which contain currentStmt,
- // namely a set of handlerStmts.
- // 2.step: find out all stmts whose exceptional destination-traps are with the found
- // handlerStmts.
- // 3.step: iterate these stmts, find a modified oriL((Local) with a maximum name index.
- // 4.step: Use this modified oriL to modify the visitedStmt
- if (currentStmt.getUses().contains(oriLocal)) {
- // 1.step:
- Set handlerStmts = traceHandlerStmts(graph, currentStmt);
- // 2.step:
- Set stmtsWithDests = new HashSet<>();
- for (Stmt handlerStmt : handlerStmts) {
- for (Stmt exceptionalPred : graph.predecessors(handlerStmt)) {
- Map dests = graph.exceptionalSuccessors(exceptionalPred);
- List destHandlerStmts = new ArrayList<>();
- dests.forEach((key, dest) -> destHandlerStmts.add(dest));
- if (destHandlerStmts.contains(handlerStmt)) {
- stmtsWithDests.add(exceptionalPred);
- }
- }
- }
- // 3.step:
- Local lastChange = null;
- for (Stmt stmt : stmtsWithDests) {
- if (hasModifiedDef(stmt, oriLocal)) {
- Local modifiedLocal = (Local) stmt.getDefs().get(0);
- if (lastChange == null || hasHigherLocalName(modifiedLocal, lastChange)) {
- lastChange = modifiedLocal;
- }
- }
- }
- // 4.step:
- if (lastChange != null) {
- Stmt newStmt = currentStmt.withNewUse(oriLocal, lastChange);
- replaceStmtInBuilder(builder, stmts, currentStmt, newStmt);
- }
+
+ // a new assignment to the local -> end walk here
+ // otherwise continue by adding all successors to the stack
+ if (!stmt.getDefs().contains(local)) {
+ stack.addAll(graph.successors(stmt));
+ stack.addAll(graph.exceptionalSuccessors(stmt).values());
}
}
}
- }
- builder.setLocals(newLocals);
- }
- // ******************assist_functions*************************
+ if (disjointSet.getSetCount() <= 1) {
+ // There is only a single that local that can't be split
+ newLocals.add(local);
+ continue;
+ }
- /**
- * Replace corresponding oldStmt with newStmt in BodyBuilder and visitList
- *
- * @param builder
- * @param stmtIterationList
- * @param oldStmt
- * @param newStmt
- */
- private void replaceStmtInBuilder(
- @Nonnull BodyBuilder builder,
- @Nonnull List stmtIterationList,
- @Nonnull Stmt oldStmt,
- @Nonnull Stmt newStmt) {
-
- builder.replaceStmt(oldStmt, newStmt);
-
- // adapt VisitList
- final int index = stmtIterationList.indexOf(oldStmt);
- if (index > -1) {
- stmtIterationList.set(index, newStmt);
- }
- }
+ // Split locals, according to the disjoint sets found above.
+ Map representativeToNewLocal = new HashMap<>();
+ final int[] nextId = {0}; // Java quirk; just an `int` doesn't work
- /**
- * Check whether a Stmt's useList contains the given modified oriLocal.
- *
- * @param stmt: a stmt is to be checked
- * @param oriLocal: a local is to be checked
- * @return if so, return true, else return false
- */
- private boolean hasModifiedUse(@Nonnull Stmt stmt, @Nonnull Local oriLocal) {
- for (Value use : stmt.getUses()) {
- return isLocalFromSameOrigin(oriLocal, use);
- }
- return false;
- }
+ for (int i = 0; i < statements.size(); i++) {
+ Stmt stmt = statements.get(i);
+ if (!stmt.getUsesAndDefs().contains(local)) {
+ continue;
+ }
- /**
- * Get the modified Local if a Stmt's useList contains the given modified oriLocal
- *
- * @param stmt: a stmt is to be checked
- * @param oriLocal: a local is to be checked.
- * @return if so, return this modified local, else return null
- */
- @Nullable
- private Local getModifiedUse(@Nonnull Stmt stmt, @Nonnull Local oriLocal) {
- if (hasModifiedUse(stmt, oriLocal)) {
- List useList = stmt.getUses();
- for (Value use : useList) {
- if (isLocalFromSameOrigin(oriLocal, use)) {
- return (Local) use;
+ Stmt oldStmt = stmt;
+
+ Function getNewLocal =
+ isDef ->
+ representativeToNewLocal.computeIfAbsent(
+ disjointSet.find(new PartialStmt(oldStmt, isDef)),
+ s -> local.withName(local.getName() + "#" + (nextId[0]++)));
+
+ if (stmt.getDefs().contains(local)) {
+ Local newDefLocal = getNewLocal.apply(true);
+ newLocals.add(newDefLocal);
+ stmt = ((AbstractDefinitionStmt) stmt).withNewDef(newDefLocal);
+ }
+
+ if (stmt.getUses().contains(local)) {
+ Local newUseLocal = getNewLocal.apply(false);
+ newLocals.add(newUseLocal);
+ stmt = stmt.withNewUse(local, newUseLocal);
}
+
+ graph.replaceNode(oldStmt, stmt);
+ statements.set(i, stmt);
}
}
- return null;
- }
- /**
- * Check whether a local is modified from the given oriLocal
- *
- * @param local: a local is to be checked
- * @param oriLocal: the given oriLocal
- * @return if so, return true, else return false.
- */
- private boolean isLocalFromSameOrigin(@Nonnull Local oriLocal, Value local) {
- if (local instanceof Local) {
- final String name = ((Local) local).getName();
- final String origName = oriLocal.getName();
- final int origLength = origName.length();
- return name.length() > origLength
- && name.startsWith(origName)
- && name.charAt(origLength) == '#';
- }
- return false;
+ builder.setLocals(newLocals);
}
- /**
- * Check whether a Stmt's def is the modified oriLocal
- *
- * @param stmt: a stmt is to be checked
- * @param oriLocal: a local is to be checked
- * @return if so, return true, else return false
- */
- private boolean hasModifiedDef(@Nonnull Stmt stmt, @Nonnull Local oriLocal) {
- final List defs = stmt.getDefs();
- if (!defs.isEmpty() && defs.get(0) instanceof Local) {
- return isLocalFromSameOrigin(oriLocal, defs.get(0));
- }
- return false;
- }
+ Map> groupAssignmentsByLocal(List statements) {
+ Map> groupings = new HashMap<>();
- /**
- * Check whether leftLocal's name has higher index than rightLocal's.
- *
- * @param leftLocal: a local in form oriLocal#num1
- * @param rightLocal: a local in form oriLocal#num2
- * @return if so return true, else return false
- */
- private boolean hasHigherLocalName(@Nonnull Local leftLocal, @Nonnull Local rightLocal) {
- String leftName = leftLocal.getName();
- String rightName = rightLocal.getName();
- int lIdx = leftName.lastIndexOf('#');
- int rIdx = rightName.lastIndexOf('#');
- int leftNum = Integer.parseInt(leftName.substring(lIdx + 1));
- int rightNum = Integer.parseInt(rightName.substring(rIdx + 1));
- return leftNum > rightNum;
- }
+ for (int i = 0; i < statements.size(); i++) {
+ Stmt stmt = statements.get(i);
+ if (!(stmt instanceof AbstractDefinitionStmt)) continue;
+ AbstractDefinitionStmt defStmt = (AbstractDefinitionStmt) stmt;
+ if (!(defStmt.getLeftOp() instanceof Local)) continue;
- /**
- * A given entryStmt may be in one or several trapStmtGraphs, return these trapStmtGraphs'
- * handlerStmts
- *
- * @param entryStmt a given entryStmt which is in one or several trapStmtGraphs
- * @param graph to trace handlerStmts
- * @return a set of handlerStmts
- */
- @Nonnull
- private Set traceHandlerStmts(@Nonnull StmtGraph> graph, @Nonnull Stmt entryStmt) {
-
- Set handlerStmts = new HashSet<>();
-
- Deque queue = new ArrayDeque<>();
- queue.add(entryStmt);
- while (!queue.isEmpty()) {
- Stmt stmt = queue.removeFirst();
- if (stmt instanceof JIdentityStmt
- && ((JIdentityStmt) stmt).getRightOp() instanceof JCaughtExceptionRef) {
- handlerStmts.add(stmt);
- } else {
- final List predecessors = graph.predecessors(stmt);
- queue.addAll(predecessors);
- }
+ groupings.computeIfAbsent((Local) defStmt.getLeftOp(), x -> new ArrayList<>()).add(i);
}
- return handlerStmts;
+
+ return groupings;
}
}
diff --git a/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.class b/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.class
new file mode 100644
index 00000000000..fbf57011fa2
Binary files /dev/null and b/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.class differ
diff --git a/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.java b/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.java
new file mode 100644
index 00000000000..cbbd4052a19
--- /dev/null
+++ b/sootup.java.bytecode/src/test/java/resources/interceptors/LocalSplitterTarget.java
@@ -0,0 +1,128 @@
+public class LocalSplitterTarget {
+ void simpleAssignment() {
+ int a = 0;
+ int b = 1;
+ a = b + 1;
+ b = a + 1;
+ }
+
+ void selfAssignment() {
+ int a = 0;
+ int b = 1;
+ a = a + 1;
+ b = b + 1;
+ }
+
+ int branch() {
+ int a = 0;
+ if (a < 0) {
+ a = a + 1;
+ } else {
+ a = a - 1;
+ a = a + 2;
+ }
+ return a;
+ }
+
+ int branchMoreLocals() {
+ int a = 0;
+ if (a < 0) {
+ a = a + 1;
+ a = a + 2;
+ a = a + 3;
+ } else {
+ a = a - 1;
+ a = a - 2;
+ a = a - 3;
+ }
+ return a;
+ }
+
+ int branchMoreBranches() {
+ int a = 0;
+ if (a < 0) {
+ a = a + 1;
+ a = a + 2;
+ } else {
+ a = a - 1;
+ a = a - 2;
+ }
+
+ if (a > 1) {
+ a = a + 3;
+ a = a + 5;
+ } else {
+ a = a - 3;
+ a = a - 5;
+ }
+
+ return a;
+ }
+
+ int branchElseIf() {
+ int a = 0;
+ if (a < 0) {
+ a = a + 1;
+ a = a + 2;
+ } else if (a < 5) {
+ a = a - 1;
+ a = a - 2;
+ } else {
+ a = a * 1;
+ a = a * 2;
+ }
+
+ return a;
+ }
+
+ int forLoop() {
+ int a = 0;
+ for (int i = 0; i < 10; i++) {
+ i = i + 1;
+ a++;
+ }
+ return a;
+ }
+
+ void reusedLocals() {
+ // Somewhat interesting test case since the compiler will store
+ // both `b`s at the same local index
+ // and both `a`s at the same local index.
+ // This also works without them having the same name.
+ // The case for `b` is particularly interesting,
+ // since without splitting the local the `TypeAssigner` will have to assign a laxer type to `b`.
+ {
+ Object a;
+ if (Math.random() == 0.0) {
+ Integer b = 1;
+ a = b;
+ } else {
+ String b = "";
+ a = b;
+ }
+ System.out.println(a);
+ }
+ {
+ Object a = null;
+ System.out.println(a);
+ }
+ }
+
+ Object traps() {
+ int a = 1;
+ try {
+ a = 2;
+ } catch (Throwable t) {
+ return a;
+ }
+
+ String b = "";
+ try {
+ System.out.println();
+ } catch (Throwable t) {
+ return b;
+ }
+
+ return 0.0;
+ }
+}
diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/LocalSplitterTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/LocalSplitterTest.java
index c2ff3c08f3a..cc40f60384c 100644
--- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/LocalSplitterTest.java
+++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/LocalSplitterTest.java
@@ -1,603 +1,412 @@
package sootup.java.bytecode.interceptors;
+import static org.junit.Assert.assertEquals;
+
import categories.Java8Test;
import java.util.*;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
-import sootup.core.graph.MutableBlockStmtGraph;
-import sootup.core.graph.MutableStmtGraph;
import sootup.core.jimple.basic.Local;
-import sootup.core.jimple.basic.NoPositionInformation;
-import sootup.core.jimple.basic.StmtPositionInfo;
-import sootup.core.jimple.common.constant.IntConstant;
-import sootup.core.jimple.common.ref.IdentityRef;
-import sootup.core.jimple.common.stmt.BranchingStmt;
-import sootup.core.jimple.common.stmt.FallsThroughStmt;
-import sootup.core.jimple.common.stmt.JGotoStmt;
-import sootup.core.jimple.common.stmt.Stmt;
import sootup.core.model.Body;
-import sootup.core.model.Position;
-import sootup.core.signatures.MethodSignature;
+import sootup.core.model.SootMethod;
+import sootup.core.model.SourceType;
+import sootup.core.signatures.PackageName;
import sootup.core.types.ClassType;
-import sootup.core.types.VoidType;
-import sootup.core.util.ImmutableUtils;
-import sootup.java.core.JavaIdentifierFactory;
-import sootup.java.core.language.JavaJimple;
+import sootup.java.bytecode.inputlocation.JavaClassPathAnalysisInputLocation;
+import sootup.java.bytecode.inputlocation.JrtFileSystemAnalysisInputLocation;
import sootup.java.core.types.JavaClassType;
+import sootup.java.core.views.JavaView;
-/** @author Zun Wang */
@Category(Java8Test.class)
public class LocalSplitterTest {
-
- // Preparation
- JavaIdentifierFactory factory = JavaIdentifierFactory.getInstance();
- StmtPositionInfo noStmtPositionInfo = StmtPositionInfo.getNoStmtPositionInfo();
-
- JavaClassType intType = factory.getClassType("int");
- JavaClassType classType = factory.getClassType("Test");
- MethodSignature methodSignature =
- new MethodSignature(classType, "test", Collections.emptyList(), VoidType.getInstance());
- IdentityRef identityRef = JavaJimple.newThisRef(classType);
-
- // build locals
- Local l0 = JavaJimple.newLocal("l0", intType);
- Local l1 = JavaJimple.newLocal("l1", intType);
- Local l2 = JavaJimple.newLocal("l2", intType);
- Local l3 = JavaJimple.newLocal("l3", intType);
- Local stack3 = JavaJimple.newLocal("stack3", intType);
- Local stack4 = JavaJimple.newLocal("stack4", intType);
- Local l1hash1 = JavaJimple.newLocal("l1#1", intType);
- Local l1hash2 = JavaJimple.newLocal("l1#2", intType);
- Local l1hash3 = JavaJimple.newLocal("l1#3", intType);
- Local l2hash2 = JavaJimple.newLocal("l2#2", intType);
- Local l2hash4 = JavaJimple.newLocal("l2#4", intType);
-
- ClassType exception = factory.getClassType("Exception");
- JavaJimple javaJimple = JavaJimple.getInstance();
- IdentityRef caughtExceptionRef = javaJimple.newCaughtExceptionRef();
- Stmt startingStmt = JavaJimple.newIdentityStmt(l0, identityRef, noStmtPositionInfo);
-
- /**
- * int a = 0; if(a<0) a = a + 1; else {a = a+ 1; a = a +1;} return a
- *
- *
- * l0 := @this Test
- * l1 = 0
- * if l1 >= 0 goto label1
- * l1 = l1 + 1
- * goto label2
- * label1:
- * l1 = l1 - 1
- * l1 = l1 + 2
- * label2:
- * return l1
- *
- *
- * to:
- *
- *
- * l0 := @this Test
- * l1#1 = 0
- * if l1#1 >= 0 goto label1
- * l1#2 = l1#1 + 1
- * goto label2
- * label1:
- * l1#3 = l1#1 - 1
- * l1#2 = l1#3 + 2
- * label2:
- * return l1#2
- *
- */
- @Test
- public void testLocalSplitterForBinaryBranches() {
-
- Body body = createBBBody();
- Body.BodyBuilder builder = Body.builder(body, Collections.emptySet());
- LocalSplitter localSplitter = new LocalSplitter();
- localSplitter.interceptBody(builder, null);
- Body expectedBody = createExpectedBBBody();
-
- final Body interceptedBody = builder.build();
- // check newBody's locals
- AssertUtils.assertLocalsEquiv(expectedBody, interceptedBody);
-
- // check newBody's stmtGraph
- AssertUtils.assertStmtGraphEquiv(expectedBody, interceptedBody);
+ JavaView view;
+ LocalSplitter localSplitter = new LocalSplitter();
+
+ @Before
+ public void setup() {
+ String classPath = "src/test/java/resources/interceptors";
+ JavaClassPathAnalysisInputLocation inputLocation =
+ new JavaClassPathAnalysisInputLocation(classPath);
+ view = new JavaView(inputLocation);
}
- /**
- * int a = 0; int b = 0; a = a + 1; b = b + 1;
- *
- *
- * l0 := @this Test
- * l1 = 0
- * l2 = 1
- * l1 = l1 + 1
- * l2 = l2 + 1
- * return
- *
- *
- * to:
- *
- *
- * l0 := @this Test
- * l1#1 = 0
- * l2#2 = 1
- * l1#3 = l1#1 + 1
- * l2#4 = l2#2 + 1
- * return
- *
- */
- @Test
- public void testLocalSplitterForMultilocals() {
-
- Body body = createMultilocalsBody();
- Body.BodyBuilder builder = Body.builder(body, Collections.emptySet());
- LocalSplitter localSplitter = new LocalSplitter();
- localSplitter.interceptBody(builder, null);
- Body expectedBody = createExpectedMuiltilocalsBody();
-
- // check newBody's locals
- AssertUtils.assertLocalsEquiv(expectedBody, builder.build());
+ private Body getBody(String methodName) {
+ ClassType type = new JavaClassType("LocalSplitterTarget", PackageName.DEFAULT_PACKAGE);
+ SootMethod sootMethod =
+ view.getClass(type).get().getMethods().stream()
+ .filter(method -> method.getName().equals(methodName))
+ .findFirst()
+ .get();
+ return sootMethod.getBody();
+ }
- // check newBody's stmtGraph
- AssertUtils.assertStmtGraphEquiv(expectedBody, builder.build());
+ void assertLocals(Set localNames, Body.BodyBuilder builder) {
+ assertEquals(
+ localNames, builder.getLocals().stream().map(Local::getName).collect(Collectors.toSet()));
}
- /**
- * for(int i = 0; i < 10; i++){ i = i + 1 } transform:
- *
- *
- * l0 := @this Test
- * l1 = 0
- * label1:
- * $stack4 = l1
- * $stack3 = 10
- * if $stack4 >= $stack3 goto label2
- * l2 = l1 + 1
- * l1 = l2 + 1
- * l1 = l1 +1
- * goto label1
- * label2:
- * return
- *
- *
- * to:
- *
- *
- * l0 := @this Test
- * l1#1 = 0
- * label1:
- * $stack4 = l1#1
- * $stack3 = 10
- * if $stack4 >= $stack3 goto label2
- * l2 = l1#1 + 1
- * l1#2 = l2 + 1
- * l1#1 = l1#2 +1
- * goto label1
- * label2:
- * return
- *
- */
@Test
- public void testLocalSplitterForLoop() {
-
- Body body = createLoopBody();
- Body.BodyBuilder builder = Body.builder(body, Collections.emptySet());
- LocalSplitter localSplitter = new LocalSplitter();
- localSplitter.interceptBody(builder, null);
- Body expectedBody = createExpectedLoopBody();
-
- // check newBody's locals
- AssertUtils.assertLocalsEquiv(expectedBody, builder.build());
-
- // check newBody's stmtGraph
- AssertUtils.assertStmtGraphEquiv(expectedBody, builder.build());
+ @Ignore("Takes too long. Good for profiling though.")
+ public void JRT() {
+ JrtFileSystemAnalysisInputLocation inputLocation =
+ new JrtFileSystemAnalysisInputLocation(
+ SourceType.Library, Collections.singletonList(localSplitter));
+ JavaView view = new JavaView(Collections.singletonList(inputLocation));
+
+ // ~200_000 methods
+ view.getClasses()
+ .parallelStream()
+ .flatMap(clazz -> clazz.getMethods().stream())
+ .filter(method -> !method.isAbstract() && !method.isNative())
+ .forEach(SootMethod::getBody);
}
- /**
- * for(int i = 0; i < 10; i++){ i = i + 1 } transform:
- *
- *
- * l0 := @this Test
- * l1 = 0
- * l1 = 1
- * l2 = 2
- * return
- * $stack3 := @caughtexception
- * l3 = l1
- * goto return
- *
- *
- * to:
- *
- *
- * l0 := @this Test
- * l1#1 = 0
- * l1#2 = 1
- * l2 = 2
- * return
- * $stack3 := @caughtexception
- * l3 = l1#2
- * goto return
- *
- */
@Test
- public void testLocalSplitterInTraps() {
-
- Body.BodyBuilder builder = createTrapBody();
- new LocalSplitter().interceptBody(builder, null);
- Body expectedBody = createExpectedTrapBody();
-
- // check newBody's locals
- AssertUtils.assertLocalsEquiv(expectedBody, builder.build());
-
- // check newBody's stmtGraph
- AssertUtils.assertStmtGraphEquiv(expectedBody, builder.build());
+ public void testSimpleAssignment() {
+ Body.BodyBuilder builder = Body.builder(getBody("simpleAssignment"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l2#0");
+ expectedLocals.add("l2#1");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "l2#0 = 1;\n"
+ + "l1#1 = l2#0 + 1;\n"
+ + "l2#1 = l1#1 + 1;\n"
+ + "\n"
+ + "return;";
+
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- /** bodycreater for BinaryBranches */
- private Body createBBBody() {
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1, IntConstant.getInstance(0), noStmtPositionInfo);
- BranchingStmt stmt2 =
- JavaJimple.newIfStmt(
- JavaJimple.newGeExpr(l1, IntConstant.getInstance(0)), noStmtPositionInfo);
- Stmt stmt3 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newAddExpr(l1, IntConstant.getInstance(1)), noStmtPositionInfo);
- BranchingStmt stmt4 = JavaJimple.newGotoStmt(noStmtPositionInfo);
- Stmt stmt5 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newSubExpr(l1, IntConstant.getInstance(1)), noStmtPositionInfo);
- FallsThroughStmt stmt6 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newAddExpr(l1, IntConstant.getInstance(2)), noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnStmt(l1, noStmtPositionInfo);
-
- graph.addBlock(Arrays.asList(startingStmt, stmt1, stmt2), Collections.emptyMap());
- graph.setEdges(stmt2, Arrays.asList(stmt3, stmt5));
- graph.addBlock(Arrays.asList(stmt3, stmt4), Collections.emptyMap());
- graph.addBlock(Arrays.asList(stmt5, stmt6), Collections.emptyMap());
- graph.putEdge(stmt4, JGotoStmt.BRANCH_IDX, ret);
- graph.putEdge(stmt6, ret);
-
- graph.setStartingStmt(startingStmt);
-
- /* set graph
- stmtGraph.putEdge(startingStmt, stmt1);
- stmtGraph.putEdge(stmt1, stmt2);
- stmtGraph.putEdge(stmt2, stmt3);
- stmtGraph.putEdge(stmt3, stmt4);
- stmtGraph.putEdge(stmt4, ret);
- stmtGraph.putEdge(stmt2, stmt5);
- stmtGraph.putEdge(stmt5, stmt6);
- stmtGraph.putEdge(stmt6, ret);
-
- // build startingStmt
- builder.setStartingStmt(startingStmt);
- */
-
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testSelfAssignment() {
+ Body.BodyBuilder builder = Body.builder(getBody("selfAssignment"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l2#0");
+ expectedLocals.add("l2#1");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "l2#0 = 1;\n"
+ + "l1#1 = l1#0 + 1;\n"
+ + "l2#1 = l2#0 + 1;\n"
+ + "\n"
+ + "return;";
+
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- private Body createExpectedBBBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l1hash1, l1hash2, l1hash3);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1hash1, IntConstant.getInstance(0), noStmtPositionInfo);
- BranchingStmt stmt2 =
- JavaJimple.newIfStmt(
- JavaJimple.newGeExpr(l1hash1, IntConstant.getInstance(0)), noStmtPositionInfo);
- Stmt stmt3 =
- JavaJimple.newAssignStmt(
- l1hash2,
- JavaJimple.newAddExpr(l1hash1, IntConstant.getInstance(1)),
- noStmtPositionInfo);
- BranchingStmt stmt4 = JavaJimple.newGotoStmt(noStmtPositionInfo);
- Stmt stmt5 =
- JavaJimple.newAssignStmt(
- l1hash3,
- JavaJimple.newSubExpr(l1hash1, IntConstant.getInstance(1)),
- noStmtPositionInfo);
- FallsThroughStmt stmt6 =
- JavaJimple.newAssignStmt(
- l1hash2,
- JavaJimple.newAddExpr(l1hash3, IntConstant.getInstance(2)),
- noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnStmt(l1hash2, noStmtPositionInfo);
-
- graph.addBlock(Arrays.asList(startingStmt, stmt1, stmt2), Collections.emptyMap());
- graph.setEdges(stmt2, Arrays.asList(stmt3, stmt5));
- graph.addBlock(Arrays.asList(stmt3, stmt4), Collections.emptyMap());
- graph.putEdge(stmt4, JGotoStmt.BRANCH_IDX, ret);
- graph.addBlock(Arrays.asList(stmt5, stmt6), Collections.emptyMap());
- graph.putEdge(stmt6, ret);
-
- graph.setStartingStmt(startingStmt);
-
- /* set graph
- stmtGraph.putEdge(startingStmt, stmt1);
- stmtGraph.putEdge(stmt1, stmt2);
- stmtGraph.putEdge(stmt2, stmt3);
- stmtGraph.putEdge(stmt3, stmt4);
- stmtGraph.putEdge(stmt4, ret);
- stmtGraph.putEdge(stmt2, stmt5);
- stmtGraph.putEdge(stmt5, stmt6);
- stmtGraph.putEdge(stmt6, ret);
-
- // build startingStmt
- builder.setStartingStmt(startingStmt);
- */
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testBranch() {
+ Body.BodyBuilder builder = Body.builder(getBody("branch"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l1#2");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "\n"
+ + "if l1#0 >= 0 goto label1;\n"
+ + "l1#1 = l1#0 + 1;\n"
+ + "\n"
+ + "goto label2;\n"
+ + "\n"
+ + "label1:\n"
+ + "l1#2 = l1#0 - 1;\n"
+ + "l1#1 = l1#2 + 2;\n"
+ + "\n"
+ + "label2:\n"
+ + "return l1#1;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- /** bodycreater for multilocals */
- private Body createMultilocalsBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1, IntConstant.getInstance(0), noStmtPositionInfo);
- Stmt stmt2 = JavaJimple.newAssignStmt(l2, IntConstant.getInstance(1), noStmtPositionInfo);
- Stmt stmt3 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newAddExpr(l1, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt stmt4 =
- JavaJimple.newAssignStmt(
- l2, JavaJimple.newAddExpr(l2, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- graph.addBlock(
- Arrays.asList(startingStmt, stmt1, stmt2, stmt3, stmt4, ret), Collections.emptyMap());
- graph.setStartingStmt(startingStmt);
-
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testBranchMoreLocals() {
+ Body.BodyBuilder builder = Body.builder(getBody("branchMoreLocals"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l1#2");
+ expectedLocals.add("l1#3");
+ expectedLocals.add("l1#4");
+ expectedLocals.add("l1#5");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "\n"
+ + "if l1#0 >= 0 goto label1;\n"
+ + "l1#1 = l1#0 + 1;\n"
+ + "l1#2 = l1#1 + 2;\n"
+ + "l1#3 = l1#2 + 3;\n"
+ + "\n"
+ + "goto label2;\n"
+ + "\n"
+ + "label1:\n"
+ + "l1#4 = l1#0 - 1;\n"
+ + "l1#5 = l1#4 - 2;\n"
+ + "l1#3 = l1#5 - 3;\n"
+ + "\n"
+ + "label2:\n"
+ + "return l1#3;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- private Body createExpectedMuiltilocalsBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2, l1hash1, l2hash2, l1hash3, l2hash4);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1hash1, IntConstant.getInstance(0), noStmtPositionInfo);
- Stmt stmt2 = JavaJimple.newAssignStmt(l2hash2, IntConstant.getInstance(1), noStmtPositionInfo);
- Stmt stmt3 =
- JavaJimple.newAssignStmt(
- l1hash3,
- JavaJimple.newAddExpr(l1hash1, IntConstant.getInstance(1)),
- noStmtPositionInfo);
- Stmt stmt4 =
- JavaJimple.newAssignStmt(
- l2hash4,
- JavaJimple.newAddExpr(l2hash2, IntConstant.getInstance(1)),
- noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- graph.addBlock(
- Arrays.asList(startingStmt, stmt1, stmt2, stmt3, stmt4, ret), Collections.emptyMap());
- graph.setStartingStmt(startingStmt);
-
- /* set graph
- stmtGraph.putEdge(startingStmt, stmt1);
- stmtGraph.putEdge(stmt1, stmt2);
- stmtGraph.putEdge(stmt2, stmt3);
- stmtGraph.putEdge(stmt3, stmt4);
- stmtGraph.putEdge(stmt4, ret);
-
- // set first stmt
- builder.setStartingStmt(startingStmt);
- */
-
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testBranchMoreBranches() {
+ Body.BodyBuilder builder = Body.builder(getBody("branchMoreBranches"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l1#2");
+ expectedLocals.add("l1#3");
+ expectedLocals.add("l1#4");
+ expectedLocals.add("l1#5");
+ expectedLocals.add("l1#6");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "\n"
+ + "if l1#0 >= 0 goto label1;\n"
+ + "l1#1 = l1#0 + 1;\n"
+ + "l1#2 = l1#1 + 2;\n"
+ + "\n"
+ + "goto label2;\n"
+ + "\n"
+ + "label1:\n"
+ + "l1#3 = l1#0 - 1;\n"
+ + "l1#2 = l1#3 - 2;\n"
+ + "\n"
+ + "label2:\n"
+ + "if l1#2 <= 1 goto label3;\n"
+ + "l1#4 = l1#2 + 3;\n"
+ + "l1#5 = l1#4 + 5;\n"
+ + "\n"
+ + "goto label4;\n"
+ + "\n"
+ + "label3:\n"
+ + "l1#6 = l1#2 - 3;\n"
+ + "l1#5 = l1#6 - 5;\n"
+ + "\n"
+ + "label4:\n"
+ + "return l1#5;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- /** bodycreater for Loop */
- private Body createLoopBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2, stack3, stack4);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1, IntConstant.getInstance(0), noStmtPositionInfo);
- Stmt stmt2 = JavaJimple.newAssignStmt(stack4, l1, noStmtPositionInfo);
- Stmt stmt3 = JavaJimple.newAssignStmt(stack3, IntConstant.getInstance(10), noStmtPositionInfo);
- BranchingStmt stmt4 =
- JavaJimple.newIfStmt(
- JavaJimple.newGeExpr(stack4, stack3), noStmtPositionInfo); // branch to ret
- Stmt stmt5 =
- JavaJimple.newAssignStmt(
- l2, JavaJimple.newAddExpr(l1, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt stmt6 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newAddExpr(l2, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt stmt7 =
- JavaJimple.newAssignStmt(
- l1, JavaJimple.newAddExpr(l1, IntConstant.getInstance(1)), noStmtPositionInfo);
- BranchingStmt stmt8 = JavaJimple.newGotoStmt(noStmtPositionInfo); // goto stmt2
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- graph.addBlock(Arrays.asList(startingStmt, stmt1, stmt2, stmt3, stmt4), Collections.emptyMap());
- graph.setEdges(stmt4, Arrays.asList(stmt5, ret));
- graph.addBlock(Arrays.asList(stmt5, stmt6, stmt7, stmt8), Collections.emptyMap());
- graph.putEdge(stmt8, JGotoStmt.BRANCH_IDX, stmt2);
-
- graph.setStartingStmt(startingStmt);
-
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testBranchElseIf() {
+ Body.BodyBuilder builder = Body.builder(getBody("branchElseIf"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l1#2");
+ expectedLocals.add("l1#3");
+ expectedLocals.add("l1#4");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 0;\n"
+ + "\n"
+ + "if l1#0 >= 0 goto label1;\n"
+ + "l1#1 = l1#0 + 1;\n"
+ + "l1#2 = l1#1 + 2;\n"
+ + "\n"
+ + "goto label3;\n"
+ + "\n"
+ + "label1:\n"
+ + "if l1#0 >= 5 goto label2;\n"
+ + "l1#3 = l1#0 - 1;\n"
+ + "l1#2 = l1#3 - 2;\n"
+ + "\n"
+ + "goto label3;\n"
+ + "\n"
+ + "label2:\n"
+ + "l1#4 = l1#0 * 1;\n"
+ + "l1#2 = l1#4 * 2;\n"
+ + "\n"
+ + "label3:\n"
+ + "return l1#2;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- private Body createExpectedLoopBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2, stack3, stack4, l1hash1, l1hash2);
-
- builder.setLocals(locals);
-
- Stmt stmt1 = JavaJimple.newAssignStmt(l1hash1, IntConstant.getInstance(0), noStmtPositionInfo);
- Stmt stmt2 = JavaJimple.newAssignStmt(stack4, l1hash1, noStmtPositionInfo);
- Stmt stmt3 = JavaJimple.newAssignStmt(stack3, IntConstant.getInstance(10), noStmtPositionInfo);
- BranchingStmt stmt4 =
- JavaJimple.newIfStmt(
- JavaJimple.newGeExpr(stack4, stack3), noStmtPositionInfo); // branch to ret
- Stmt stmt5 =
- JavaJimple.newAssignStmt(
- l2, JavaJimple.newAddExpr(l1hash1, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt stmt6 =
- JavaJimple.newAssignStmt(
- l1hash2, JavaJimple.newAddExpr(l2, IntConstant.getInstance(1)), noStmtPositionInfo);
- Stmt stmt7 =
- JavaJimple.newAssignStmt(
- l1hash1,
- JavaJimple.newAddExpr(l1hash2, IntConstant.getInstance(1)),
- noStmtPositionInfo);
- BranchingStmt stmt8 = JavaJimple.newGotoStmt(noStmtPositionInfo); // goto stmt2
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- graph.addBlock(Arrays.asList(startingStmt, stmt1, stmt2, stmt3, stmt4), Collections.emptyMap());
- graph.setEdges(stmt4, Arrays.asList(stmt5, ret));
- graph.addBlock(Arrays.asList(stmt5, stmt6, stmt7, stmt8), Collections.emptyMap());
- graph.putEdge(stmt8, JGotoStmt.BRANCH_IDX, stmt2);
-
- graph.setStartingStmt(startingStmt);
-
- // build startingStmt
- builder.setStartingStmt(startingStmt);
-
- // build position
- Position position = NoPositionInformation.getInstance();
- builder.setPosition(position);
-
- return builder.build();
+ @Test
+ public void testForLoop() {
+ Body.BodyBuilder builder = Body.builder(getBody("forLoop"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1");
+ expectedLocals.add("l2#0");
+ expectedLocals.add("l2#1");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1 = 0;\n"
+ + "l2#0 = 0;\n"
+ + "\n"
+ + "label1:\n"
+ + "if l2#0 >= 10 goto label2;\n"
+ + "l2#1 = l2#0 + 1;\n"
+ + "l1 = l1 + 1;\n"
+ + "l2#0 = l2#1 + 1;\n"
+ + "\n"
+ + "goto label1;\n"
+ + "\n"
+ + "label2:\n"
+ + "return l1;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- private Body.BodyBuilder createTrapBody() {
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2, l3, stack3);
-
- builder.setLocals(locals);
-
- FallsThroughStmt stmt1 =
- JavaJimple.newAssignStmt(l1, IntConstant.getInstance(0), noStmtPositionInfo);
- FallsThroughStmt stmt2 =
- JavaJimple.newAssignStmt(l1, IntConstant.getInstance(1), noStmtPositionInfo);
- FallsThroughStmt stmt3 =
- JavaJimple.newAssignStmt(l2, IntConstant.getInstance(2), noStmtPositionInfo);
- FallsThroughStmt stmt4 =
- JavaJimple.newIdentityStmt(stack3, caughtExceptionRef, noStmtPositionInfo);
- FallsThroughStmt stmt5 = JavaJimple.newAssignStmt(l3, l1, noStmtPositionInfo);
- BranchingStmt stmt6 = JavaJimple.newGotoStmt(noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- // build graph
- graph.addBlock(
- Arrays.asList(startingStmt, stmt1, stmt2), Collections.singletonMap(exception, stmt4));
- graph.addBlock(Arrays.asList(stmt4, stmt5, stmt6), Collections.emptyMap());
- graph.addNode(stmt3);
- graph.putEdge(stmt2, stmt3);
- graph.putEdge(stmt3, ret);
- graph.putEdge(stmt6, JGotoStmt.BRANCH_IDX, ret);
-
- graph.setStartingStmt(startingStmt);
-
- return builder;
+ @Test
+ public void testReusedLocals() {
+ Body.BodyBuilder builder = Body.builder(getBody("reusedLocals"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l2#0");
+ expectedLocals.add("l2#1");
+ expectedLocals.add("$stack3");
+ expectedLocals.add("$stack4");
+ expectedLocals.add("$stack5");
+ expectedLocals.add("$stack6");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "$stack3 = staticinvoke ();\n"
+ + "$stack4 = $stack3 cmpl 0.0;\n"
+ + "\n"
+ + "if $stack4 != 0 goto label1;\n"
+ + "l2#0 = staticinvoke (1);\n"
+ + "l1#0 = l2#0;\n"
+ + "\n"
+ + "goto label2;\n"
+ + "\n"
+ + "label1:\n"
+ + "l2#1 = \"\";\n"
+ + "l1#0 = l2#1;\n"
+ + "\n"
+ + "label2:\n"
+ + "$stack5 = ;\n"
+ + "virtualinvoke $stack5.(l1#0);\n"
+ + "l1#1 = null;\n"
+ + "$stack6 = ;\n"
+ + "virtualinvoke $stack6.(l1#1);\n"
+ + "\n"
+ + "return;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
- private Body createExpectedTrapBody() {
-
- MutableStmtGraph graph = new MutableBlockStmtGraph();
- Body.BodyBuilder builder = Body.builder(graph);
- builder.setMethodSignature(methodSignature);
-
- // build set locals
- Set locals = ImmutableUtils.immutableSet(l0, l1, l2, l3, stack3, l1hash1, l1hash2);
-
- builder.setLocals(locals);
-
- Stmt l1hash1assign0Stmt =
- JavaJimple.newAssignStmt(l1hash1, IntConstant.getInstance(0), noStmtPositionInfo);
- FallsThroughStmt l1hash2assign1Stmt =
- JavaJimple.newAssignStmt(l1hash2, IntConstant.getInstance(1), noStmtPositionInfo);
- FallsThroughStmt l2assign2Stmt =
- JavaJimple.newAssignStmt(l2, IntConstant.getInstance(2), noStmtPositionInfo);
- FallsThroughStmt exceptionCatchStmt =
- JavaJimple.newIdentityStmt(stack3, caughtExceptionRef, noStmtPositionInfo);
- Stmt l3assignl1hash2Stmt = JavaJimple.newAssignStmt(l3, l1hash2, noStmtPositionInfo);
- BranchingStmt gotoStmt = JavaJimple.newGotoStmt(noStmtPositionInfo);
- Stmt ret = JavaJimple.newReturnVoidStmt(noStmtPositionInfo);
-
- graph.addBlock(
- Arrays.asList(startingStmt, l1hash1assign0Stmt, l1hash2assign1Stmt),
- Collections.singletonMap(exception, exceptionCatchStmt));
- graph.addBlock(
- Arrays.asList(exceptionCatchStmt, l3assignl1hash2Stmt, gotoStmt), Collections.emptyMap());
- graph.addNode(l2assign2Stmt);
- graph.putEdge(l1hash2assign1Stmt, l2assign2Stmt);
- graph.putEdge(l2assign2Stmt, ret);
- graph.putEdge(gotoStmt, JGotoStmt.BRANCH_IDX, ret);
-
- graph.setStartingStmt(startingStmt);
-
- return builder.build();
+ @Test
+ public void testTraps() {
+ Body.BodyBuilder builder = Body.builder(getBody("traps"), Collections.emptySet());
+ localSplitter.interceptBody(builder, view);
+
+ Set expectedLocals = new HashSet<>();
+ expectedLocals.add("l0");
+ expectedLocals.add("l1#0");
+ expectedLocals.add("l1#1");
+ expectedLocals.add("l2#0");
+ expectedLocals.add("l2#1");
+ expectedLocals.add("l3");
+ expectedLocals.add("$stack4");
+ expectedLocals.add("$stack5");
+ expectedLocals.add("$stack6");
+ expectedLocals.add("$stack7");
+ expectedLocals.add("$stack8");
+
+ assertLocals(expectedLocals, builder);
+
+ String expectedStmts =
+ "l0 := @this: LocalSplitterTarget;\n"
+ + "l1#0 = 1;\n"
+ + "\n"
+ + "label1:\n"
+ + "l1#1 = 2;\n"
+ + "\n"
+ + "label2:\n"
+ + "goto label4;\n"
+ + "\n"
+ + "label3:\n"
+ + "$stack7 := @caughtexception;\n"
+ + "l2#0 = $stack7;\n"
+ + "$stack8 = staticinvoke (l1#1);\n"
+ + "\n"
+ + "return $stack8;\n"
+ + "\n"
+ + "label4:\n"
+ + "l2#1 = \"\";\n"
+ + "\n"
+ + "label5:\n"
+ + "$stack4 = ;\n"
+ + "virtualinvoke $stack4.();\n"
+ + "\n"
+ + "label6:\n"
+ + "goto label8;\n"
+ + "\n"
+ + "label7:\n"
+ + "$stack6 := @caughtexception;\n"
+ + "l3 = $stack6;\n"
+ + "\n"
+ + "return l2#1;\n"
+ + "\n"
+ + "label8:\n"
+ + "$stack5 = staticinvoke (0.0);\n"
+ + "\n"
+ + "return $stack5;\n"
+ + "\n"
+ + " catch java.lang.Throwable from label1 to label2 with label3;\n"
+ + " catch java.lang.Throwable from label5 to label6 with label7;";
+ assertEquals(expectedStmts, builder.getStmtGraph().toString().trim());
}
}