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()); } }