From 9f9400a6dc4d20b98ac0ca5165dbff5ffa9b2bb9 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Sun, 25 Aug 2024 02:02:02 +0200 Subject: [PATCH 01/14] fix ConditionalBranchFolder, forloop jimple order changed, fix try catch blocks --- .../core/graph/MutableBlockStmtGraph.java | 30 +++- .../sootup/core/graph/MutableStmtGraph.java | 5 +- .../java/sootup/core/graph/StmtGraph.java | 168 ++++++++++++++++++ .../interceptors/ConditionalBranchFolder.java | 86 +++++++-- 4 files changed, 268 insertions(+), 21 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java index 119c7d75fb6..ed0f1352eff 100644 --- a/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java @@ -562,7 +562,7 @@ private MutableBasicBlock addBlockInternal( } @Override - public void removeBlock(BasicBlock block) { + public List removeBlock(BasicBlock block) { Pair blockOfPair = stmtToBlock.get(block.getHead()); if (blockOfPair.getRight() != block) { throw new IllegalArgumentException( @@ -573,12 +573,40 @@ public void removeBlock(BasicBlock block) { List stmts = block.getStmts(); stmts.forEach(stmtToBlock::remove); + // remove current block from Predecessor & Successor + blockOf + .getPredecessors() + .forEach( + pred -> { + pred.removeFromSuccessorBlocks(blockOf); + }); + blockOf + .getSuccessors() + .forEach( + succ -> { + succ.removePredecessorBlock(blockOf); + }); + // unlink block from graph blockOf.clearPredecessorBlocks(); blockOf.clearSuccessorBlocks(); blockOf.clearExceptionalSuccessorBlocks(); blocks.remove(blockOf); + return stmts; + } + + @Override + public void replaceStmt(@Nonnull Stmt oldStmt, @Nonnull Stmt newStmt) { + Pair blockPair = stmtToBlock.get(oldStmt); + if (blockPair == null) { + // Stmt does not exist in the graph + throw new IllegalArgumentException("splitStmt does not exist in this block!"); + } + MutableBasicBlock block = blockPair.getRight(); + block.replaceStmt(oldStmt, newStmt); + stmtToBlock.remove(oldStmt); + stmtToBlock.put(newStmt, blockPair); } @Override diff --git a/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java index 48ebfe01a54..69eeee8284a 100644 --- a/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java @@ -45,13 +45,16 @@ public void addNode(@Nonnull Stmt stmt) { addNode(stmt, Collections.emptyMap()); } + /** replace a "oldStmt" with "newStmt" in the StmtGraph */ + public abstract void replaceStmt(@Nonnull Stmt oldStmt, @Nonnull Stmt newStmt); + /** inserts a "stmt" with exceptional flows "traps" into the StmtGraph */ public abstract void addNode(@Nonnull Stmt stmt, @Nonnull Map traps); /** creates a whole BasicBlock with the details from the parameters */ public abstract void addBlock(@Nonnull List stmts, @Nonnull Map traps); - public abstract void removeBlock(BasicBlock block); + public abstract List removeBlock(BasicBlock block); /** * creates a whole BasicBlock which contains the sequence of (n-1)*fallsthrough()-stmt + optional diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 3a484d7f90b..643be8a17e3 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -308,6 +308,170 @@ public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull return null; } + /** + * Finds and returns all possible paths between two blocks in a control flow graph. Look for a + * path of blocks in graph. ** Note: This path handle cycle ** (and this property implies + * uniqueness.). The path returned includes from and to. + * + * @param from start point for the path. + * @param to end point for the path. + * @return a list of paths, where each path is represented as a list of {@link MutableBasicBlock}. + * Each path in the list starts with the 'from' block and ends with the 'to' block. If no + * paths exist between the two blocks, an empty list is returned. + * @implNote This method performs a depth-first search (DFS) to explore all possible paths between + * the two blocks. + */ + public List> getAllPaths( + @Nonnull MutableBasicBlock from, @Nonnull MutableBasicBlock to) { + List> allPaths = new ArrayList<>(); + List currentPath = new ArrayList<>(); + Set visited = new HashSet<>(); + dfs(from, to, visited, currentPath, allPaths); + return allPaths; + } + + // Helper function to perform DFS and find all paths between current and target + private void dfs( + MutableBasicBlock current, + MutableBasicBlock target, + Set visited, + List currentPath, + List> allPaths) { + // Add the current block to the path and mark it as visited + currentPath.add(current); + visited.add(current); + + // If we reach the target block, add the current path to allPaths + if (current.equals(target)) { + allPaths.add(new ArrayList<>(currentPath)); + } else { + // Explore all successors of the current block + for (MutableBasicBlock successor : current.getSuccessors()) { + if (!visited.contains(successor)) { + dfs(successor, target, visited, currentPath, allPaths); + } + } + } + + // Backtrack: remove the current block from the path and mark it as unvisited + currentPath.remove(currentPath.size() - 1); + visited.remove(current); + } + + /** + * Finds and returns all possible paths from start block in a control flow graph. Note: This path + * handle cycle ** (and this property implies uniqueness.). The path returned includes start + * block. + * + * @param start start point for the path. + * @return a list of paths, where each path is represented as a list of {@link MutableBasicBlock}. + * Each path in the list starts with the 'start' block. If no paths exist, an empty list is + * returned. + * @implNote This method performs a depth-first search (DFS) to explore all possible paths from + * start block. + */ + public List> getAllPaths(@Nonnull MutableBasicBlock start) { + List> allPaths = new ArrayList<>(); + List currentPath = new ArrayList<>(); + Set visited = new HashSet<>(); + dfs(start, currentPath, visited, allPaths); + return allPaths; + } + + // Helper function to perform DFS to explore all paths and find all paths from current block, + // considering loops + private void dfs( + MutableBasicBlock current, + List currentPath, + Set visited, + List> allPaths) { + // Add current block to the path and mark it as visited + currentPath.add(current); + visited.add(current); + + // If the current block has no successors, this is an end path + if (current.getSuccessors().isEmpty()) { + allPaths.add(new ArrayList<>(currentPath)); // Add a copy of the current path to allPaths + } else { + // Explore all successors + for (MutableBasicBlock successor : current.getSuccessors()) { + // Check if the successor is already in the current path (indicating a loop) + if (!visited.contains(successor)) { + dfs(successor, currentPath, visited, allPaths); + } else { + // Optional: Handle loops by recording the path up to the loop, if needed + allPaths.add(new ArrayList<>(currentPath)); + } + } + } + + // Backtrack: remove the current block from the path and visited set before going back + currentPath.remove(currentPath.size() - 1); + visited.remove(current); + } + + /** + * Finds the first merge point between two starting blocks in a control flow graph (CFG). This + * method performs a breadth-first search (BFS) starting from two distinct blocks (`start1` and + * `start2`) and attempts to find the first common block (merge point) that can be reached from + * both starting points. The method returns the first merge point encountered during the + * traversal, or an empty set if no merge point is found. + * + * @param start1 The starting block for the first path in the CFG. + * @param start2 The starting block for the second path in the CFG. + * @return A set containing the first merge point block, or an empty set if no merge point is + * found. The set will contain a single `MutableBasicBlock` if a merge point is found. + */ + public List findFirstMergePoint( + @Nonnull MutableBasicBlock start1, @Nonnull MutableBasicBlock start2) { + // Step 1: Initialize two sets to store the visited blocks from both start points + Set visitedFromStart1 = new HashSet<>(); + Set visitedFromStart2 = new HashSet<>(); + + // Step 2: Initialize two queues for BFS traversal from both nodes + Queue queue1 = new LinkedList<>(); + Queue queue2 = new LinkedList<>(); + + queue1.add(start1); + queue2.add(start2); + visitedFromStart1.add(start1); + visitedFromStart2.add(start2); + + // Step 3: Traverse from both nodes simultaneously + while (!queue1.isEmpty() || !queue2.isEmpty()) { + if (!queue1.isEmpty()) { + MutableBasicBlock current1 = queue1.poll(); + if (visitedFromStart2.contains(current1)) { + return Collections.singletonList(current1); // Found the first merge point + } + for (MutableBasicBlock successor : current1.getSuccessors()) { + // handle cycles in CFG + if (!visitedFromStart1.contains(successor)) { + visitedFromStart1.add(successor); + queue1.add(successor); + } + } + } + + if (!queue2.isEmpty()) { + MutableBasicBlock current2 = queue2.poll(); + if (visitedFromStart1.contains(current2)) { + return Collections.singletonList(current2); // Found the first merge point + } + for (MutableBasicBlock successor : current2.getSuccessors()) { + // handle cycles in CFG + if (!visitedFromStart2.contains(successor)) { + visitedFromStart2.add(successor); + queue2.add(successor); + } + } + } + } + + // Step 4: If no merge point is found, return null + return Collections.emptyList(); + } + @Override public boolean equals(Object o) { if (o == this) { @@ -356,6 +520,10 @@ public Iterator iterator() { public List getBranchTargetsOf(BranchingStmt fromStmt) { final List successors = successors(fromStmt); if (fromStmt instanceof JIfStmt) { + // Conditional Branch Folder removes neverReachedSucessor Target + if (successors.size() == 1) { + return Collections.singletonList(successors.get(0)); + } // remove the first successor as if its a fallsthrough stmt and not a branch target return Collections.singletonList(successors.get(1)); } diff --git a/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java b/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java index 9a7d4f03ed6..54051ac554c 100644 --- a/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java +++ b/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java @@ -24,11 +24,12 @@ import com.google.common.collect.Lists; import java.util.*; import javax.annotation.Nonnull; +import sootup.core.graph.MutableBasicBlock; import sootup.core.graph.MutableStmtGraph; import sootup.core.graph.StmtGraph; +import sootup.core.jimple.Jimple; import sootup.core.jimple.common.constant.*; -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.JIfStmt; import sootup.core.jimple.common.stmt.Stmt; import sootup.core.model.Body; @@ -50,7 +51,14 @@ public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) final MutableStmtGraph stmtGraph = builder.getStmtGraph(); - for (Stmt stmt : Lists.newArrayList(stmtGraph.getNodes())) { + ArrayList stmtsList = Lists.newArrayList(stmtGraph.getNodes()); + List removedStmts = new ArrayList<>(); + for (Stmt stmt : stmtsList) { + // Statements which were removed while removing nodes + if (removedStmts.contains(stmt)) { + continue; + } + if (!(stmt instanceof JIfStmt)) { continue; } @@ -85,7 +93,11 @@ else if (evaluatedCondition instanceof DoubleConstant) { continue; } - final List ifSuccessors = stmtGraph.successors(ifStmt); + List ifSuccessors = stmtGraph.successors(ifStmt); + // The successors of IfStmt have true branch at index 0 & false branch at index 1. + // However, in other parts of code, TRUE_BRANCH_IDX is defined as 1 & FALSE_BRANCH_IDX as 0. + // To maintain consistency, we need to reverse the order of the successors. + ifSuccessors = Lists.reverse(ifSuccessors); final Stmt tautologicSuccessor; final Stmt neverReachedSucessor; @@ -102,28 +114,64 @@ else if (evaluatedCondition instanceof DoubleConstant) { neverReachedSucessor = ifSuccessors.get(JIfStmt.FALSE_BRANCH_IDX); } - // link previous stmt with always-reached successor of the if-Stmt - for (Stmt predecessor : stmtGraph.predecessors(ifStmt)) { - List successorIdxList = stmtGraph.removeEdge(predecessor, ifStmt); - - if (predecessor instanceof FallsThroughStmt) { - FallsThroughStmt fallsThroughPred = (FallsThroughStmt) predecessor; - for (Integer successorIdx : successorIdxList) { - stmtGraph.putEdge(fallsThroughPred, tautologicSuccessor); - } + MutableBasicBlock ifStmtBlock = (MutableBasicBlock) stmtGraph.getBlockOf(ifStmt); + MutableBasicBlock tautologicSuccessorBlock = + (MutableBasicBlock) stmtGraph.getBlockOf(tautologicSuccessor); + MutableBasicBlock neverReachedSucessorBlock = + (MutableBasicBlock) stmtGraph.getBlockOf(neverReachedSucessor); + List firstMergePoint = + stmtGraph.findFirstMergePoint(tautologicSuccessorBlock, neverReachedSucessorBlock); + // No merge point found + assert firstMergePoint.size() <= 1; + if (firstMergePoint.isEmpty()) { + // get all paths from ifStmt and remove the paths which contains neverReachedSucessorBlock + List> allPaths = stmtGraph.getAllPaths(ifStmtBlock); + System.out.println("No merge point: " + allPaths); + Set blocksToRemove = new HashSet<>(); + allPaths.stream() + .filter(path -> path.contains(neverReachedSucessorBlock)) + .forEach(path -> blocksToRemove.addAll(path)); + System.out.println("No merge point after filtering paths that contain NR: " + allPaths); + // we will remove ifStmtBlock at the end + blocksToRemove.remove(ifStmtBlock); + for (MutableBasicBlock block : blocksToRemove) { + List stmts = stmtGraph.removeBlock(block); + removedStmts.addAll(stmts); + } + } else { + MutableBasicBlock mergePoint = firstMergePoint.get(0); + if (mergePoint == neverReachedSucessorBlock) { + List successorIdxList = stmtGraph.removeEdge(ifStmt, neverReachedSucessor); } else { - // should not be anything else than BranchingStmt.. just Stmt can have no successor - BranchingStmt branchingPred = (BranchingStmt) predecessor; - for (Integer successorIdx : successorIdxList) { - stmtGraph.putEdge(branchingPred, successorIdx, tautologicSuccessor); + List> allPaths = stmtGraph.getAllPaths(ifStmtBlock, mergePoint); + System.out.println("If to Merge point: " + allPaths); + Set blocksToRemove = new HashSet<>(); + allPaths.stream() + .filter(path -> path.contains(neverReachedSucessorBlock)) + .forEach(path -> blocksToRemove.addAll(path)); + System.out.println("Merge point, After filtering paths that contain NR: " + allPaths); + // we will remove ifStmtBlock at the end + blocksToRemove.remove(ifStmtBlock); + blocksToRemove.remove(mergePoint); + for (MutableBasicBlock block : blocksToRemove) { + List stmts = stmtGraph.removeBlock(block); + removedStmts.addAll(stmts); } } } - stmtGraph.removeNode(ifStmt, false); + // replace ifStmt block + JGotoStmt gotoStmt = Jimple.newGotoStmt(ifStmt.getPositionInfo()); + // ifStmtBlock.replaceStmt(ifStmt, gotoStmt); + + // ifStmtBlock.replaceSuccessorBlock(neverReachedSucessorBlock, null); + stmtGraph.replaceStmt(ifStmt, gotoStmt); - pruneExclusivelyReachableStmts(builder, neverReachedSucessor); + // stmtGraph.removeStmt(ifStmt); + removedStmts.add(ifStmt); + removedStmts.add(gotoStmt); } + System.out.println("New StatementGraph" + stmtGraph); } private void pruneExclusivelyReachableStmts( From f3141e3d7aeb3ee3fc07a61fc4b05387d65825f2 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Wed, 28 Aug 2024 22:45:34 +0200 Subject: [PATCH 02/14] better approach --- .../core/graph/MutableBlockStmtGraph.java | 36 ++-- .../sootup/core/graph/MutableStmtGraph.java | 4 +- .../java/sootup/core/graph/StmtGraph.java | 168 ------------------ .../interceptors/ConditionalBranchFolder.java | 145 +-------------- 4 files changed, 28 insertions(+), 325 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java index ed0f1352eff..4fc5a38c092 100644 --- a/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/MutableBlockStmtGraph.java @@ -562,7 +562,7 @@ private MutableBasicBlock addBlockInternal( } @Override - public List removeBlock(BasicBlock block) { + public void removeBlock(BasicBlock block) { Pair blockOfPair = stmtToBlock.get(block.getHead()); if (blockOfPair.getRight() != block) { throw new IllegalArgumentException( @@ -573,27 +573,11 @@ public List removeBlock(BasicBlock block) { List stmts = block.getStmts(); stmts.forEach(stmtToBlock::remove); - // remove current block from Predecessor & Successor - blockOf - .getPredecessors() - .forEach( - pred -> { - pred.removeFromSuccessorBlocks(blockOf); - }); - blockOf - .getSuccessors() - .forEach( - succ -> { - succ.removePredecessorBlock(blockOf); - }); - // unlink block from graph blockOf.clearPredecessorBlocks(); blockOf.clearSuccessorBlocks(); blockOf.clearExceptionalSuccessorBlocks(); - blocks.remove(blockOf); - return stmts; } @Override @@ -1304,6 +1288,24 @@ protected void putEdge_internal(@Nonnull Stmt stmtA, int succesorIdx, @Nonnull S } } + @Override + public void unLinkNodes(@Nonnull Stmt from, @Nonnull Stmt to) { + Pair blockOfFromPair = stmtToBlock.get(from); + if (blockOfFromPair == null) { + throw new IllegalArgumentException("stmt '" + from + "' does not exist in this StmtGraph!"); + } + MutableBasicBlock blockOfFrom = blockOfFromPair.getRight(); + Pair blockOfToPair = stmtToBlock.get(to); + if (blockOfToPair == null) { + throw new IllegalArgumentException("stmt '" + to + "' does not exist in this StmtGraph!"); + } + MutableBasicBlock blockOfTo = blockOfToPair.getRight(); + + // Unlink 2 blocks + blockOfFrom.removeFromSuccessorBlocks(blockOfTo); + blockOfTo.removePredecessorBlock(blockOfFrom); + } + @Override public List removeEdge(@Nonnull Stmt from, @Nonnull Stmt to) { Pair blockOfFromPair = stmtToBlock.get(from); diff --git a/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java index 69eeee8284a..3a32457aef4 100644 --- a/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/MutableStmtGraph.java @@ -54,7 +54,7 @@ public void addNode(@Nonnull Stmt stmt) { /** creates a whole BasicBlock with the details from the parameters */ public abstract void addBlock(@Nonnull List stmts, @Nonnull Map traps); - public abstract List removeBlock(BasicBlock block); + public abstract void removeBlock(BasicBlock block); /** * creates a whole BasicBlock which contains the sequence of (n-1)*fallsthrough()-stmt + optional @@ -110,6 +110,8 @@ public void setEdges(@Nonnull BranchingStmt from, @Nonnull Stmt... targets) { setEdges(from, Arrays.asList(targets)); } + public abstract void unLinkNodes(@Nonnull Stmt from, @Nonnull Stmt to); + /** * removes the current outgoing flows of "from" to "to" * diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 643be8a17e3..3a484d7f90b 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -308,170 +308,6 @@ public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull return null; } - /** - * Finds and returns all possible paths between two blocks in a control flow graph. Look for a - * path of blocks in graph. ** Note: This path handle cycle ** (and this property implies - * uniqueness.). The path returned includes from and to. - * - * @param from start point for the path. - * @param to end point for the path. - * @return a list of paths, where each path is represented as a list of {@link MutableBasicBlock}. - * Each path in the list starts with the 'from' block and ends with the 'to' block. If no - * paths exist between the two blocks, an empty list is returned. - * @implNote This method performs a depth-first search (DFS) to explore all possible paths between - * the two blocks. - */ - public List> getAllPaths( - @Nonnull MutableBasicBlock from, @Nonnull MutableBasicBlock to) { - List> allPaths = new ArrayList<>(); - List currentPath = new ArrayList<>(); - Set visited = new HashSet<>(); - dfs(from, to, visited, currentPath, allPaths); - return allPaths; - } - - // Helper function to perform DFS and find all paths between current and target - private void dfs( - MutableBasicBlock current, - MutableBasicBlock target, - Set visited, - List currentPath, - List> allPaths) { - // Add the current block to the path and mark it as visited - currentPath.add(current); - visited.add(current); - - // If we reach the target block, add the current path to allPaths - if (current.equals(target)) { - allPaths.add(new ArrayList<>(currentPath)); - } else { - // Explore all successors of the current block - for (MutableBasicBlock successor : current.getSuccessors()) { - if (!visited.contains(successor)) { - dfs(successor, target, visited, currentPath, allPaths); - } - } - } - - // Backtrack: remove the current block from the path and mark it as unvisited - currentPath.remove(currentPath.size() - 1); - visited.remove(current); - } - - /** - * Finds and returns all possible paths from start block in a control flow graph. Note: This path - * handle cycle ** (and this property implies uniqueness.). The path returned includes start - * block. - * - * @param start start point for the path. - * @return a list of paths, where each path is represented as a list of {@link MutableBasicBlock}. - * Each path in the list starts with the 'start' block. If no paths exist, an empty list is - * returned. - * @implNote This method performs a depth-first search (DFS) to explore all possible paths from - * start block. - */ - public List> getAllPaths(@Nonnull MutableBasicBlock start) { - List> allPaths = new ArrayList<>(); - List currentPath = new ArrayList<>(); - Set visited = new HashSet<>(); - dfs(start, currentPath, visited, allPaths); - return allPaths; - } - - // Helper function to perform DFS to explore all paths and find all paths from current block, - // considering loops - private void dfs( - MutableBasicBlock current, - List currentPath, - Set visited, - List> allPaths) { - // Add current block to the path and mark it as visited - currentPath.add(current); - visited.add(current); - - // If the current block has no successors, this is an end path - if (current.getSuccessors().isEmpty()) { - allPaths.add(new ArrayList<>(currentPath)); // Add a copy of the current path to allPaths - } else { - // Explore all successors - for (MutableBasicBlock successor : current.getSuccessors()) { - // Check if the successor is already in the current path (indicating a loop) - if (!visited.contains(successor)) { - dfs(successor, currentPath, visited, allPaths); - } else { - // Optional: Handle loops by recording the path up to the loop, if needed - allPaths.add(new ArrayList<>(currentPath)); - } - } - } - - // Backtrack: remove the current block from the path and visited set before going back - currentPath.remove(currentPath.size() - 1); - visited.remove(current); - } - - /** - * Finds the first merge point between two starting blocks in a control flow graph (CFG). This - * method performs a breadth-first search (BFS) starting from two distinct blocks (`start1` and - * `start2`) and attempts to find the first common block (merge point) that can be reached from - * both starting points. The method returns the first merge point encountered during the - * traversal, or an empty set if no merge point is found. - * - * @param start1 The starting block for the first path in the CFG. - * @param start2 The starting block for the second path in the CFG. - * @return A set containing the first merge point block, or an empty set if no merge point is - * found. The set will contain a single `MutableBasicBlock` if a merge point is found. - */ - public List findFirstMergePoint( - @Nonnull MutableBasicBlock start1, @Nonnull MutableBasicBlock start2) { - // Step 1: Initialize two sets to store the visited blocks from both start points - Set visitedFromStart1 = new HashSet<>(); - Set visitedFromStart2 = new HashSet<>(); - - // Step 2: Initialize two queues for BFS traversal from both nodes - Queue queue1 = new LinkedList<>(); - Queue queue2 = new LinkedList<>(); - - queue1.add(start1); - queue2.add(start2); - visitedFromStart1.add(start1); - visitedFromStart2.add(start2); - - // Step 3: Traverse from both nodes simultaneously - while (!queue1.isEmpty() || !queue2.isEmpty()) { - if (!queue1.isEmpty()) { - MutableBasicBlock current1 = queue1.poll(); - if (visitedFromStart2.contains(current1)) { - return Collections.singletonList(current1); // Found the first merge point - } - for (MutableBasicBlock successor : current1.getSuccessors()) { - // handle cycles in CFG - if (!visitedFromStart1.contains(successor)) { - visitedFromStart1.add(successor); - queue1.add(successor); - } - } - } - - if (!queue2.isEmpty()) { - MutableBasicBlock current2 = queue2.poll(); - if (visitedFromStart1.contains(current2)) { - return Collections.singletonList(current2); // Found the first merge point - } - for (MutableBasicBlock successor : current2.getSuccessors()) { - // handle cycles in CFG - if (!visitedFromStart2.contains(successor)) { - visitedFromStart2.add(successor); - queue2.add(successor); - } - } - } - } - - // Step 4: If no merge point is found, return null - return Collections.emptyList(); - } - @Override public boolean equals(Object o) { if (o == this) { @@ -520,10 +356,6 @@ public Iterator iterator() { public List getBranchTargetsOf(BranchingStmt fromStmt) { final List successors = successors(fromStmt); if (fromStmt instanceof JIfStmt) { - // Conditional Branch Folder removes neverReachedSucessor Target - if (successors.size() == 1) { - return Collections.singletonList(successors.get(0)); - } // remove the first successor as if its a fallsthrough stmt and not a branch target return Collections.singletonList(successors.get(1)); } diff --git a/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java b/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java index 54051ac554c..921d5576e42 100644 --- a/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java +++ b/sootup.java.core/src/main/java/sootup/java/core/interceptors/ConditionalBranchFolder.java @@ -24,9 +24,7 @@ import com.google.common.collect.Lists; import java.util.*; import javax.annotation.Nonnull; -import sootup.core.graph.MutableBasicBlock; import sootup.core.graph.MutableStmtGraph; -import sootup.core.graph.StmtGraph; import sootup.core.jimple.Jimple; import sootup.core.jimple.common.constant.*; import sootup.core.jimple.common.stmt.JGotoStmt; @@ -51,14 +49,7 @@ public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) final MutableStmtGraph stmtGraph = builder.getStmtGraph(); - ArrayList stmtsList = Lists.newArrayList(stmtGraph.getNodes()); - List removedStmts = new ArrayList<>(); - for (Stmt stmt : stmtsList) { - // Statements which were removed while removing nodes - if (removedStmts.contains(stmt)) { - continue; - } - + for (Stmt stmt : Lists.newArrayList(stmtGraph.getNodes())) { if (!(stmt instanceof JIfStmt)) { continue; } @@ -114,138 +105,14 @@ else if (evaluatedCondition instanceof DoubleConstant) { neverReachedSucessor = ifSuccessors.get(JIfStmt.FALSE_BRANCH_IDX); } - MutableBasicBlock ifStmtBlock = (MutableBasicBlock) stmtGraph.getBlockOf(ifStmt); - MutableBasicBlock tautologicSuccessorBlock = - (MutableBasicBlock) stmtGraph.getBlockOf(tautologicSuccessor); - MutableBasicBlock neverReachedSucessorBlock = - (MutableBasicBlock) stmtGraph.getBlockOf(neverReachedSucessor); - List firstMergePoint = - stmtGraph.findFirstMergePoint(tautologicSuccessorBlock, neverReachedSucessorBlock); - // No merge point found - assert firstMergePoint.size() <= 1; - if (firstMergePoint.isEmpty()) { - // get all paths from ifStmt and remove the paths which contains neverReachedSucessorBlock - List> allPaths = stmtGraph.getAllPaths(ifStmtBlock); - System.out.println("No merge point: " + allPaths); - Set blocksToRemove = new HashSet<>(); - allPaths.stream() - .filter(path -> path.contains(neverReachedSucessorBlock)) - .forEach(path -> blocksToRemove.addAll(path)); - System.out.println("No merge point after filtering paths that contain NR: " + allPaths); - // we will remove ifStmtBlock at the end - blocksToRemove.remove(ifStmtBlock); - for (MutableBasicBlock block : blocksToRemove) { - List stmts = stmtGraph.removeBlock(block); - removedStmts.addAll(stmts); - } - } else { - MutableBasicBlock mergePoint = firstMergePoint.get(0); - if (mergePoint == neverReachedSucessorBlock) { - List successorIdxList = stmtGraph.removeEdge(ifStmt, neverReachedSucessor); - } else { - List> allPaths = stmtGraph.getAllPaths(ifStmtBlock, mergePoint); - System.out.println("If to Merge point: " + allPaths); - Set blocksToRemove = new HashSet<>(); - allPaths.stream() - .filter(path -> path.contains(neverReachedSucessorBlock)) - .forEach(path -> blocksToRemove.addAll(path)); - System.out.println("Merge point, After filtering paths that contain NR: " + allPaths); - // we will remove ifStmtBlock at the end - blocksToRemove.remove(ifStmtBlock); - blocksToRemove.remove(mergePoint); - for (MutableBasicBlock block : blocksToRemove) { - List stmts = stmtGraph.removeBlock(block); - removedStmts.addAll(stmts); - } - } - } - - // replace ifStmt block + // remove edge from ifStmt to neverReachedSucessor + stmtGraph.unLinkNodes(ifStmt, neverReachedSucessor); + // replace ifStmt block by gotoStmt JGotoStmt gotoStmt = Jimple.newGotoStmt(ifStmt.getPositionInfo()); - // ifStmtBlock.replaceStmt(ifStmt, gotoStmt); - - // ifStmtBlock.replaceSuccessorBlock(neverReachedSucessorBlock, null); stmtGraph.replaceStmt(ifStmt, gotoStmt); - // stmtGraph.removeStmt(ifStmt); - removedStmts.add(ifStmt); - removedStmts.add(gotoStmt); - } - System.out.println("New StatementGraph" + stmtGraph); - } - - private void pruneExclusivelyReachableStmts( - @Nonnull Body.BodyBuilder builder, @Nonnull Stmt fallsThroughStmt) { - - MutableStmtGraph stmtGraph = builder.getStmtGraph(); - Set reachedBranchingStmts = new HashSet<>(); - Deque q = new ArrayDeque<>(); - - q.addFirst(fallsThroughStmt); - // stmts we want to remove - // remove all now unreachable stmts from "true"-block - while (!q.isEmpty()) { - Stmt itStmt = q.pollFirst(); - if (itStmt.branches()) { - // reachable branching stmts that may or may not branch to another reachable stmt is all we - // are actually interested in - reachedBranchingStmts.add(itStmt); - } - if (stmtGraph.containsNode(itStmt)) { - final List predecessors = stmtGraph.predecessors(itStmt); - if (predecessors.size() <= 1) { - q.addAll(stmtGraph.successors(itStmt)); - } - } - } - // now iterate again and remove if possible: ie predecessor.size() < 1 - q.addFirst(fallsThroughStmt); - while (!q.isEmpty()) { - Stmt itStmt = q.pollFirst(); - if (stmtGraph.containsNode(itStmt)) { - // hint: predecessor could also be already removed - if (isExclusivelyReachable(stmtGraph, itStmt, reachedBranchingStmts)) { - q.addAll(stmtGraph.successors(itStmt)); - stmtGraph.removeNode(itStmt, false); - builder.removeDefLocalsOf(itStmt); - } - } - } - } - - /** reachedStmts contains all reached Stmts from entrypoint which ALSO do branch! */ - private boolean isExclusivelyReachable( - @Nonnull StmtGraph graph, @Nonnull Stmt stmt, @Nonnull Set reachedStmts) { - final List predecessors = graph.predecessors(stmt); - final int predecessorSize = predecessors.size(); - int amount = predecessorSize; - if (predecessorSize <= 1) { - // we already reached this stmt somehow via reachable stmts so at least one predecessor was - // reachable which makes it exclusively reachable if there are no other ingoing flows - // hint: <= because a predecessor could already be removed - return true; - } - for (Stmt predecessor : predecessors) { - if (predecessor.fallsThrough()) { - if (predecessor instanceof JIfStmt) { - final List predsSuccessors = graph.successors(predecessor); - if (predsSuccessors.size() > 0 && predsSuccessors.get(0) == stmt) { - // TODO: hint: possible problem occurs with partial removed targets as they change the - // idx positions.. - amount--; - continue; - } - } else { - // "usual" fallsthrough - amount--; - continue; - } - } - // was a branching predecessor reachable? - if (reachedStmts.contains(predecessor)) { - amount--; - } + // Call Unreachable Code Eliminator for pruning unreachable blocks + new UnreachableCodeEliminator().interceptBody(builder, view); } - return amount == 0; } } From 6a8c54f8eca7cb90af147cdbe74075ba5d4d2c49 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Thu, 29 Aug 2024 11:55:22 +0200 Subject: [PATCH 03/14] fix and added test cases --- .../ConditionalBranchFolderTest.class | Bin 0 -> 573 bytes .../bugfixes/ConditionalBranchFolderTest.java | 16 ++++ .../ConditionalBranchFolderTest.java | 79 ++++++++++++++++-- 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 shared-test-resources/bugfixes/ConditionalBranchFolderTest.class create mode 100644 shared-test-resources/bugfixes/ConditionalBranchFolderTest.java diff --git a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class new file mode 100644 index 0000000000000000000000000000000000000000..a9d857222a155aa2af18acea8e856f9a4b3ec2c6 GIT binary patch literal 573 zcmaKp%SyvQ6o&t4QkLurb8V~Cz4DBmp+ERfNNbS zxbOjdCK1m>5d<-ibI$zppD!mfZyztO0Cuq8B7>|A%R>$W3`6JqiZ?>8x{aguxd=4F zz?M`}Z!=^U>!*1*$lGu|6yW``x2_W{dgO}w)Ku9|K{9F_$5QE*js@>+Iv8ZIn>uQwL)VPAGIzO@weTK?C$;$HCOk zht_ONGx&Rv>PRgk#lzj0tKh5|g&h%}h=j(q_M(npDD6un4*R_}>AW41lGVW~L$RfK zaB;vdzg4#t_2WP^rLk21A+MO9LJdBhvUxIK=A0?c&}xPz-5zr{Gc1tV2RGI^Xb*-~wiYQxT?>?OEVXK7^-JBKpG j3T#Z$z1C>24iC#HVjU$^$(e`sjxsCR1sbT9V&*>qXiIZ0 literal 0 HcmV?d00001 diff --git a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java new file mode 100644 index 00000000000..9eb9192a020 --- /dev/null +++ b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java @@ -0,0 +1,16 @@ +public class ConditionalBranchFolderTest { + void tc1() { + boolean bool = true; + if (!bool) { + System.out.println("False 1"); + } else if (bool) { + if (bool){ + System.out.println("lets see"); + } + System.out.println("mid"); + } + if (!bool) { + System.out.println("False 2"); + } + } +} \ No newline at end of file diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java index 4de56f3674a..33f9a361e59 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java @@ -1,14 +1,18 @@ package sootup.java.bytecode.interceptors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import categories.TestCategories; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import sootup.core.graph.MutableStmtGraph; +import sootup.core.inputlocation.AnalysisInputLocation; import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.Local; import sootup.core.jimple.basic.StmtPositionInfo; @@ -19,12 +23,15 @@ import sootup.core.jimple.common.stmt.JIfStmt; import sootup.core.jimple.common.stmt.Stmt; import sootup.core.model.Body; +import sootup.core.model.SourceType; import sootup.core.signatures.MethodSignature; import sootup.core.signatures.PackageName; import sootup.core.util.ImmutableUtils; import sootup.core.util.Utils; +import sootup.java.bytecode.inputlocation.PathBasedAnalysisInputLocation; import sootup.java.core.JavaIdentifierFactory; import sootup.java.core.interceptors.ConditionalBranchFolder; +import sootup.java.core.interceptors.CopyPropagator; import sootup.java.core.language.JavaJimple; import sootup.java.core.types.JavaClassType; import sootup.java.core.views.JavaView; @@ -33,6 +40,9 @@ @Tag(TestCategories.JAVA_8_CATEGORY) public class ConditionalBranchFolderTest { + Path classFilePath = + Paths.get("../shared-test-resources/bugfixes/ConditionalBranchFolderTest.class"); + /** * Tests the correct deletion of an if-statement with a constant condition. Transforms from * @@ -46,9 +56,17 @@ public class ConditionalBranchFolderTest { public void testUnconditionalBranching() { Body.BodyBuilder builder = createBodyBuilder(0); new ConditionalBranchFolder().interceptBody(builder, new JavaView(Collections.emptyList())); + Body body = builder.build(); assertEquals( - Arrays.asList("a = \"str\"", "b = \"str\"", "return a"), - Utils.bodyStmtsAsStrings(builder.build())); + Stream.of( + "java.lang.String a, b", + "a = \"str\"", + "b = \"str\"", + "goto label1", + "label1:", + "return b") + .collect(Collectors.toList()), + Utils.filterJimple(body.toString())); } /** @@ -65,8 +83,15 @@ public void testConditionalBranching() { Body processedBody = builder.build(); assertEquals( - Arrays.asList("a = \"str\"", "b = \"different string\"", "return b"), - Utils.bodyStmtsAsStrings(processedBody)); + Stream.of( + "java.lang.String a, b", + "a = \"str\"", + "b = \"different string\"", + "goto label1", + "label1:", + "return a") + .collect(Collectors.toList()), + Utils.filterJimple(processedBody.toString())); } @Test @@ -79,6 +104,46 @@ public void testConditionalBranchingWithNoConclusiveIfCondition() { assertEquals(Utils.bodyStmtsAsStrings(originalBody), Utils.bodyStmtsAsStrings(processedBody)); } + @Test + public void testConditionalBranchFolderWithMultipleBranches() { + AnalysisInputLocation inputLocation = + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation + .ClassFileBasedAnalysisInputLocation( + classFilePath, + "", + SourceType.Application, + Arrays.asList(new CopyPropagator(), new ConditionalBranchFolder())); + JavaView view = new JavaView(Collections.singletonList(inputLocation)); + + final MethodSignature methodSignature = + view.getIdentifierFactory() + .getMethodSignature( + "ConditionalBranchFolderTest", "tc1", "void", Collections.emptyList()); + Body body = view.getMethod(methodSignature).get().getBody(); + assertFalse(body.getStmts().isEmpty()); + assertEquals( + Stream.of( + "ConditionalBranchFolderTest this", + "unknown $stack3, $stack4, l1", + "this := @this: ConditionalBranchFolderTest", + "l1 = 1", + "goto label1", + "label1:", + "goto label2", + "label2:", + "goto label3", + "label3:", + "$stack4 = ", + "virtualinvoke $stack4.(\"lets see\")", + "$stack3 = ", + "virtualinvoke $stack3.(\"mid\")", + "goto label4", + "label4:", + "return") + .collect(Collectors.toList()), + Utils.filterJimple(body.toString())); + } + /** * Generates the correct test {@link Body} for the corresponding test case. * From a8480137c2438bf9c83510e9d7f44d56530db110 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Tue, 3 Sep 2024 15:35:48 +0200 Subject: [PATCH 04/14] added test case for ConditionalBranchFolder --- .../ConditionalBranchFolderTest.class | Bin 573 -> 1081 bytes .../bugfixes/ConditionalBranchFolderTest.java | 39 ++++++++++++++++++ .../java/sootup/core/graph/StmtGraph.java | 25 ++++++----- .../ConditionalBranchFolderTest.java | 20 +++++++++ 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class index a9d857222a155aa2af18acea8e856f9a4b3ec2c6..6acf22e302b393cc480a526b2f85c8f1e533a84d 100644 GIT binary patch delta 646 zcmb7>KTq3G6vfZA4KYqo(f|c&;yB??Ax&^0VS`G5s!p(wh0&O(a)}%i0-{r9MwE%Y z5*rc>pn_UEJgix;;U>_BVU^_*m_)xIx!(+<)>g|8_Nq-ksF|1+3{;z6 z-L-ln*fa5#XVm9z7#)PWHJ2?Da{|QhPGGM$qTNt|{QBwBYD#Yfivq@SH&!|;QvOhY z#!G@|;nUApWR>BYGUzMZKrWdGN{p(la$x@XXy-ycNvZQ&ytc>P0T_>XCCONv#rvMx z4se_ delta 124 zcmdnVv6qGG)W2Q(7#J8F8I&h-y_lTJ=sWocqoJKN0~1h?0Z1}3urja#Nm(FI7DzJ! zX;!W642&Cr3`PbyAPJHNu^AZTAxbA3Gr2R$O|E9rmrwwTax$>|VGv~GXXgegQUnSx HF(?55L(C9T diff --git a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java index 9eb9192a020..a0e0296a29c 100644 --- a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java +++ b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.java @@ -13,4 +13,43 @@ void tc1() { System.out.println("False 2"); } } + + void tc2() { + boolean bool = true; + try { + if (bool) { + throw new Exception("True"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void tc3() { + boolean bool = false; + try { + if (bool) { + throw new Exception("True"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void tc4() { + int x = 10; + boolean bool = true; + if(x > 5) { + try { + System.out.println("Try Block"); + if (bool) { + System.out.println("True inside Try"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + System.out.println(""); + } + } \ No newline at end of file diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 3a484d7f90b..f3d6e259774 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -579,26 +579,25 @@ private void updateFollowingBlocks(BasicBlock currentBlock) { // create the longest FallsThroughStmt sequence possible final BasicBlock successorBlock = successors.get(i); - BasicBlock leaderOfFallsthroughBlocks = successorBlock; + BasicBlock continousBlockLeader = successorBlock; while (true) { - final List> itPreds = - leaderOfFallsthroughBlocks.getPredecessors(); + final List> itPreds = continousBlockLeader.getPredecessors(); - BasicBlock finalLeaderOfFallsthroughBlocks = leaderOfFallsthroughBlocks; + BasicBlock continousBlockTailCandidate = continousBlockLeader; final Optional> fallsthroughPredOpt = itPreds.stream() .filter( b -> b.getTail().fallsThrough() - && b.getSuccessors().get(0) == finalLeaderOfFallsthroughBlocks) + && b.getSuccessors().get(0) == continousBlockTailCandidate) .findAny(); if (!fallsthroughPredOpt.isPresent()) { break; } BasicBlock predecessorBlock = fallsthroughPredOpt.get(); if (predecessorBlock.getTail().fallsThrough() - && predecessorBlock.getSuccessors().get(0) == leaderOfFallsthroughBlocks) { - leaderOfFallsthroughBlocks = predecessorBlock; + && predecessorBlock.getSuccessors().get(0) == continousBlockLeader) { + continousBlockLeader = predecessorBlock; } else { break; } @@ -606,7 +605,7 @@ private void updateFollowingBlocks(BasicBlock currentBlock) { // find a return Stmt inside the current Block Stmt succTailStmt = successorBlock.getTail(); - boolean hasNoSuccessorStmts = succTailStmt.getExpectedSuccessorCount() == 0; + boolean hasNoSuccessorStmts = !succTailStmt.fallsThrough(); boolean isExceptionFree = successorBlock.getExceptionalSuccessors().isEmpty(); boolean isLastStmtCandidate = hasNoSuccessorStmts && isExceptionFree; @@ -614,16 +613,16 @@ private void updateFollowingBlocks(BasicBlock currentBlock) { if (tailStmt instanceof JGotoStmt) { if (isLastStmtCandidate) { nestedBlocks.removeFirstOccurrence(currentBlock); - otherBlocks.addLast(leaderOfFallsthroughBlocks); + otherBlocks.addLast(continousBlockLeader); } else { - otherBlocks.addFirst(leaderOfFallsthroughBlocks); + otherBlocks.addFirst(continousBlockLeader); } - } else if (!nestedBlocks.contains(leaderOfFallsthroughBlocks)) { + } else if (!nestedBlocks.contains(continousBlockLeader)) { // JSwitchStmt, JIfStmt if (isLastStmtCandidate) { - nestedBlocks.addLast(leaderOfFallsthroughBlocks); + nestedBlocks.addLast(continousBlockLeader); } else { - nestedBlocks.addFirst(leaderOfFallsthroughBlocks); + nestedBlocks.addFirst(continousBlockLeader); } } } diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java index 33f9a361e59..ac7b02ec133 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java @@ -144,6 +144,26 @@ public void testConditionalBranchFolderWithMultipleBranches() { Utils.filterJimple(body.toString())); } + @Test + public void testConditionalBranchFolderWithTraps() { + AnalysisInputLocation inputLocation = + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation + .ClassFileBasedAnalysisInputLocation( + classFilePath, + "", + SourceType.Application, + Arrays.asList(new CopyPropagator(), new ConditionalBranchFolder())); + JavaView view = new JavaView(Collections.singletonList(inputLocation)); + + final MethodSignature methodSignature = + view.getIdentifierFactory() + .getMethodSignature( + "ConditionalBranchFolderTest", "tc2", "void", Collections.emptyList()); + Body body = view.getMethod(methodSignature).get().getBody(); + System.out.println(body.getStmtGraph()); + assertFalse(body.getStmts().isEmpty()); + } + /** * Generates the correct test {@link Body} for the corresponding test case. * From 4c6f99ee8832356cd2d328e3747b07a400959be8 Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Tue, 3 Sep 2024 17:13:14 +0200 Subject: [PATCH 05/14] fix trap is not iterated exception in valid cases (i.e. there exists at least one continuous block sequence that has no exception range applied to its last block) --- .../java/sootup/core/graph/StmtGraph.java | 158 ++++++++++-------- .../ConditionalBranchFolderTest.java | 23 ++- 2 files changed, 109 insertions(+), 72 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index f3d6e259774..8d9e705050d 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -495,10 +495,12 @@ public List getTraps() { /** Iterates over the blocks */ protected class BlockGraphIterator implements Iterator> { - @Nonnull private final ArrayDeque> trapHandlerBlocks = new ArrayDeque<>(); + @Nonnull + private final Map, Deque>> fallsThroughSequences = new HashMap<>(); - @Nonnull private final ArrayDeque> nestedBlocks = new ArrayDeque<>(); - @Nonnull private final ArrayDeque> otherBlocks = new ArrayDeque<>(); + @Nullable private BasicBlock fallsTroughWorklist = null; + @Nonnull private final ArrayDeque> trapHandlerBlocks = new ArrayDeque<>(); + @Nonnull private final ArrayDeque> branchingBlockWorklist = new ArrayDeque<>(); @Nonnull private final Set> iteratedBlocks; public BlockGraphIterator() { @@ -508,32 +510,84 @@ public BlockGraphIterator() { if (startingStmt != null) { final BasicBlock startingBlock = getStartingStmtBlock(); updateFollowingBlocks(startingBlock); - nestedBlocks.addFirst(startingBlock); + fallsTroughWorklist = startingBlock; } } + protected Deque> calculateFallsThroughSequence(@Nonnull BasicBlock param) { + Deque> basicBlockSequence = fallsThroughSequences.get(param); + if (basicBlockSequence != null) { + return basicBlockSequence; + } + + Deque> list = new ArrayDeque<>(); + + BasicBlock continousBlockSequenceHeadCandidate = param; + // TODO: [ms] looks ugly.. simplify readability of the loop! + // find the leader of the Block Sequence (connected via FallsthroughStmts) + while (true) { + list.addFirst(continousBlockSequenceHeadCandidate); + final List> itPreds = + continousBlockSequenceHeadCandidate.getPredecessors(); + BasicBlock continousBlockTailCandidate = continousBlockSequenceHeadCandidate; + final Optional> fallsthroughPredOpt = + itPreds.stream() + .filter( + b -> + b.getTail().fallsThrough() + && b.getSuccessors().get(0) == continousBlockTailCandidate) + .findAny(); + if (!fallsthroughPredOpt.isPresent()) { + break; + } + BasicBlock predecessorBlock = fallsthroughPredOpt.get(); + if (predecessorBlock.getTail().fallsThrough() + && predecessorBlock.getSuccessors().get(0) == continousBlockSequenceHeadCandidate) { + continousBlockSequenceHeadCandidate = predecessorBlock; + } else { + break; + } + } + + // iterate to the end of the sequence + BasicBlock continousBlockSequenceTailCandidate = param; + while (continousBlockSequenceTailCandidate.getTail().fallsThrough()) { + continousBlockSequenceTailCandidate = + continousBlockSequenceTailCandidate.getSuccessors().get(0); + list.addLast(continousBlockSequenceTailCandidate); + } + + // cache calculated sequence for every block in the sequence + for (BasicBlock basicBlock : list) { + fallsThroughSequences.put(basicBlock, list); + } + return list; + } + @Nullable private BasicBlock retrieveNextBlock() { BasicBlock nextBlock; do { - if (!nestedBlocks.isEmpty()) { - nextBlock = nestedBlocks.pollFirst(); + if (fallsTroughWorklist != null) { + nextBlock = fallsTroughWorklist; + fallsTroughWorklist = null; + } else if (!branchingBlockWorklist.isEmpty()) { + nextBlock = branchingBlockWorklist.pollFirst(); } else if (!trapHandlerBlocks.isEmpty()) { nextBlock = trapHandlerBlocks.pollFirst(); - } else if (!otherBlocks.isEmpty()) { - nextBlock = otherBlocks.pollFirst(); } else { + /* Fallback mode */ Collection> blocks = getBlocks(); if (iteratedBlocks.size() < blocks.size()) { // graph is not connected! iterate/append all not connected blocks at the end in no // particular order. for (BasicBlock block : blocks) { if (!iteratedBlocks.contains(block)) { - nestedBlocks.addLast(block); + branchingBlockWorklist.addLast(block); } } - if (!nestedBlocks.isEmpty()) { - return nestedBlocks.pollFirst(); + if (!branchingBlockWorklist.isEmpty()) { + return branchingBlockWorklist.pollFirst(); } } @@ -560,71 +614,35 @@ public BasicBlock next() { private void updateFollowingBlocks(BasicBlock currentBlock) { // collect traps final Stmt tailStmt = currentBlock.getTail(); - for (Map.Entry> entry : - currentBlock.getExceptionalSuccessors().entrySet()) { - BasicBlock trapHandlerBlock = entry.getValue(); + for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { trapHandlerBlocks.addLast(trapHandlerBlock); - nestedBlocks.addFirst(trapHandlerBlock); } final List> successors = currentBlock.getSuccessors(); + final int endIdx; + if (tailStmt.fallsThrough()) { + // handle the falls-through successor + assert (fallsTroughWorklist == null); + fallsTroughWorklist = successors.get(0); + endIdx = 1; + } else { + endIdx = 0; + } - for (int i = successors.size() - 1; i >= 0; i--) { - if (i == 0 && tailStmt.fallsThrough()) { - // non-branching successors i.e. not a BranchingStmt or is the first successor (i.e. its - // false successor) of - // JIfStmt - nestedBlocks.addFirst(successors.get(0)); - } else { + // handle the branching successor(s) + for (int i = successors.size() - 1; i >= endIdx; i--) { + // find the leader/beginning block of a continuous sequence of Blocks (connected via + // FallsThroughStmts) + final BasicBlock successorBlock = successors.get(i); - // create the longest FallsThroughStmt sequence possible - final BasicBlock successorBlock = successors.get(i); - BasicBlock continousBlockLeader = successorBlock; - while (true) { - final List> itPreds = continousBlockLeader.getPredecessors(); - - BasicBlock continousBlockTailCandidate = continousBlockLeader; - final Optional> fallsthroughPredOpt = - itPreds.stream() - .filter( - b -> - b.getTail().fallsThrough() - && b.getSuccessors().get(0) == continousBlockTailCandidate) - .findAny(); - if (!fallsthroughPredOpt.isPresent()) { - break; - } - BasicBlock predecessorBlock = fallsthroughPredOpt.get(); - if (predecessorBlock.getTail().fallsThrough() - && predecessorBlock.getSuccessors().get(0) == continousBlockLeader) { - continousBlockLeader = predecessorBlock; - } else { - break; - } - } + Deque> blockSequence = calculateFallsThroughSequence(successorBlock); + boolean isSequenceTailExceptionFree = + blockSequence.getLast().getExceptionalSuccessors().isEmpty(); - // find a return Stmt inside the current Block - Stmt succTailStmt = successorBlock.getTail(); - boolean hasNoSuccessorStmts = !succTailStmt.fallsThrough(); - boolean isExceptionFree = successorBlock.getExceptionalSuccessors().isEmpty(); - - boolean isLastStmtCandidate = hasNoSuccessorStmts && isExceptionFree; - // remember branching successors - if (tailStmt instanceof JGotoStmt) { - if (isLastStmtCandidate) { - nestedBlocks.removeFirstOccurrence(currentBlock); - otherBlocks.addLast(continousBlockLeader); - } else { - otherBlocks.addFirst(continousBlockLeader); - } - } else if (!nestedBlocks.contains(continousBlockLeader)) { - // JSwitchStmt, JIfStmt - if (isLastStmtCandidate) { - nestedBlocks.addLast(continousBlockLeader); - } else { - nestedBlocks.addFirst(continousBlockLeader); - } - } + if (isSequenceTailExceptionFree) { + branchingBlockWorklist.addLast(blockSequence.getFirst()); + } else { + branchingBlockWorklist.addLast(blockSequence.getLast()); } } } @@ -636,7 +654,7 @@ public boolean hasNext() { if (b != null) { // reinsert at FIRST position -> not great for performance - but easier handling in // next() - nestedBlocks.addFirst(b); + branchingBlockWorklist.addFirst(b); hasIteratorMoreElements = true; } else { hasIteratorMoreElements = false; diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java index ac7b02ec133..0ee14583edc 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/interceptors/ConditionalBranchFolderTest.java @@ -160,8 +160,27 @@ public void testConditionalBranchFolderWithTraps() { .getMethodSignature( "ConditionalBranchFolderTest", "tc2", "void", Collections.emptyList()); Body body = view.getMethod(methodSignature).get().getBody(); - System.out.println(body.getStmtGraph()); - assertFalse(body.getStmts().isEmpty()); + assertEquals(5, body.getStmtGraph().getBlocks().size()); + + List actualStmts = Utils.bodyStmtsAsStrings(body); + assertEquals( + Stream.of( + "this := @this: ConditionalBranchFolderTest", + "l1 = 1", + "label1:", + "goto label2", + "label2:", + "$stack3 = new java.lang.Exception", + "specialinvoke $stack3.(java.lang.String)>(\"True\")", + "throw $stack3", + "label3:", + "$stack4 := @caughtexception", + "l2 = $stack4", + "virtualinvoke $stack4.()", + "return", + "catch java.lang.Exception from label1 to label3 with label3") + .collect(Collectors.toList()), + actualStmts); } /** From 51f56ccb02fd9a26ebed2c08ee586ebb3fb5b15a Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Tue, 10 Sep 2024 18:11:01 +0200 Subject: [PATCH 06/14] fix style --- .../UnreachableCodeEliminator.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/sootup.java.core/src/main/java/sootup/java/core/interceptors/UnreachableCodeEliminator.java b/sootup.java.core/src/main/java/sootup/java/core/interceptors/UnreachableCodeEliminator.java index 29ec9c6a682..161b78fdaa5 100644 --- a/sootup.java.core/src/main/java/sootup/java/core/interceptors/UnreachableCodeEliminator.java +++ b/sootup.java.core/src/main/java/sootup/java/core/interceptors/UnreachableCodeEliminator.java @@ -21,9 +21,11 @@ * #L% */ import java.util.*; +import java.util.stream.Collectors; import javax.annotation.Nonnull; +import sootup.core.graph.BasicBlock; +import sootup.core.graph.MutableBasicBlock; import sootup.core.graph.MutableStmtGraph; -import sootup.core.jimple.common.stmt.Stmt; import sootup.core.model.Body; import sootup.core.transform.BodyInterceptor; import sootup.core.views.View; @@ -31,12 +33,10 @@ /** * A BodyInterceptor that removes all unreachable stmts from the given Body. * - * @author Zun Wang + * @author Zun Wang, Sahil Agichani */ public class UnreachableCodeEliminator implements BodyInterceptor { - // TODO: performance - quite expensive; maybe work on Block level to reduce hash calculations etc? - @Override public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) { @@ -47,32 +47,40 @@ public void interceptBody(@Nonnull Body.BodyBuilder builder, @Nonnull View view) return; } - Deque queue = new ArrayDeque<>(); - queue.add(graph.getStartingStmt()); + Collection> allBlocks = graph.getBlocks(); + MutableBasicBlock startingStmtBlock = (MutableBasicBlock) graph.getStartingStmtBlock(); + Set reachableNodes = new HashSet<>(); + Deque stack = new ArrayDeque<>(); + stack.push(startingStmtBlock); - // calculate all reachable stmts - Set reachableStmts = new HashSet<>(); - while (!queue.isEmpty()) { - Stmt stmt = queue.removeFirst(); - reachableStmts.add(stmt); - for (Stmt succ : graph.getAllSuccessors(stmt)) { - if (!reachableStmts.contains(succ)) { - queue.add(succ); - } + // Traverse the call graph using DFS + while (!stack.isEmpty()) { + MutableBasicBlock currentBlock = stack.pop(); + // If the method has already been visited, skip it + if (!reachableNodes.add(currentBlock)) { + continue; } - } + // Get all the successors (i.e., called methods) of the current method + List currentBlockExceptionalSuccessors = + new ArrayList<>(currentBlock.getExceptionalSuccessors().values()); + List currentBlockSuccessors = currentBlock.getSuccessors(); + List currentBlockAllSuccessors = new ArrayList<>(currentBlockSuccessors); + currentBlockAllSuccessors.addAll(currentBlockExceptionalSuccessors); - // remove unreachable stmts from StmtGraph - Queue removeQ = new ArrayDeque<>(); - for (Stmt stmt : graph.getNodes()) { - if (!reachableStmts.contains(stmt)) { - removeQ.add(stmt); + // Push the successors into the stack + for (MutableBasicBlock successor : currentBlockAllSuccessors) { + if (!reachableNodes.contains(successor)) { + stack.push(successor); + } } } - for (Stmt stmt : removeQ) { - graph.removeNode(stmt, false); - builder.removeDefLocalsOf(stmt); + List> unreachableBlocks = + allBlocks.stream() + .filter(basicBlock -> !reachableNodes.contains(basicBlock)) + .collect(Collectors.toList()); + for (BasicBlock unreachableBlock : unreachableBlocks) { + graph.removeBlock(unreachableBlock); } } } From 626e2497e6a1b4ee76ebfd3e748196753afb38bd Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Fri, 13 Sep 2024 12:26:26 +0200 Subject: [PATCH 07/14] fix exceptionial flow caused nontermination + order improvement --- .../bugfixes/NestedTryCatchFinally.java | 25 + .../java/sootup/core/graph/StmtGraph.java | 1246 +++++++++-------- .../java/bytecode/TryCatchFinallyTests.java | 49 + .../java6/IfElseStatementTest.java | 36 +- 4 files changed, 737 insertions(+), 619 deletions(-) create mode 100644 shared-test-resources/bugfixes/NestedTryCatchFinally.java create mode 100644 sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java diff --git a/shared-test-resources/bugfixes/NestedTryCatchFinally.java b/shared-test-resources/bugfixes/NestedTryCatchFinally.java new file mode 100644 index 00000000000..938700fd60f --- /dev/null +++ b/shared-test-resources/bugfixes/NestedTryCatchFinally.java @@ -0,0 +1,25 @@ +import java.io.FileInputStream; +import java.io.File; +import java.io.ObjectInputStream; + +public class NestedTryCatchFinally { + + private static String test0(File storedResults) throws Exception { + try { + FileInputStream file = new FileInputStream(storedResults); + try { + ObjectInputStream stream = new ObjectInputStream(file); + try { + return (String) stream.readObject(); + } finally { + stream.close(); + } + } finally { + file.close(); + } + } catch (Exception e) { + throw new Exception(e); + } + } + +} \ No newline at end of file diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 8d9e705050d..076bcd786b2 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -22,12 +22,6 @@ */ import com.google.common.collect.Iterators; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.*; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import sootup.core.jimple.basic.Trap; import sootup.core.jimple.common.stmt.*; import sootup.core.jimple.javabytecode.stmt.JSwitchStmt; @@ -36,6 +30,13 @@ import sootup.core.util.EscapedWriter; import sootup.core.util.printer.JimplePrinter; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; +import java.util.stream.Collectors; + /** * Interface for control flow graphs on Jimple Stmts. A StmtGraph is directed and connected (except * for traphandlers - those are not connected to the unexceptional flow via StmtGraph). Its directed @@ -62,669 +63,694 @@ */ public abstract class StmtGraph> implements Iterable { - public abstract Stmt getStartingStmt(); - - public abstract BasicBlock getStartingStmtBlock(); - - /** - * returns the nodes in this graph in a non-deterministic order (->Set) to get the nodes in - * linearized, ordered manner use iterator() or getStmts. - */ - @Nonnull - public abstract Collection getNodes(); - - public List getStmts() { - final ArrayList res = new ArrayList<>(); - Iterators.addAll(res, iterator()); - return res; - } - - @Nonnull - public abstract Collection> getBlocks(); - - @Nonnull - public abstract List> getBlocksSorted(); - - public Iterator> getBlockIterator() { - return new BlockGraphIterator(); - } - - public abstract BasicBlock getBlockOf(@Nonnull Stmt stmt); - - public abstract boolean containsNode(@Nonnull Stmt node); - - /** - * returns the ingoing flows to node as an List with no reliable/specific order and possibly - * duplicate entries i.e. if a JSwitchStmt has multiple cases that brnach to `node` - */ - @Nonnull - public abstract List predecessors(@Nonnull Stmt node); - - /** it is possible to reach traphandlers through inline code i.e. without any exceptional flow */ - @Nonnull - public abstract List exceptionalPredecessors(@Nonnull Stmt node); - - /** returns the outgoing flows of node as ordered List. The List can have duplicate entries! */ - @Nonnull - public abstract List successors(@Nonnull Stmt node); - - @Nonnull - public abstract Map exceptionalSuccessors(@Nonnull Stmt node); - - /** - * Collects all successors i.e. unexceptional and exceptional successors of a given stmt into a - * list. - * - * @param stmt in the given graph - * @return a list containing the unexceptional+exceptional successors of the given stmt - */ - @Nonnull - public List getAllSuccessors(@Nonnull Stmt stmt) { - final List successors = successors(stmt); - final Map exSuccessors = exceptionalSuccessors(stmt); - List allSuccessors = new ArrayList<>(successors.size() + exSuccessors.size()); - allSuccessors.addAll(successors); - allSuccessors.addAll(exSuccessors.values()); - return allSuccessors; - } - - /** returns the amount of ingoing flows into node */ - public abstract int inDegree(@Nonnull Stmt node); - - /** returns the amount of flows that start from node */ - public abstract int outDegree(@Nonnull Stmt node); - - /** returns the amount of flows with node as source or target. */ - public int degree(@Nonnull Stmt node) { - return inDegree(node) + outDegree(node); - } - - /** - * returns true if there is a flow between source and target throws an Exception if at least one - * of the parameters is not contained in the graph. - */ - public abstract boolean hasEdgeConnecting(@Nonnull Stmt source, @Nonnull Stmt target); - - /** - * returns a (reconstructed) list of traps like the traptable in the bytecode - * - *

Note: if you need exceptionional flow information in more augmented with the affected - * blocks/stmts and not just a (reconstructed, possibly more verbose) traptable - have a look at - * BasicBlock.getExceptionalSuccessor() - */ - public abstract List buildTraps(); - - /** - * returns a Collection of Stmts that leave the body (i.e. JReturnVoidStmt, JReturnStmt and - * JThrowStmt) - */ - @Nonnull - public List getTails() { - return getNodes().stream() - .filter(stmt -> stmt.getExpectedSuccessorCount() == 0) - .collect(Collectors.toList()); - } - - /** - * returns a Collection of all stmts in the graph that don't have an unexceptional ingoing flow or - * are the starting Stmt. - */ - @Nonnull - public Collection getEntrypoints() { - final ArrayList stmts = new ArrayList<>(); - stmts.add(getStartingStmt()); - // TODO: [ms] memory/performance: instead of gettraps(): iterate through all stmts and add - // startingStmt+@caughtexception/predecessors().size() == 0? - buildTraps().stream().map(Trap::getHandlerStmt).forEach(stmts::add); - return stmts; - } - - /** validates whether the each Stmt has the correct amount of outgoing flows. */ - public void validateStmtConnectionsInGraph() { - try { - - for (Stmt stmt : getNodes()) { - final List successors = successors(stmt); - final int successorCount = successors.size(); - - if (predecessors(stmt).isEmpty()) { - if (!(stmt == getStartingStmt() - || buildTraps().stream() - .map(Trap::getHandlerStmt) - .anyMatch(handler -> handler == stmt))) { - throw new IllegalStateException( - "Stmt '" - + stmt - + "' which is neither the StartingStmt nor a TrapHandler is missing a predecessor!"); - } - } - - if (stmt instanceof BranchingStmt) { - if (stmt instanceof JSwitchStmt) { - if (successorCount != ((JSwitchStmt) stmt).getValueCount()) { - throw new IllegalStateException( - stmt - + ": size of outgoing flows (i.e. " - + successorCount - + ") does not match the amount of JSwitchStmts case labels (i.e. " - + ((JSwitchStmt) stmt).getValueCount() - + ")."); - } - } else if (stmt instanceof JIfStmt) { - if (successorCount != 2) { - throw new IllegalStateException( - stmt + ": JIfStmt must have '2' outgoing flow but has '" + successorCount + "'."); - } - } else if (stmt instanceof JGotoStmt) { - if (successorCount != 1) { - throw new IllegalStateException( - stmt + ": JGoto must have '1' outgoing flow but has '" + successorCount + "'."); - } - } - - } else if (stmt instanceof JReturnStmt - || stmt instanceof JReturnVoidStmt - || stmt instanceof JThrowStmt) { - if (successorCount != 0) { - throw new IllegalStateException( - stmt + ": must have '0' outgoing flow but has '" + successorCount + "'."); - } - } else { - if (successorCount != 1) { - throw new IllegalStateException( - stmt + ": must have '1' outgoing flow but has '" + successorCount + "'."); - } - } - } - - } catch (Exception e) { - final String urlToWebeditor = DotExporter.createUrlToWebeditor(this); - throw new IllegalStateException("visualize invalid StmtGraph: " + urlToWebeditor, e); - } - } - - /** - * Look for a path in graph, from def to use. This path has to lie inside an extended basic block - * (and this property implies uniqueness.). The path returned includes from and to. FIXME: ms: - * explain better - * - * @param from start point for the path. - * @param to end point for the path. - * @return null if there is no such path. - */ - @Nullable - public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull Stmt to) { - - // if this holds, we're doomed to failure!!! - if (inDegree(to) > 1) { - return null; - } + public abstract Stmt getStartingStmt(); - // pathStack := list of succs lists - // pathStackIndex := last visited index in pathStack - List pathStack = new ArrayList<>(); - List pathStackIndex = new ArrayList<>(); - - pathStack.add(from); - pathStackIndex.add(0); - - int psiMax = outDegree(pathStack.get(0)); - int level = 0; - while (pathStackIndex.get(0) != psiMax) { - int p = pathStackIndex.get(level); - - List succs = successors((pathStack.get(level))); - if (p >= succs.size()) { - // no more succs - backtrack to previous level. - - pathStack.remove(level); - pathStackIndex.remove(level); - - level--; - int q = pathStackIndex.get(level); - pathStackIndex.set(level, q + 1); - continue; - } - - Stmt betweenStmt = succs.get(p); - - // we win! - if (betweenStmt == to) { - pathStack.add(to); - return pathStack; - } - - // check preds of betweenStmt to see if we should visit its kids. - if (inDegree(betweenStmt) > 1) { - pathStackIndex.set(level, p + 1); - continue; - } - - // visit kids of betweenStmt. - level++; - pathStackIndex.add(0); - pathStack.add(betweenStmt); - } - return null; - } + public abstract BasicBlock getStartingStmtBlock(); - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } + /** + * returns the nodes in this graph in a non-deterministic order (->Set) to get the nodes in + * linearized, ordered manner use iterator() or getStmts. + */ + @Nonnull + public abstract Collection getNodes(); - if (!(o instanceof StmtGraph)) { - return false; + public List getStmts() { + final ArrayList res = new ArrayList<>(); + Iterators.addAll(res, iterator()); + return res; } - StmtGraph otherGraph = (StmtGraph) o; - if (getStartingStmt() != otherGraph.getStartingStmt()) { - return false; - } + @Nonnull + public abstract Collection> getBlocks(); - Collection nodes = getNodes(); - final Collection otherNodes = otherGraph.getNodes(); - if (nodes.size() != otherNodes.size()) { - return false; + @Nonnull + public abstract List> getBlocksSorted(); + + public Iterator> getBlockIterator() { + return new BlockGraphIterator(); } - if (!buildTraps().equals(otherGraph.buildTraps())) { - return false; + public abstract BasicBlock getBlockOf(@Nonnull Stmt stmt); + + public abstract boolean containsNode(@Nonnull Stmt node); + + /** + * returns the ingoing flows to node as an List with no reliable/specific order and possibly + * duplicate entries i.e. if a JSwitchStmt has multiple cases that brnach to `node` + */ + @Nonnull + public abstract List predecessors(@Nonnull Stmt node); + + /** + * it is possible to reach traphandlers through inline code i.e. without any exceptional flow + */ + @Nonnull + public abstract List exceptionalPredecessors(@Nonnull Stmt node); + + /** + * returns the outgoing flows of node as ordered List. The List can have duplicate entries! + */ + @Nonnull + public abstract List successors(@Nonnull Stmt node); + + @Nonnull + public abstract Map exceptionalSuccessors(@Nonnull Stmt node); + + /** + * Collects all successors i.e. unexceptional and exceptional successors of a given stmt into a + * list. + * + * @param stmt in the given graph + * @return a list containing the unexceptional+exceptional successors of the given stmt + */ + @Nonnull + public List getAllSuccessors(@Nonnull Stmt stmt) { + final List successors = successors(stmt); + final Map exSuccessors = exceptionalSuccessors(stmt); + List allSuccessors = new ArrayList<>(successors.size() + exSuccessors.size()); + allSuccessors.addAll(successors); + allSuccessors.addAll(exSuccessors.values()); + return allSuccessors; } - for (Stmt node : nodes) { - if (!otherNodes.contains(node)) { - return false; - } - final List successors = successors(node); - final List otherSuccessors = otherGraph.successors(node); - if (!successors.equals(otherSuccessors)) { - return false; - } + /** + * returns the amount of ingoing flows into node + */ + public abstract int inDegree(@Nonnull Stmt node); + + /** + * returns the amount of flows that start from node + */ + public abstract int outDegree(@Nonnull Stmt node); + + /** + * returns the amount of flows with node as source or target. + */ + public int degree(@Nonnull Stmt node) { + return inDegree(node) + outDegree(node); } - return true; - } + /** + * returns true if there is a flow between source and target throws an Exception if at least one + * of the parameters is not contained in the graph. + */ + public abstract boolean hasEdgeConnecting(@Nonnull Stmt source, @Nonnull Stmt target); - @Override - @Nonnull - public Iterator iterator() { - return new BlockStmtGraphIterator(); - } + /** + * returns a (reconstructed) list of traps like the traptable in the bytecode + * + *

Note: if you need exceptionional flow information in more augmented with the affected + * blocks/stmts and not just a (reconstructed, possibly more verbose) traptable - have a look at + * BasicBlock.getExceptionalSuccessor() + */ + public abstract List buildTraps(); - public List getBranchTargetsOf(BranchingStmt fromStmt) { - final List successors = successors(fromStmt); - if (fromStmt instanceof JIfStmt) { - // remove the first successor as if its a fallsthrough stmt and not a branch target - return Collections.singletonList(successors.get(1)); + /** + * returns a Collection of Stmts that leave the body (i.e. JReturnVoidStmt, JReturnStmt and + * JThrowStmt) + */ + @Nonnull + public List getTails() { + return getNodes().stream() + .filter(stmt -> stmt.getExpectedSuccessorCount() == 0) + .collect(Collectors.toList()); } - return successors; - } - - public boolean isStmtBranchTarget(@Nonnull Stmt targetStmt) { - final List predecessors = predecessors(targetStmt); - if (predecessors.size() > 1) { - // join node i.e. at least one is a branch - return true; + + /** + * returns a Collection of all stmts in the graph that don't have an unexceptional ingoing flow or + * are the starting Stmt. + */ + @Nonnull + public Collection getEntrypoints() { + final ArrayList stmts = new ArrayList<>(); + stmts.add(getStartingStmt()); + // TODO: [ms] memory/performance: instead of gettraps(): iterate through all stmts and add + // startingStmt+@caughtexception/predecessors().size() == 0? + buildTraps().stream().map(Trap::getHandlerStmt).forEach(stmts::add); + return stmts; } - final Iterator iterator = predecessors.iterator(); - if (iterator.hasNext()) { - Stmt pred = iterator.next(); - if (pred.branches()) { - if (pred instanceof JIfStmt) { - // [ms] bounds are validated in Body - return getBranchTargetsOf((JIfStmt) pred).get(0) == targetStmt; + /** + * validates whether the each Stmt has the correct amount of outgoing flows. + */ + public void validateStmtConnectionsInGraph() { + try { + + for (Stmt stmt : getNodes()) { + final List successors = successors(stmt); + final int successorCount = successors.size(); + + if (predecessors(stmt).isEmpty()) { + if (!(stmt == getStartingStmt() + || buildTraps().stream() + .map(Trap::getHandlerStmt) + .anyMatch(handler -> handler == stmt))) { + throw new IllegalStateException( + "Stmt '" + + stmt + + "' which is neither the StartingStmt nor a TrapHandler is missing a predecessor!"); + } + } + + if (stmt instanceof BranchingStmt) { + if (stmt instanceof JSwitchStmt) { + if (successorCount != ((JSwitchStmt) stmt).getValueCount()) { + throw new IllegalStateException( + stmt + + ": size of outgoing flows (i.e. " + + successorCount + + ") does not match the amount of JSwitchStmts case labels (i.e. " + + ((JSwitchStmt) stmt).getValueCount() + + ")."); + } + } else if (stmt instanceof JIfStmt) { + if (successorCount != 2) { + throw new IllegalStateException( + stmt + ": JIfStmt must have '2' outgoing flow but has '" + successorCount + "'."); + } + } else if (stmt instanceof JGotoStmt) { + if (successorCount != 1) { + throw new IllegalStateException( + stmt + ": JGoto must have '1' outgoing flow but has '" + successorCount + "'."); + } + } + + } else if (stmt instanceof JReturnStmt + || stmt instanceof JReturnVoidStmt + || stmt instanceof JThrowStmt) { + if (successorCount != 0) { + throw new IllegalStateException( + stmt + ": must have '0' outgoing flow but has '" + successorCount + "'."); + } + } else { + if (successorCount != 1) { + throw new IllegalStateException( + stmt + ": must have '1' outgoing flow but has '" + successorCount + "'."); + } + } + } + + } catch (Exception e) { + final String urlToWebeditor = DotExporter.createUrlToWebeditor(this); + throw new IllegalStateException("visualize invalid StmtGraph: " + urlToWebeditor, e); } - return true; - } } - return false; - } + /** + * Look for a path in graph, from def to use. This path has to lie inside an extended basic block + * (and this property implies uniqueness.). The path returned includes from and to. FIXME: ms: + * explain better + * + * @param from start point for the path. + * @param to end point for the path. + * @return null if there is no such path. + */ + @Nullable + public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull Stmt to) { - /** Iterates the Stmts according to the jimple output order. */ - private class BlockStmtGraphIterator implements Iterator { + // if this holds, we're doomed to failure!!! + if (inDegree(to) > 1) { + return null; + } - private final BlockGraphIterator blockIt; - @Nonnull private Iterator currentBlockIt = Collections.emptyIterator(); + // pathStack := list of succs lists + // pathStackIndex := last visited index in pathStack + List pathStack = new ArrayList<>(); + List pathStackIndex = new ArrayList<>(); - public BlockStmtGraphIterator() { - this(new BlockGraphIterator()); - } + pathStack.add(from); + pathStackIndex.add(0); - public BlockStmtGraphIterator(@Nonnull BlockGraphIterator blockIterator) { - blockIt = blockIterator; - } + int psiMax = outDegree(pathStack.get(0)); + int level = 0; + while (pathStackIndex.get(0) != psiMax) { + int p = pathStackIndex.get(level); - @Override - public boolean hasNext() { - // hint: a BasicBlock has at least 1 Stmt or should not be in a StmtGraph! - return currentBlockIt.hasNext() || blockIt.hasNext(); + List succs = successors((pathStack.get(level))); + if (p >= succs.size()) { + // no more succs - backtrack to previous level. + + pathStack.remove(level); + pathStackIndex.remove(level); + + level--; + int q = pathStackIndex.get(level); + pathStackIndex.set(level, q + 1); + continue; + } + + Stmt betweenStmt = succs.get(p); + + // we win! + if (betweenStmt == to) { + pathStack.add(to); + return pathStack; + } + + // check preds of betweenStmt to see if we should visit its kids. + if (inDegree(betweenStmt) > 1) { + pathStackIndex.set(level, p + 1); + continue; + } + + // visit kids of betweenStmt. + level++; + pathStackIndex.add(0); + pathStack.add(betweenStmt); + } + return null; } @Override - public Stmt next() { - if (!currentBlockIt.hasNext()) { - if (!blockIt.hasNext()) { - throw new NoSuchElementException("Iterator has no more Stmts."); - } - BasicBlock currentBlock = blockIt.next(); - currentBlockIt = currentBlock.getStmts().iterator(); - } - return currentBlockIt.next(); - } - } + public boolean equals(Object o) { + if (o == this) { + return true; + } - /** Iterates over the Blocks and collects/aggregates Trap information */ - public class BlockGraphIteratorAndTrapAggregator extends BlockGraphIterator { + if (!(o instanceof StmtGraph)) { + return false; + } + StmtGraph otherGraph = (StmtGraph) o; - @Nonnull private final List collectedTraps = new ArrayList<>(); + if (getStartingStmt() != otherGraph.getStartingStmt()) { + return false; + } + + Collection nodes = getNodes(); + final Collection otherNodes = otherGraph.getNodes(); + if (nodes.size() != otherNodes.size()) { + return false; + } - Map activeTraps = new HashMap<>(); - BasicBlock lastIteratedBlock; // dummy value to remove n-1 unnecessary null-checks + if (!buildTraps().equals(otherGraph.buildTraps())) { + return false; + } + + for (Stmt node : nodes) { + if (!otherNodes.contains(node)) { + return false; + } + final List successors = successors(node); + final List otherSuccessors = otherGraph.successors(node); + if (!successors.equals(otherSuccessors)) { + return false; + } + } - /* - * @param dummyBlock is just an empty instantiation of type V - as neither BasicBlock nor V instantiable we need a concrete object from the using subclass itclass. - * */ - public BlockGraphIteratorAndTrapAggregator(V dummyBlock) { - super(); - lastIteratedBlock = dummyBlock; + return true; } - @Nonnull @Override - public BasicBlock next() { - final BasicBlock block = super.next(); - - final Map> currentBlocksExceptions = - block.getExceptionalSuccessors(); - final Map> lastBlocksExceptions = - lastIteratedBlock.getExceptionalSuccessors(); - - // former trap info is not in the current blocks info -> add it to the trap collection - lastBlocksExceptions.forEach( - (type, trapHandlerBlock) -> { - if (trapHandlerBlock != block.getExceptionalSuccessors().get(type)) { - final Stmt trapBeginStmt = activeTraps.remove(type); - if (trapBeginStmt == null) { - throw new IllegalStateException("Trap start for '" + type + "' is not in the Map!"); - } - // trapend is exclusive! - collectedTraps.add( - new Trap(type, trapBeginStmt, block.getHead(), trapHandlerBlock.getHead())); - } - }); - - // is there a new trap in the current block -> add it to currentTraps - block - .getExceptionalSuccessors() - .forEach( - (type, trapHandlerBlock) -> { - if (trapHandlerBlock != lastBlocksExceptions.get(type)) { - activeTraps.put(type, block.getHead()); + @Nonnull + public Iterator iterator() { + return new BlockStmtGraphIterator(); + } + + public List getBranchTargetsOf(BranchingStmt fromStmt) { + final List successors = successors(fromStmt); + if (fromStmt instanceof JIfStmt) { + // remove the first successor as if its a fallsthrough stmt and not a branch target + return Collections.singletonList(successors.get(1)); + } + return successors; + } + + public boolean isStmtBranchTarget(@Nonnull Stmt targetStmt) { + final List predecessors = predecessors(targetStmt); + if (predecessors.size() > 1) { + // join node i.e. at least one is a branch + return true; + } + + final Iterator iterator = predecessors.iterator(); + if (iterator.hasNext()) { + Stmt pred = iterator.next(); + if (pred.branches()) { + if (pred instanceof JIfStmt) { + // [ms] bounds are validated in Body + return getBranchTargetsOf((JIfStmt) pred).get(0) == targetStmt; } - }); + return true; + } + } - lastIteratedBlock = block; - return block; + return false; } /** - * for jimple serialization - this info contains only valid/useful information if all stmts are - * iterated i.e. hasNext() == false! - * - * @return List of Traps + * Iterates the Stmts according to the jimple output order. */ - public List getTraps() { - - if (hasNext()) { - throw new IllegalStateException("Iterator needs to be iterated completely!"); - } - - // check for dangling traps that are not collected as the endStmt was not visited. - if (!activeTraps.isEmpty()) { - throw new IllegalArgumentException( - "Invalid StmtGraph. A Trap is not created as a traps endStmt was not visited during the iteration of all Stmts."); - } - return collectedTraps; + private class BlockStmtGraphIterator implements Iterator { + + private final BlockGraphIterator blockIt; + @Nonnull + private Iterator currentBlockIt = Collections.emptyIterator(); + + public BlockStmtGraphIterator() { + this(new BlockGraphIterator()); + } + + public BlockStmtGraphIterator(@Nonnull BlockGraphIterator blockIterator) { + blockIt = blockIterator; + } + + @Override + public boolean hasNext() { + // hint: a BasicBlock has at least 1 Stmt or should not be in a StmtGraph! + return currentBlockIt.hasNext() || blockIt.hasNext(); + } + + @Override + public Stmt next() { + if (!currentBlockIt.hasNext()) { + if (!blockIt.hasNext()) { + throw new NoSuchElementException("Iterator has no more Stmts."); + } + BasicBlock currentBlock = blockIt.next(); + currentBlockIt = currentBlock.getStmts().iterator(); + } + return currentBlockIt.next(); + } } - } - /** Iterates over the blocks */ - protected class BlockGraphIterator implements Iterator> { + /** + * Iterates over the Blocks and collects/aggregates Trap information + */ + public class BlockGraphIteratorAndTrapAggregator extends BlockGraphIterator { - @Nonnull - private final Map, Deque>> fallsThroughSequences = new HashMap<>(); - - @Nullable private BasicBlock fallsTroughWorklist = null; - @Nonnull private final ArrayDeque> trapHandlerBlocks = new ArrayDeque<>(); - @Nonnull private final ArrayDeque> branchingBlockWorklist = new ArrayDeque<>(); - @Nonnull private final Set> iteratedBlocks; - - public BlockGraphIterator() { - final Collection> blocks = getBlocks(); - iteratedBlocks = new LinkedHashSet<>(blocks.size(), 1); - Stmt startingStmt = getStartingStmt(); - if (startingStmt != null) { - final BasicBlock startingBlock = getStartingStmtBlock(); - updateFollowingBlocks(startingBlock); - fallsTroughWorklist = startingBlock; - } + @Nonnull + private final List collectedTraps = new ArrayList<>(); + + Map activeTraps = new HashMap<>(); + BasicBlock lastIteratedBlock; // dummy value to remove n-1 unnecessary null-checks + + /* + * @param dummyBlock is just an empty instantiation of type V - as neither BasicBlock nor V instantiable we need a concrete object from the using subclass itclass. + * */ + public BlockGraphIteratorAndTrapAggregator(V dummyBlock) { + super(); + lastIteratedBlock = dummyBlock; + } + + @Nonnull + @Override + public BasicBlock next() { + final BasicBlock block = super.next(); + + final Map> currentBlocksExceptions = + block.getExceptionalSuccessors(); + final Map> lastBlocksExceptions = + lastIteratedBlock.getExceptionalSuccessors(); + + // former trap info is not in the current blocks info -> add it to the trap collection + lastBlocksExceptions.forEach( + (type, trapHandlerBlock) -> { + if (trapHandlerBlock != block.getExceptionalSuccessors().get(type)) { + final Stmt trapBeginStmt = activeTraps.remove(type); + if (trapBeginStmt == null) { + throw new IllegalStateException("Trap start for '" + type + "' is not in the Map!"); + } + // trapend is exclusive! + collectedTraps.add( + new Trap(type, trapBeginStmt, block.getHead(), trapHandlerBlock.getHead())); + } + }); + + // is there a new trap in the current block -> add it to currentTraps + block + .getExceptionalSuccessors() + .forEach( + (type, trapHandlerBlock) -> { + if (trapHandlerBlock != lastBlocksExceptions.get(type)) { + activeTraps.put(type, block.getHead()); + } + }); + + lastIteratedBlock = block; + return block; + } + + /** + * for jimple serialization - this info contains only valid/useful information if all stmts are + * iterated i.e. hasNext() == false! + * + * @return List of Traps + */ + public List getTraps() { + + if (hasNext()) { + throw new IllegalStateException("Iterator needs to be iterated completely!"); + } + + // check for dangling traps that are not collected as the endStmt was not visited. + if (!activeTraps.isEmpty()) { + throw new IllegalArgumentException( + "Invalid StmtGraph. A Trap is not created as a traps endStmt was not visited during the iteration of all Stmts."); + } + return collectedTraps; + } } - protected Deque> calculateFallsThroughSequence(@Nonnull BasicBlock param) { - Deque> basicBlockSequence = fallsThroughSequences.get(param); - if (basicBlockSequence != null) { - return basicBlockSequence; - } - - Deque> list = new ArrayDeque<>(); - - BasicBlock continousBlockSequenceHeadCandidate = param; - // TODO: [ms] looks ugly.. simplify readability of the loop! - // find the leader of the Block Sequence (connected via FallsthroughStmts) - while (true) { - list.addFirst(continousBlockSequenceHeadCandidate); - final List> itPreds = - continousBlockSequenceHeadCandidate.getPredecessors(); - BasicBlock continousBlockTailCandidate = continousBlockSequenceHeadCandidate; - final Optional> fallsthroughPredOpt = - itPreds.stream() - .filter( - b -> - b.getTail().fallsThrough() - && b.getSuccessors().get(0) == continousBlockTailCandidate) - .findAny(); - if (!fallsthroughPredOpt.isPresent()) { - break; - } - BasicBlock predecessorBlock = fallsthroughPredOpt.get(); - if (predecessorBlock.getTail().fallsThrough() - && predecessorBlock.getSuccessors().get(0) == continousBlockSequenceHeadCandidate) { - continousBlockSequenceHeadCandidate = predecessorBlock; - } else { - break; - } - } - - // iterate to the end of the sequence - BasicBlock continousBlockSequenceTailCandidate = param; - while (continousBlockSequenceTailCandidate.getTail().fallsThrough()) { - continousBlockSequenceTailCandidate = - continousBlockSequenceTailCandidate.getSuccessors().get(0); - list.addLast(continousBlockSequenceTailCandidate); - } - - // cache calculated sequence for every block in the sequence - for (BasicBlock basicBlock : list) { - fallsThroughSequences.put(basicBlock, list); - } - return list; + + protected static class IteratorFrame implements Comparable, Iterable> { + private final int weight; + private final Deque> sequence; + + protected IteratorFrame(Deque> sequence, int weight) { + this.weight = weight; + this.sequence = sequence; + } + + @Override + public int compareTo(IteratorFrame o) { + return Integer.compare(weight, o.weight); + } + + public int getWeight() { + return weight; + } + + public int size(){ + return sequence.size(); + } + + @Nonnull + @Override + public Iterator> iterator() { + return sequence.iterator(); + } } - @Nullable - private BasicBlock retrieveNextBlock() { - BasicBlock nextBlock; - do { - if (fallsTroughWorklist != null) { - nextBlock = fallsTroughWorklist; - fallsTroughWorklist = null; - } else if (!branchingBlockWorklist.isEmpty()) { - nextBlock = branchingBlockWorklist.pollFirst(); - } else if (!trapHandlerBlocks.isEmpty()) { - nextBlock = trapHandlerBlocks.pollFirst(); - } else { - /* Fallback mode */ - Collection> blocks = getBlocks(); - if (iteratedBlocks.size() < blocks.size()) { - // graph is not connected! iterate/append all not connected blocks at the end in no - // particular order. - for (BasicBlock block : blocks) { - if (!iteratedBlocks.contains(block)) { - branchingBlockWorklist.addLast(block); - } + /** + * Iterates over the blocks + */ + protected class BlockGraphIterator implements Iterator> { + + private final PriorityQueue worklist = new PriorityQueue<>(); + private IteratorFrame itFrame; + private Iterator> itBlock; + @Nonnull + private final Map, Deque>> fallsThroughSequenceMap = new HashMap<>(); + @Nonnull + private final Set> seenTargets; + + public BlockGraphIterator() { + final Collection> blocks = getBlocks(); + seenTargets = new LinkedHashSet<>(blocks.size(), 1); + Stmt startingStmt = getStartingStmt(); + if (startingStmt != null) { + final BasicBlock startingBlock = getStartingStmtBlock(); + itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0); + itBlock = itFrame.iterator(); + updateFollowingBlocks(startingBlock, 0); + } + } + + protected Deque> calculateFallsThroughSequence(@Nonnull BasicBlock param) { + Deque> basicBlockSequence = fallsThroughSequenceMap.get(param); + if (basicBlockSequence != null) { + return basicBlockSequence; + } + + Deque> blockSequence = new ArrayDeque<>(); + + BasicBlock continousBlockSequenceHeadCandidate = param; + // TODO: [ms] looks ugly.. simplify readability of the loop! + // find the leader of the Block Sequence (connected via FallsthroughStmts) + while (true) { + blockSequence.addFirst(continousBlockSequenceHeadCandidate); + final List> itPreds = + continousBlockSequenceHeadCandidate.getPredecessors(); + BasicBlock continousBlockTailCandidate = continousBlockSequenceHeadCandidate; + final Optional> fallsthroughPredOpt = + itPreds.stream() + .filter( + b -> + b.getTail().fallsThrough() + && b.getSuccessors().get(0) == continousBlockTailCandidate) + .findAny(); + if (!fallsthroughPredOpt.isPresent()) { + break; + } + BasicBlock predecessorBlock = fallsthroughPredOpt.get(); + if (predecessorBlock.getTail().fallsThrough() + && predecessorBlock.getSuccessors().get(0) == continousBlockSequenceHeadCandidate) { + continousBlockSequenceHeadCandidate = predecessorBlock; + } else { + break; + } } - if (!branchingBlockWorklist.isEmpty()) { - return branchingBlockWorklist.pollFirst(); + + // iterate to the end of the sequence + BasicBlock continousBlockSequenceTailCandidate = param; + while (continousBlockSequenceTailCandidate.getTail().fallsThrough()) { + continousBlockSequenceTailCandidate = + continousBlockSequenceTailCandidate.getSuccessors().get(0); + blockSequence.addLast(continousBlockSequenceTailCandidate); } - } - return null; + // cache calculated sequence for every block in the sequence + for (BasicBlock basicBlock : blockSequence) { + fallsThroughSequenceMap.put(basicBlock, blockSequence); + seenTargets.add(basicBlock); + } + return blockSequence; } - // skip retrieved nextBlock if its already returned - } while (iteratedBlocks.contains(nextBlock)); - return nextBlock; - } + @Override + @Nonnull + public BasicBlock next() { + if(!itBlock.hasNext()){ + itFrame = worklist.poll(); + if (itFrame == null) { + throw new NoSuchElementException("Iterator has no more Blocks."); + } + itBlock = itFrame.iterator(); + } - @Override - @Nonnull - public BasicBlock next() { - BasicBlock currentBlock = retrieveNextBlock(); - if (currentBlock == null) { - throw new NoSuchElementException("Iterator has no more Blocks."); - } - updateFollowingBlocks(currentBlock); - iteratedBlocks.add(currentBlock); - return currentBlock; - } + BasicBlock currentBlock = itBlock.next(); + updateFollowingBlocks(currentBlock, itFrame.getWeight()); + return currentBlock; + } - private void updateFollowingBlocks(BasicBlock currentBlock) { - // collect traps - final Stmt tailStmt = currentBlock.getTail(); - for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { - trapHandlerBlocks.addLast(trapHandlerBlock); - } - - final List> successors = currentBlock.getSuccessors(); - final int endIdx; - if (tailStmt.fallsThrough()) { - // handle the falls-through successor - assert (fallsTroughWorklist == null); - fallsTroughWorklist = successors.get(0); - endIdx = 1; - } else { - endIdx = 0; - } - - // handle the branching successor(s) - for (int i = successors.size() - 1; i >= endIdx; i--) { - // find the leader/beginning block of a continuous sequence of Blocks (connected via - // FallsThroughStmts) - final BasicBlock successorBlock = successors.get(i); - - Deque> blockSequence = calculateFallsThroughSequence(successorBlock); - boolean isSequenceTailExceptionFree = - blockSequence.getLast().getExceptionalSuccessors().isEmpty(); - - if (isSequenceTailExceptionFree) { - branchingBlockWorklist.addLast(blockSequence.getFirst()); - } else { - branchingBlockWorklist.addLast(blockSequence.getLast()); - } - } - } + private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight) { + // collect traps + final Stmt tailStmt = currentBlock.getTail(); - @Override - public boolean hasNext() { - final boolean hasIteratorMoreElements; - BasicBlock b = retrieveNextBlock(); - if (b != null) { - // reinsert at FIRST position -> not great for performance - but easier handling in - // next() - branchingBlockWorklist.addFirst(b); - hasIteratorMoreElements = true; - } else { - hasIteratorMoreElements = false; - } - - // "assertion" that all elements are iterated - if (!hasIteratorMoreElements) { - final int returnedSize = iteratedBlocks.size(); - final Collection> blocks = getBlocks(); - final int actualSize = blocks.size(); - if (returnedSize != actualSize) { - String info = - blocks.stream() - .filter(n -> !iteratedBlocks.contains(n)) - .map(BasicBlock::getStmts) - .collect(Collectors.toList()) - .toString(); - throw new IllegalStateException( - "There are " - + (actualSize - returnedSize) - + " Blocks that are not iterated! i.e. the StmtGraph is not connected from its startingStmt!" - + info - + DotExporter.createUrlToWebeditor(StmtGraph.this)); - } - } - return hasIteratorMoreElements; - } - } - - /** - * Returns the result of iterating through all Stmts in this body. All Stmts thus found are - * returned. Branching Stmts and statements which use PhiExpr will have Stmts; a Stmt contains a - * Stmt that is either a target of a branch or is being used as a pointer to the end of a CFG - * block. - * - *

This method was typically used for pointer patching, e.g. when the unit chain is cloned. - * - * @return A collection of all the Stmts that are targets of a BranchingStmt - */ - @Nonnull - public Collection getLabeledStmts() { - Set stmtList = new HashSet<>(); - for (Stmt stmt : getNodes()) { - if (stmt instanceof BranchingStmt) { - if (stmt instanceof JIfStmt) { - stmtList.add(getBranchTargetsOf((JIfStmt) stmt).get(JIfStmt.FALSE_BRANCH_IDX)); - } else if (stmt instanceof JGotoStmt) { - // [ms] bounds are validated in Body if its a valid StmtGraph - stmtList.add(getBranchTargetsOf((JGotoStmt) stmt).get(JGotoStmt.BRANCH_IDX)); - } else if (stmt instanceof JSwitchStmt) { - stmtList.addAll(getBranchTargetsOf((BranchingStmt) stmt)); - } - } - } + final List> successors = currentBlock.getSuccessors(); + final int endIdx = tailStmt.fallsThrough() ? 1 : 0; + + // handle the branching successor(s) + for (int i = successors.size() - 1; i >= endIdx; i--) { + // find the leader/beginning block of a continuous sequence of Blocks (connected via + // FallsThroughStmts) + final BasicBlock successorBlock = successors.get(i); - for (Trap trap : buildTraps()) { - stmtList.add(trap.getBeginStmt()); - stmtList.add(trap.getEndStmt()); - stmtList.add(trap.getHandlerStmt()); + if( seenTargets.contains(successorBlock) ){ + // already added / seen + continue; + } + + Deque> blockSequence = calculateFallsThroughSequence(successorBlock); + BasicBlock lastBlockOfSequence = blockSequence.getLast(); + boolean isSequenceTailExceptionFree = + lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); + + int newWeight; + if (isSequenceTailExceptionFree) { + if( lastBlockOfSequence.getTail() instanceof JReturnStmt || lastBlockOfSequence.getTail() instanceof JReturnVoidStmt ) { + // biggest number, yet - only bigger weight if there follows another JReturn(Void)Stmt + newWeight = ++currentWeight + getBlocks().size(); + }else{ + newWeight = getBlocks().size(); + } + } else { + newWeight = ++currentWeight; + } + worklist.add(new IteratorFrame(blockSequence, newWeight)); + } + + + for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { +// trapHandlerBlocks.addLast(trapHandlerBlock); + if(!seenTargets.contains(trapHandlerBlock)) { + worklist.add(new IteratorFrame(calculateFallsThroughSequence(trapHandlerBlock), ++currentWeight)); + } + } + } + + @Override + public boolean hasNext() { + // "assertion" that all elements are iterated + if (itBlock.hasNext()) { + return true; + } + + if (worklist.isEmpty()) { + final Collection> blocks = getBlocks(); + final int actualSize = blocks.size(); + if (seenTargets.size() != actualSize) { + String info = + blocks.stream() + .filter(n -> !seenTargets.contains(n)) + .map(BasicBlock::getStmts) + .collect(Collectors.toList()) + .toString(); + throw new IllegalStateException( + "There are " + + (actualSize - seenTargets.size()) + + " Blocks that are not iterated! i.e. the StmtGraph is not connected from its startingStmt!" + + info + + DotExporter.createUrlToWebeditor(StmtGraph.this)); + } + + return false; + } + return true; + } } - return stmtList; - } + /** + * Returns the result of iterating through all Stmts in this body. All Stmts thus found are + * returned. Branching Stmts and statements which use PhiExpr will have Stmts; a Stmt contains a + * Stmt that is either a target of a branch or is being used as a pointer to the end of a CFG + * block. + * + *

This method was typically used for pointer patching, e.g. when the unit chain is cloned. + * + * @return A collection of all the Stmts that are targets of a BranchingStmt + */ + @Nonnull + public Collection getLabeledStmts() { + Set stmtList = new HashSet<>(); + for (Stmt stmt : getNodes()) { + if (stmt instanceof BranchingStmt) { + if (stmt instanceof JIfStmt) { + stmtList.add(getBranchTargetsOf((JIfStmt) stmt).get(JIfStmt.FALSE_BRANCH_IDX)); + } else if (stmt instanceof JGotoStmt) { + // [ms] bounds are validated in Body if its a valid StmtGraph + stmtList.add(getBranchTargetsOf((JGotoStmt) stmt).get(JGotoStmt.BRANCH_IDX)); + } else if (stmt instanceof JSwitchStmt) { + stmtList.addAll(getBranchTargetsOf((BranchingStmt) stmt)); + } + } + } + + for (Trap trap : buildTraps()) { + stmtList.add(trap.getBeginStmt()); + stmtList.add(trap.getEndStmt()); + stmtList.add(trap.getHandlerStmt()); + } + + return stmtList; + } - @Override - public String toString() { - StringWriter writer = new StringWriter(); - try (PrintWriter writerOut = new PrintWriter(new EscapedWriter(writer))) { - new JimplePrinter().printTo(this, writerOut); + @Override + public String toString() { + StringWriter writer = new StringWriter(); + try (PrintWriter writerOut = new PrintWriter(new EscapedWriter(writer))) { + new JimplePrinter().printTo(this, writerOut); + } + return writer.toString(); } - return writer.toString(); - } } diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java new file mode 100644 index 00000000000..766b9308c6e --- /dev/null +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java @@ -0,0 +1,49 @@ +package sootup.java.bytecode; + +import categories.TestCategories; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import sootup.core.inputlocation.AnalysisInputLocation; +import sootup.core.jimple.basic.Trap; +import sootup.core.model.SourceType; +import sootup.core.signatures.MethodSignature; +import sootup.java.bytecode.inputlocation.PathBasedAnalysisInputLocation; +import sootup.java.core.views.JavaView; + +@Tag(TestCategories.JAVA_8_CATEGORY) +public class TryCatchFinallyTests { + + @Test + public void testTryWithResourcesFinally() { + Path classFilePath = Paths.get("../shared-test-resources/bugfixes/TryWithResourcesFinally.class"); + + AnalysisInputLocation inputLocation = + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( + classFilePath, "", SourceType.Application); + JavaView view = new JavaView(Collections.singletonList(inputLocation)); + + MethodSignature methodSignature = + view.getIdentifierFactory() + .parseMethodSignature(""); + List traps = view.getMethod(methodSignature).get().getBody().getTraps(); + } + + @Test + public void testNestedTryCatchFinally() { + Path classFilePath = Paths.get("../shared-test-resources/bugfixes/NestedTryCatchFinally.class"); + + AnalysisInputLocation inputLocation = + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( + classFilePath, "", SourceType.Application); + JavaView view = new JavaView(Collections.singletonList(inputLocation)); + + MethodSignature methodSignature = + view.getIdentifierFactory() + .parseMethodSignature(""); + List traps = view.getMethod(methodSignature).get().getBody().getTraps(); + } +} diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java index a4ad3f61580..41a2e49dc75 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java @@ -256,18 +256,29 @@ public List expectedBodyStmtsIfElseCascadingElseIfStatement() { "l1 := @parameter0: int", "l2 = 0", "if l1 >= 42 goto label3", - "if l1 >= 42 goto label1", - "l2 = 11", - "goto label4", + + "if l1 >= 42 goto label1", + + "l2 = 11", + "goto label4", + "label1:", "if l1 <= 123 goto label2", + "l2 = 12", "goto label4", + + + "label2:", "l2 = 13", "goto label4", + + + "label3:", "l2 = 2", + "label4:", "return l2") .collect(Collectors.toList()); @@ -300,18 +311,25 @@ public List expectedBodyStmtsIfElseCascadingElseIfInElseStatement() { "l1 := @parameter0: int", "l2 = 0", "if l1 >= 42 goto label1", - "l2 = 1", - "goto label4", + + "l2 = 1", + "goto label4", + "label1:", "if l1 >= 42 goto label2", - "l2 = 21", - "goto label4", + + "l2 = 21", + "goto label4", + "label2:", "if l1 <= 123 goto label3", - "l2 = 22", - "goto label4", + + "l2 = 22", + "goto label4", + "label3:", "l2 = 23", + "label4:", "return l2") .collect(Collectors.toList()); From 0452813dfc970de041412d23a671dfcedaa231ed Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Fri, 13 Sep 2024 14:19:34 +0200 Subject: [PATCH 08/14] reduced more ordering issues --- sootup.core/src/main/java/sootup/core/graph/StmtGraph.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 076bcd786b2..0a131707ede 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -638,10 +638,10 @@ private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight final Stmt tailStmt = currentBlock.getTail(); final List> successors = currentBlock.getSuccessors(); - final int endIdx = tailStmt.fallsThrough() ? 1 : 0; + final int startIdx = tailStmt.fallsThrough() ? 1 : 0; // handle the branching successor(s) - for (int i = successors.size() - 1; i >= endIdx; i--) { + for (int i = startIdx; i < successors.size(); i++) { // find the leader/beginning block of a continuous sequence of Blocks (connected via // FallsThroughStmts) final BasicBlock successorBlock = successors.get(i); From 9a7555b7ba1aafa40e280bd1dd08f5ed9f46d980 Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Fri, 13 Sep 2024 19:34:00 +0200 Subject: [PATCH 09/14] fix order for exceptions as well; progress: -2 failing --- .../java/sootup/core/graph/StmtGraph.java | 1242 ++++++++--------- .../jimple/common/stmt/FallsThroughStmt.java | 2 + .../java/bytecode/TryCatchFinallyTests.java | 11 +- .../java6/IfElseStatementTest.java | 36 +- 4 files changed, 632 insertions(+), 659 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index 0a131707ede..fd02af1f395 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -21,7 +21,15 @@ * #L% */ +import static sootup.core.jimple.common.stmt.FallsThroughStmt.FALLTSTHROUH_IDX; + import com.google.common.collect.Iterators; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import sootup.core.jimple.basic.Trap; import sootup.core.jimple.common.stmt.*; import sootup.core.jimple.javabytecode.stmt.JSwitchStmt; @@ -30,13 +38,6 @@ import sootup.core.util.EscapedWriter; import sootup.core.util.printer.JimplePrinter; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.*; -import java.util.stream.Collectors; - /** * Interface for control flow graphs on Jimple Stmts. A StmtGraph is directed and connected (except * for traphandlers - those are not connected to the unexceptional flow via StmtGraph). Its directed @@ -63,694 +64,681 @@ */ public abstract class StmtGraph> implements Iterable { - public abstract Stmt getStartingStmt(); - - public abstract BasicBlock getStartingStmtBlock(); - - /** - * returns the nodes in this graph in a non-deterministic order (->Set) to get the nodes in - * linearized, ordered manner use iterator() or getStmts. - */ - @Nonnull - public abstract Collection getNodes(); - - public List getStmts() { - final ArrayList res = new ArrayList<>(); - Iterators.addAll(res, iterator()); - return res; + public abstract Stmt getStartingStmt(); + + public abstract BasicBlock getStartingStmtBlock(); + + /** + * returns the nodes in this graph in a non-deterministic order (->Set) to get the nodes in + * linearized, ordered manner use iterator() or getStmts. + */ + @Nonnull + public abstract Collection getNodes(); + + public List getStmts() { + final ArrayList res = new ArrayList<>(); + Iterators.addAll(res, iterator()); + return res; + } + + @Nonnull + public abstract Collection> getBlocks(); + + @Nonnull + public abstract List> getBlocksSorted(); + + public Iterator> getBlockIterator() { + return new BlockGraphIterator(); + } + + public abstract BasicBlock getBlockOf(@Nonnull Stmt stmt); + + public abstract boolean containsNode(@Nonnull Stmt node); + + /** + * returns the ingoing flows to node as an List with no reliable/specific order and possibly + * duplicate entries i.e. if a JSwitchStmt has multiple cases that brnach to `node` + */ + @Nonnull + public abstract List predecessors(@Nonnull Stmt node); + + /** it is possible to reach traphandlers through inline code i.e. without any exceptional flow */ + @Nonnull + public abstract List exceptionalPredecessors(@Nonnull Stmt node); + + /** returns the outgoing flows of node as ordered List. The List can have duplicate entries! */ + @Nonnull + public abstract List successors(@Nonnull Stmt node); + + @Nonnull + public abstract Map exceptionalSuccessors(@Nonnull Stmt node); + + /** + * Collects all successors i.e. unexceptional and exceptional successors of a given stmt into a + * list. + * + * @param stmt in the given graph + * @return a list containing the unexceptional+exceptional successors of the given stmt + */ + @Nonnull + public List getAllSuccessors(@Nonnull Stmt stmt) { + final List successors = successors(stmt); + final Map exSuccessors = exceptionalSuccessors(stmt); + List allSuccessors = new ArrayList<>(successors.size() + exSuccessors.size()); + allSuccessors.addAll(successors); + allSuccessors.addAll(exSuccessors.values()); + return allSuccessors; + } + + /** returns the amount of ingoing flows into node */ + public abstract int inDegree(@Nonnull Stmt node); + + /** returns the amount of flows that start from node */ + public abstract int outDegree(@Nonnull Stmt node); + + /** returns the amount of flows with node as source or target. */ + public int degree(@Nonnull Stmt node) { + return inDegree(node) + outDegree(node); + } + + /** + * returns true if there is a flow between source and target throws an Exception if at least one + * of the parameters is not contained in the graph. + */ + public abstract boolean hasEdgeConnecting(@Nonnull Stmt source, @Nonnull Stmt target); + + /** + * returns a (reconstructed) list of traps like the traptable in the bytecode + * + *

Note: if you need exceptionional flow information in more augmented with the affected + * blocks/stmts and not just a (reconstructed, possibly more verbose) traptable - have a look at + * BasicBlock.getExceptionalSuccessor() + */ + public abstract List buildTraps(); + + /** + * returns a Collection of Stmts that leave the body (i.e. JReturnVoidStmt, JReturnStmt and + * JThrowStmt) + */ + @Nonnull + public List getTails() { + return getNodes().stream() + .filter(stmt -> stmt.getExpectedSuccessorCount() == 0) + .collect(Collectors.toList()); + } + + /** + * returns a Collection of all stmts in the graph that don't have an unexceptional ingoing flow or + * are the starting Stmt. + */ + @Nonnull + public Collection getEntrypoints() { + final ArrayList stmts = new ArrayList<>(); + stmts.add(getStartingStmt()); + // TODO: [ms] memory/performance: instead of gettraps(): iterate through all stmts and add + // startingStmt+@caughtexception/predecessors().size() == 0? + buildTraps().stream().map(Trap::getHandlerStmt).forEach(stmts::add); + return stmts; + } + + /** validates whether the each Stmt has the correct amount of outgoing flows. */ + public void validateStmtConnectionsInGraph() { + try { + + for (Stmt stmt : getNodes()) { + final List successors = successors(stmt); + final int successorCount = successors.size(); + + if (predecessors(stmt).isEmpty()) { + if (!(stmt == getStartingStmt() + || buildTraps().stream() + .map(Trap::getHandlerStmt) + .anyMatch(handler -> handler == stmt))) { + throw new IllegalStateException( + "Stmt '" + + stmt + + "' which is neither the StartingStmt nor a TrapHandler is missing a predecessor!"); + } + } + + if (stmt instanceof BranchingStmt) { + if (stmt instanceof JSwitchStmt) { + if (successorCount != ((JSwitchStmt) stmt).getValueCount()) { + throw new IllegalStateException( + stmt + + ": size of outgoing flows (i.e. " + + successorCount + + ") does not match the amount of JSwitchStmts case labels (i.e. " + + ((JSwitchStmt) stmt).getValueCount() + + ")."); + } + } else if (stmt instanceof JIfStmt) { + if (successorCount != 2) { + throw new IllegalStateException( + stmt + ": JIfStmt must have '2' outgoing flow but has '" + successorCount + "'."); + } + } else if (stmt instanceof JGotoStmt) { + if (successorCount != 1) { + throw new IllegalStateException( + stmt + ": JGoto must have '1' outgoing flow but has '" + successorCount + "'."); + } + } + + } else if (stmt instanceof JReturnStmt + || stmt instanceof JReturnVoidStmt + || stmt instanceof JThrowStmt) { + if (successorCount != 0) { + throw new IllegalStateException( + stmt + ": must have '0' outgoing flow but has '" + successorCount + "'."); + } + } else { + if (successorCount != 1) { + throw new IllegalStateException( + stmt + ": must have '1' outgoing flow but has '" + successorCount + "'."); + } + } + } + + } catch (Exception e) { + final String urlToWebeditor = DotExporter.createUrlToWebeditor(this); + throw new IllegalStateException("visualize invalid StmtGraph: " + urlToWebeditor, e); } - - @Nonnull - public abstract Collection> getBlocks(); - - @Nonnull - public abstract List> getBlocksSorted(); - - public Iterator> getBlockIterator() { - return new BlockGraphIterator(); + } + + /** + * Look for a path in graph, from def to use. This path has to lie inside an extended basic block + * (and this property implies uniqueness.). The path returned includes from and to. FIXME: ms: + * explain better + * + * @param from start point for the path. + * @param to end point for the path. + * @return null if there is no such path. + */ + @Nullable + public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull Stmt to) { + + // if this holds, we're doomed to failure!!! + if (inDegree(to) > 1) { + return null; } - public abstract BasicBlock getBlockOf(@Nonnull Stmt stmt); - - public abstract boolean containsNode(@Nonnull Stmt node); - - /** - * returns the ingoing flows to node as an List with no reliable/specific order and possibly - * duplicate entries i.e. if a JSwitchStmt has multiple cases that brnach to `node` - */ - @Nonnull - public abstract List predecessors(@Nonnull Stmt node); - - /** - * it is possible to reach traphandlers through inline code i.e. without any exceptional flow - */ - @Nonnull - public abstract List exceptionalPredecessors(@Nonnull Stmt node); - - /** - * returns the outgoing flows of node as ordered List. The List can have duplicate entries! - */ - @Nonnull - public abstract List successors(@Nonnull Stmt node); - - @Nonnull - public abstract Map exceptionalSuccessors(@Nonnull Stmt node); - - /** - * Collects all successors i.e. unexceptional and exceptional successors of a given stmt into a - * list. - * - * @param stmt in the given graph - * @return a list containing the unexceptional+exceptional successors of the given stmt - */ - @Nonnull - public List getAllSuccessors(@Nonnull Stmt stmt) { - final List successors = successors(stmt); - final Map exSuccessors = exceptionalSuccessors(stmt); - List allSuccessors = new ArrayList<>(successors.size() + exSuccessors.size()); - allSuccessors.addAll(successors); - allSuccessors.addAll(exSuccessors.values()); - return allSuccessors; + // pathStack := list of succs lists + // pathStackIndex := last visited index in pathStack + List pathStack = new ArrayList<>(); + List pathStackIndex = new ArrayList<>(); + + pathStack.add(from); + pathStackIndex.add(0); + + int psiMax = outDegree(pathStack.get(0)); + int level = 0; + while (pathStackIndex.get(0) != psiMax) { + int p = pathStackIndex.get(level); + + List succs = successors((pathStack.get(level))); + if (p >= succs.size()) { + // no more succs - backtrack to previous level. + + pathStack.remove(level); + pathStackIndex.remove(level); + + level--; + int q = pathStackIndex.get(level); + pathStackIndex.set(level, q + 1); + continue; + } + + Stmt betweenStmt = succs.get(p); + + // we win! + if (betweenStmt == to) { + pathStack.add(to); + return pathStack; + } + + // check preds of betweenStmt to see if we should visit its kids. + if (inDegree(betweenStmt) > 1) { + pathStackIndex.set(level, p + 1); + continue; + } + + // visit kids of betweenStmt. + level++; + pathStackIndex.add(0); + pathStack.add(betweenStmt); } + return null; + } - /** - * returns the amount of ingoing flows into node - */ - public abstract int inDegree(@Nonnull Stmt node); - - /** - * returns the amount of flows that start from node - */ - public abstract int outDegree(@Nonnull Stmt node); + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } - /** - * returns the amount of flows with node as source or target. - */ - public int degree(@Nonnull Stmt node) { - return inDegree(node) + outDegree(node); + if (!(o instanceof StmtGraph)) { + return false; } + StmtGraph otherGraph = (StmtGraph) o; - /** - * returns true if there is a flow between source and target throws an Exception if at least one - * of the parameters is not contained in the graph. - */ - public abstract boolean hasEdgeConnecting(@Nonnull Stmt source, @Nonnull Stmt target); + if (getStartingStmt() != otherGraph.getStartingStmt()) { + return false; + } - /** - * returns a (reconstructed) list of traps like the traptable in the bytecode - * - *

Note: if you need exceptionional flow information in more augmented with the affected - * blocks/stmts and not just a (reconstructed, possibly more verbose) traptable - have a look at - * BasicBlock.getExceptionalSuccessor() - */ - public abstract List buildTraps(); + Collection nodes = getNodes(); + final Collection otherNodes = otherGraph.getNodes(); + if (nodes.size() != otherNodes.size()) { + return false; + } - /** - * returns a Collection of Stmts that leave the body (i.e. JReturnVoidStmt, JReturnStmt and - * JThrowStmt) - */ - @Nonnull - public List getTails() { - return getNodes().stream() - .filter(stmt -> stmt.getExpectedSuccessorCount() == 0) - .collect(Collectors.toList()); + if (!buildTraps().equals(otherGraph.buildTraps())) { + return false; } - /** - * returns a Collection of all stmts in the graph that don't have an unexceptional ingoing flow or - * are the starting Stmt. - */ - @Nonnull - public Collection getEntrypoints() { - final ArrayList stmts = new ArrayList<>(); - stmts.add(getStartingStmt()); - // TODO: [ms] memory/performance: instead of gettraps(): iterate through all stmts and add - // startingStmt+@caughtexception/predecessors().size() == 0? - buildTraps().stream().map(Trap::getHandlerStmt).forEach(stmts::add); - return stmts; + for (Stmt node : nodes) { + if (!otherNodes.contains(node)) { + return false; + } + final List successors = successors(node); + final List otherSuccessors = otherGraph.successors(node); + if (!successors.equals(otherSuccessors)) { + return false; + } } - /** - * validates whether the each Stmt has the correct amount of outgoing flows. - */ - public void validateStmtConnectionsInGraph() { - try { - - for (Stmt stmt : getNodes()) { - final List successors = successors(stmt); - final int successorCount = successors.size(); - - if (predecessors(stmt).isEmpty()) { - if (!(stmt == getStartingStmt() - || buildTraps().stream() - .map(Trap::getHandlerStmt) - .anyMatch(handler -> handler == stmt))) { - throw new IllegalStateException( - "Stmt '" - + stmt - + "' which is neither the StartingStmt nor a TrapHandler is missing a predecessor!"); - } - } + return true; + } - if (stmt instanceof BranchingStmt) { - if (stmt instanceof JSwitchStmt) { - if (successorCount != ((JSwitchStmt) stmt).getValueCount()) { - throw new IllegalStateException( - stmt - + ": size of outgoing flows (i.e. " - + successorCount - + ") does not match the amount of JSwitchStmts case labels (i.e. " - + ((JSwitchStmt) stmt).getValueCount() - + ")."); - } - } else if (stmt instanceof JIfStmt) { - if (successorCount != 2) { - throw new IllegalStateException( - stmt + ": JIfStmt must have '2' outgoing flow but has '" + successorCount + "'."); - } - } else if (stmt instanceof JGotoStmt) { - if (successorCount != 1) { - throw new IllegalStateException( - stmt + ": JGoto must have '1' outgoing flow but has '" + successorCount + "'."); - } - } - - } else if (stmt instanceof JReturnStmt - || stmt instanceof JReturnVoidStmt - || stmt instanceof JThrowStmt) { - if (successorCount != 0) { - throw new IllegalStateException( - stmt + ": must have '0' outgoing flow but has '" + successorCount + "'."); - } - } else { - if (successorCount != 1) { - throw new IllegalStateException( - stmt + ": must have '1' outgoing flow but has '" + successorCount + "'."); - } - } - } + @Override + @Nonnull + public Iterator iterator() { + return new BlockStmtGraphIterator(); + } - } catch (Exception e) { - final String urlToWebeditor = DotExporter.createUrlToWebeditor(this); - throw new IllegalStateException("visualize invalid StmtGraph: " + urlToWebeditor, e); - } + public List getBranchTargetsOf(BranchingStmt fromStmt) { + final List successors = successors(fromStmt); + if (fromStmt instanceof JIfStmt) { + // remove the first successor as if its a fallsthrough stmt and not a branch target + return Collections.singletonList(successors.get(1)); + } + return successors; + } + + public boolean isStmtBranchTarget(@Nonnull Stmt targetStmt) { + final List predecessors = predecessors(targetStmt); + if (predecessors.size() > 1) { + // join node i.e. at least one is a branch + return true; } - /** - * Look for a path in graph, from def to use. This path has to lie inside an extended basic block - * (and this property implies uniqueness.). The path returned includes from and to. FIXME: ms: - * explain better - * - * @param from start point for the path. - * @param to end point for the path. - * @return null if there is no such path. - */ - @Nullable - public List getExtendedBasicBlockPathBetween(@Nonnull Stmt from, @Nonnull Stmt to) { - - // if this holds, we're doomed to failure!!! - if (inDegree(to) > 1) { - return null; + final Iterator iterator = predecessors.iterator(); + if (iterator.hasNext()) { + Stmt pred = iterator.next(); + if (pred.branches()) { + if (pred instanceof JIfStmt) { + // [ms] bounds are validated in Body + return getBranchTargetsOf((JIfStmt) pred).get(0) == targetStmt; } + return true; + } + } - // pathStack := list of succs lists - // pathStackIndex := last visited index in pathStack - List pathStack = new ArrayList<>(); - List pathStackIndex = new ArrayList<>(); - - pathStack.add(from); - pathStackIndex.add(0); - - int psiMax = outDegree(pathStack.get(0)); - int level = 0; - while (pathStackIndex.get(0) != psiMax) { - int p = pathStackIndex.get(level); - - List succs = successors((pathStack.get(level))); - if (p >= succs.size()) { - // no more succs - backtrack to previous level. - - pathStack.remove(level); - pathStackIndex.remove(level); - - level--; - int q = pathStackIndex.get(level); - pathStackIndex.set(level, q + 1); - continue; - } + return false; + } - Stmt betweenStmt = succs.get(p); + /** Iterates the Stmts according to the jimple output order. */ + private class BlockStmtGraphIterator implements Iterator { - // we win! - if (betweenStmt == to) { - pathStack.add(to); - return pathStack; - } + private final BlockGraphIterator blockIt; + @Nonnull private Iterator currentBlockIt = Collections.emptyIterator(); - // check preds of betweenStmt to see if we should visit its kids. - if (inDegree(betweenStmt) > 1) { - pathStackIndex.set(level, p + 1); - continue; - } + public BlockStmtGraphIterator() { + this(new BlockGraphIterator()); + } - // visit kids of betweenStmt. - level++; - pathStackIndex.add(0); - pathStack.add(betweenStmt); - } - return null; + public BlockStmtGraphIterator(@Nonnull BlockGraphIterator blockIterator) { + blockIt = blockIterator; } @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (!(o instanceof StmtGraph)) { - return false; - } - StmtGraph otherGraph = (StmtGraph) o; - - if (getStartingStmt() != otherGraph.getStartingStmt()) { - return false; - } - - Collection nodes = getNodes(); - final Collection otherNodes = otherGraph.getNodes(); - if (nodes.size() != otherNodes.size()) { - return false; - } - - if (!buildTraps().equals(otherGraph.buildTraps())) { - return false; - } - - for (Stmt node : nodes) { - if (!otherNodes.contains(node)) { - return false; - } - final List successors = successors(node); - final List otherSuccessors = otherGraph.successors(node); - if (!successors.equals(otherSuccessors)) { - return false; - } - } - - return true; + public boolean hasNext() { + // hint: a BasicBlock has at least 1 Stmt or should not be in a StmtGraph! + return currentBlockIt.hasNext() || blockIt.hasNext(); } @Override - @Nonnull - public Iterator iterator() { - return new BlockStmtGraphIterator(); + public Stmt next() { + if (!currentBlockIt.hasNext()) { + if (!blockIt.hasNext()) { + throw new NoSuchElementException("Iterator has no more Stmts."); + } + BasicBlock currentBlock = blockIt.next(); + currentBlockIt = currentBlock.getStmts().iterator(); + } + return currentBlockIt.next(); } + } - public List getBranchTargetsOf(BranchingStmt fromStmt) { - final List successors = successors(fromStmt); - if (fromStmt instanceof JIfStmt) { - // remove the first successor as if its a fallsthrough stmt and not a branch target - return Collections.singletonList(successors.get(1)); - } - return successors; - } + /** Iterates over the Blocks and collects/aggregates Trap information */ + public class BlockGraphIteratorAndTrapAggregator extends BlockGraphIterator { - public boolean isStmtBranchTarget(@Nonnull Stmt targetStmt) { - final List predecessors = predecessors(targetStmt); - if (predecessors.size() > 1) { - // join node i.e. at least one is a branch - return true; - } + @Nonnull private final List collectedTraps = new ArrayList<>(); - final Iterator iterator = predecessors.iterator(); - if (iterator.hasNext()) { - Stmt pred = iterator.next(); - if (pred.branches()) { - if (pred instanceof JIfStmt) { - // [ms] bounds are validated in Body - return getBranchTargetsOf((JIfStmt) pred).get(0) == targetStmt; - } - return true; - } - } + Map activeTraps = new HashMap<>(); + BasicBlock lastIteratedBlock; // dummy value to remove n-1 unnecessary null-checks - return false; + /* + * @param dummyBlock is just an empty instantiation of type V - as neither BasicBlock nor V instantiable we need a concrete object from the using subclass itclass. + * */ + public BlockGraphIteratorAndTrapAggregator(V dummyBlock) { + super(); + lastIteratedBlock = dummyBlock; } - /** - * Iterates the Stmts according to the jimple output order. - */ - private class BlockStmtGraphIterator implements Iterator { - - private final BlockGraphIterator blockIt; - @Nonnull - private Iterator currentBlockIt = Collections.emptyIterator(); - - public BlockStmtGraphIterator() { - this(new BlockGraphIterator()); - } - - public BlockStmtGraphIterator(@Nonnull BlockGraphIterator blockIterator) { - blockIt = blockIterator; - } - - @Override - public boolean hasNext() { - // hint: a BasicBlock has at least 1 Stmt or should not be in a StmtGraph! - return currentBlockIt.hasNext() || blockIt.hasNext(); - } - - @Override - public Stmt next() { - if (!currentBlockIt.hasNext()) { - if (!blockIt.hasNext()) { - throw new NoSuchElementException("Iterator has no more Stmts."); - } - BasicBlock currentBlock = blockIt.next(); - currentBlockIt = currentBlock.getStmts().iterator(); + @Nonnull + @Override + public BasicBlock next() { + final BasicBlock block = super.next(); + + final Map> currentBlocksExceptions = + block.getExceptionalSuccessors(); + final Map> lastBlocksExceptions = + lastIteratedBlock.getExceptionalSuccessors(); + + // former trap info is not in the current blocks info -> add it to the trap collection + lastBlocksExceptions.forEach( + (type, trapHandlerBlock) -> { + if (trapHandlerBlock != block.getExceptionalSuccessors().get(type)) { + final Stmt trapBeginStmt = activeTraps.remove(type); + if (trapBeginStmt == null) { + throw new IllegalStateException("Trap start for '" + type + "' is not in the Map!"); + } + // trapend is exclusive! + collectedTraps.add( + new Trap(type, trapBeginStmt, block.getHead(), trapHandlerBlock.getHead())); } - return currentBlockIt.next(); - } + }); + + // is there a new trap in the current block -> add it to currentTraps + block + .getExceptionalSuccessors() + .forEach( + (type, trapHandlerBlock) -> { + if (trapHandlerBlock != lastBlocksExceptions.get(type)) { + activeTraps.put(type, block.getHead()); + } + }); + + lastIteratedBlock = block; + return block; } /** - * Iterates over the Blocks and collects/aggregates Trap information + * for jimple serialization - this info contains only valid/useful information if all stmts are + * iterated i.e. hasNext() == false! + * + * @return List of Traps */ - public class BlockGraphIteratorAndTrapAggregator extends BlockGraphIterator { - - @Nonnull - private final List collectedTraps = new ArrayList<>(); - - Map activeTraps = new HashMap<>(); - BasicBlock lastIteratedBlock; // dummy value to remove n-1 unnecessary null-checks - - /* - * @param dummyBlock is just an empty instantiation of type V - as neither BasicBlock nor V instantiable we need a concrete object from the using subclass itclass. - * */ - public BlockGraphIteratorAndTrapAggregator(V dummyBlock) { - super(); - lastIteratedBlock = dummyBlock; - } - - @Nonnull - @Override - public BasicBlock next() { - final BasicBlock block = super.next(); - - final Map> currentBlocksExceptions = - block.getExceptionalSuccessors(); - final Map> lastBlocksExceptions = - lastIteratedBlock.getExceptionalSuccessors(); - - // former trap info is not in the current blocks info -> add it to the trap collection - lastBlocksExceptions.forEach( - (type, trapHandlerBlock) -> { - if (trapHandlerBlock != block.getExceptionalSuccessors().get(type)) { - final Stmt trapBeginStmt = activeTraps.remove(type); - if (trapBeginStmt == null) { - throw new IllegalStateException("Trap start for '" + type + "' is not in the Map!"); - } - // trapend is exclusive! - collectedTraps.add( - new Trap(type, trapBeginStmt, block.getHead(), trapHandlerBlock.getHead())); - } - }); - - // is there a new trap in the current block -> add it to currentTraps - block - .getExceptionalSuccessors() - .forEach( - (type, trapHandlerBlock) -> { - if (trapHandlerBlock != lastBlocksExceptions.get(type)) { - activeTraps.put(type, block.getHead()); - } - }); - - lastIteratedBlock = block; - return block; - } - - /** - * for jimple serialization - this info contains only valid/useful information if all stmts are - * iterated i.e. hasNext() == false! - * - * @return List of Traps - */ - public List getTraps() { + public List getTraps() { + + if (hasNext()) { + throw new IllegalStateException("Iterator needs to be iterated completely!"); + } + + // check for dangling traps that are not collected as the endStmt was not visited. + if (!activeTraps.isEmpty()) { + throw new IllegalArgumentException( + "Invalid StmtGraph. A Trap is not created as a traps endStmt was not visited during the iteration of all Stmts."); + } + return collectedTraps; + } + } - if (hasNext()) { - throw new IllegalStateException("Iterator needs to be iterated completely!"); - } + protected static class IteratorFrame + implements Comparable, Iterable> { + private final int weight; + private final Deque> sequence; - // check for dangling traps that are not collected as the endStmt was not visited. - if (!activeTraps.isEmpty()) { - throw new IllegalArgumentException( - "Invalid StmtGraph. A Trap is not created as a traps endStmt was not visited during the iteration of all Stmts."); - } - return collectedTraps; - } + protected IteratorFrame(Deque> sequence, int weight) { + this.weight = weight; + this.sequence = sequence; } + @Override + public int compareTo(IteratorFrame o) { + return Integer.compare(weight, o.weight); + } - protected static class IteratorFrame implements Comparable, Iterable> { - private final int weight; - private final Deque> sequence; + public int getWeight() { + return weight; + } - protected IteratorFrame(Deque> sequence, int weight) { - this.weight = weight; - this.sequence = sequence; - } + public int size() { + return sequence.size(); + } - @Override - public int compareTo(IteratorFrame o) { - return Integer.compare(weight, o.weight); - } + @Nonnull + @Override + public Iterator> iterator() { + return sequence.iterator(); + } + } - public int getWeight() { - return weight; - } + /** Iterates over the blocks */ + protected class BlockGraphIterator implements Iterator> { - public int size(){ - return sequence.size(); - } + private final PriorityQueue worklist = new PriorityQueue<>(); + private IteratorFrame itFrame; + private Iterator> itBlock; - @Nonnull - @Override - public Iterator> iterator() { - return sequence.iterator(); - } + @Nonnull + private final Map, Deque>> fallsThroughSequenceMap = + new IdentityHashMap<>(); + + @Nonnull private final Set> seenTargets; + private int iterations = 0; + + public BlockGraphIterator() { + final Collection> blocks = getBlocks(); + seenTargets = new LinkedHashSet<>(blocks.size(), 1); + Stmt startingStmt = getStartingStmt(); + if (startingStmt != null) { + final BasicBlock startingBlock = getStartingStmtBlock(); + itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0); + itBlock = itFrame.iterator(); + updateFollowingBlocks(startingBlock, 0); + } } - /** - * Iterates over the blocks - */ - protected class BlockGraphIterator implements Iterator> { - - private final PriorityQueue worklist = new PriorityQueue<>(); - private IteratorFrame itFrame; - private Iterator> itBlock; - @Nonnull - private final Map, Deque>> fallsThroughSequenceMap = new HashMap<>(); - @Nonnull - private final Set> seenTargets; - - public BlockGraphIterator() { - final Collection> blocks = getBlocks(); - seenTargets = new LinkedHashSet<>(blocks.size(), 1); - Stmt startingStmt = getStartingStmt(); - if (startingStmt != null) { - final BasicBlock startingBlock = getStartingStmtBlock(); - itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0); - itBlock = itFrame.iterator(); - updateFollowingBlocks(startingBlock, 0); - } - } + protected Deque> calculateFallsThroughSequence(@Nonnull BasicBlock param) { + Deque> basicBlockSequence = fallsThroughSequenceMap.get(param); + if (basicBlockSequence != null) { + return basicBlockSequence; + } + + Deque> blockSequence = new ArrayDeque<>(); + + BasicBlock continousBlockSequenceHeadCandidate = param; + // TODO: [ms] looks ugly.. simplify readability of the loop! + // find the leader of the Block Sequence (connected via FallsthroughStmts) + while (true) { + blockSequence.addFirst(continousBlockSequenceHeadCandidate); + final List> itPreds = + continousBlockSequenceHeadCandidate.getPredecessors(); + BasicBlock continousBlockTailCandidate = continousBlockSequenceHeadCandidate; + final Optional> fallsthroughPredOpt = + itPreds.stream() + .filter( + b -> + b.getTail().fallsThrough() + && b.getSuccessors().get(FALLTSTHROUH_IDX) + == continousBlockTailCandidate) + .findAny(); + if (!fallsthroughPredOpt.isPresent()) { + break; + } + BasicBlock predecessorBlock = fallsthroughPredOpt.get(); + if (predecessorBlock.getTail().fallsThrough() + && predecessorBlock.getSuccessors().get(FALLTSTHROUH_IDX) + == continousBlockSequenceHeadCandidate) { + // TODO: [ms] seems tautologic + continousBlockSequenceHeadCandidate = predecessorBlock; + } else { + break; + } + } + + // iterate to the end of the sequence + BasicBlock continousBlockSequenceTailCandidate = param; + while (continousBlockSequenceTailCandidate.getTail().fallsThrough()) { + continousBlockSequenceTailCandidate = + continousBlockSequenceTailCandidate.getSuccessors().get(0); + blockSequence.addLast(continousBlockSequenceTailCandidate); + } + + // cache calculated sequence for every block in the sequence + for (BasicBlock basicBlock : blockSequence) { + fallsThroughSequenceMap.put(basicBlock, blockSequence); + seenTargets.add(basicBlock); + } + return blockSequence; + } - protected Deque> calculateFallsThroughSequence(@Nonnull BasicBlock param) { - Deque> basicBlockSequence = fallsThroughSequenceMap.get(param); - if (basicBlockSequence != null) { - return basicBlockSequence; - } + @Override + @Nonnull + public BasicBlock next() { + if (!itBlock.hasNext()) { + itFrame = worklist.poll(); + if (itFrame == null) { + throw new NoSuchElementException("Iterator has no more Blocks."); + } + itBlock = itFrame.iterator(); + } + + BasicBlock currentBlock = itBlock.next(); + updateFollowingBlocks(currentBlock, itFrame.getWeight()); + System.out.println(iterations + " => " + currentBlock); + return currentBlock; + } - Deque> blockSequence = new ArrayDeque<>(); - - BasicBlock continousBlockSequenceHeadCandidate = param; - // TODO: [ms] looks ugly.. simplify readability of the loop! - // find the leader of the Block Sequence (connected via FallsthroughStmts) - while (true) { - blockSequence.addFirst(continousBlockSequenceHeadCandidate); - final List> itPreds = - continousBlockSequenceHeadCandidate.getPredecessors(); - BasicBlock continousBlockTailCandidate = continousBlockSequenceHeadCandidate; - final Optional> fallsthroughPredOpt = - itPreds.stream() - .filter( - b -> - b.getTail().fallsThrough() - && b.getSuccessors().get(0) == continousBlockTailCandidate) - .findAny(); - if (!fallsthroughPredOpt.isPresent()) { - break; - } - BasicBlock predecessorBlock = fallsthroughPredOpt.get(); - if (predecessorBlock.getTail().fallsThrough() - && predecessorBlock.getSuccessors().get(0) == continousBlockSequenceHeadCandidate) { - continousBlockSequenceHeadCandidate = predecessorBlock; - } else { - break; - } - } + private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight) { + // collect traps + final Stmt tailStmt = currentBlock.getTail(); - // iterate to the end of the sequence - BasicBlock continousBlockSequenceTailCandidate = param; - while (continousBlockSequenceTailCandidate.getTail().fallsThrough()) { - continousBlockSequenceTailCandidate = - continousBlockSequenceTailCandidate.getSuccessors().get(0); - blockSequence.addLast(continousBlockSequenceTailCandidate); - } + final List> successors = currentBlock.getSuccessors(); + final int startIdx = tailStmt.fallsThrough() ? 1 : 0; - // cache calculated sequence for every block in the sequence - for (BasicBlock basicBlock : blockSequence) { - fallsThroughSequenceMap.put(basicBlock, blockSequence); - seenTargets.add(basicBlock); - } - return blockSequence; - } + // handle the branching successor(s) + for (int i = startIdx; i < successors.size(); i++) { + // find the leader/beginning block of a continuous sequence of Blocks (connected via + // FallsThroughStmts) + final BasicBlock successorBlock = successors.get(i); - @Override - @Nonnull - public BasicBlock next() { - if(!itBlock.hasNext()){ - itFrame = worklist.poll(); - if (itFrame == null) { - throw new NoSuchElementException("Iterator has no more Blocks."); - } - itBlock = itFrame.iterator(); - } - - BasicBlock currentBlock = itBlock.next(); - updateFollowingBlocks(currentBlock, itFrame.getWeight()); - return currentBlock; + if (!seenTargets.contains(successorBlock)) { + // already added / seen + enqueueWeighted(currentWeight, successorBlock); } + } - private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight) { - // collect traps - final Stmt tailStmt = currentBlock.getTail(); - - final List> successors = currentBlock.getSuccessors(); - final int startIdx = tailStmt.fallsThrough() ? 1 : 0; - - // handle the branching successor(s) - for (int i = startIdx; i < successors.size(); i++) { - // find the leader/beginning block of a continuous sequence of Blocks (connected via - // FallsThroughStmts) - final BasicBlock successorBlock = successors.get(i); - - if( seenTargets.contains(successorBlock) ){ - // already added / seen - continue; - } - - Deque> blockSequence = calculateFallsThroughSequence(successorBlock); - BasicBlock lastBlockOfSequence = blockSequence.getLast(); - boolean isSequenceTailExceptionFree = - lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); - - int newWeight; - if (isSequenceTailExceptionFree) { - if( lastBlockOfSequence.getTail() instanceof JReturnStmt || lastBlockOfSequence.getTail() instanceof JReturnVoidStmt ) { - // biggest number, yet - only bigger weight if there follows another JReturn(Void)Stmt - newWeight = ++currentWeight + getBlocks().size(); - }else{ - newWeight = getBlocks().size(); - } - } else { - newWeight = ++currentWeight; - } - worklist.add(new IteratorFrame(blockSequence, newWeight)); - } - - - for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { -// trapHandlerBlocks.addLast(trapHandlerBlock); - if(!seenTargets.contains(trapHandlerBlock)) { - worklist.add(new IteratorFrame(calculateFallsThroughSequence(trapHandlerBlock), ++currentWeight)); - } - } + for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { + if (!seenTargets.contains(trapHandlerBlock)) { + enqueueWeighted(currentWeight, trapHandlerBlock); } + } + } - @Override - public boolean hasNext() { - // "assertion" that all elements are iterated - if (itBlock.hasNext()) { - return true; - } - - if (worklist.isEmpty()) { - final Collection> blocks = getBlocks(); - final int actualSize = blocks.size(); - if (seenTargets.size() != actualSize) { - String info = - blocks.stream() - .filter(n -> !seenTargets.contains(n)) - .map(BasicBlock::getStmts) - .collect(Collectors.toList()) - .toString(); - throw new IllegalStateException( - "There are " - + (actualSize - seenTargets.size()) - + " Blocks that are not iterated! i.e. the StmtGraph is not connected from its startingStmt!" - + info - + DotExporter.createUrlToWebeditor(StmtGraph.this)); - } - - return false; - } - return true; - } + private void enqueueWeighted(int currentWeight, BasicBlock successorBlock) { + Deque> blockSequence = calculateFallsThroughSequence(successorBlock); + BasicBlock lastBlockOfSequence = blockSequence.getLast(); + boolean isSequenceTailExceptionFree = + lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); + + int newWeight = iterations++; + if (isSequenceTailExceptionFree) { + if (lastBlockOfSequence.getTail() instanceof JReturnStmt + || lastBlockOfSequence.getTail() instanceof JReturnVoidStmt) { + // biggest number, yet - only bigger weight if there follows another JReturn(Void)Stmt + newWeight += getBlocks().size() * 2; + } else { + newWeight += getBlocks().size(); + } + } + worklist.add(new IteratorFrame(blockSequence, newWeight)); } - /** - * Returns the result of iterating through all Stmts in this body. All Stmts thus found are - * returned. Branching Stmts and statements which use PhiExpr will have Stmts; a Stmt contains a - * Stmt that is either a target of a branch or is being used as a pointer to the end of a CFG - * block. - * - *

This method was typically used for pointer patching, e.g. when the unit chain is cloned. - * - * @return A collection of all the Stmts that are targets of a BranchingStmt - */ - @Nonnull - public Collection getLabeledStmts() { - Set stmtList = new HashSet<>(); - for (Stmt stmt : getNodes()) { - if (stmt instanceof BranchingStmt) { - if (stmt instanceof JIfStmt) { - stmtList.add(getBranchTargetsOf((JIfStmt) stmt).get(JIfStmt.FALSE_BRANCH_IDX)); - } else if (stmt instanceof JGotoStmt) { - // [ms] bounds are validated in Body if its a valid StmtGraph - stmtList.add(getBranchTargetsOf((JGotoStmt) stmt).get(JGotoStmt.BRANCH_IDX)); - } else if (stmt instanceof JSwitchStmt) { - stmtList.addAll(getBranchTargetsOf((BranchingStmt) stmt)); - } - } - } + @Override + public boolean hasNext() { + // "assertion" that all elements are iterated + if (itBlock.hasNext()) { + return true; + } - for (Trap trap : buildTraps()) { - stmtList.add(trap.getBeginStmt()); - stmtList.add(trap.getEndStmt()); - stmtList.add(trap.getHandlerStmt()); - } + if (!worklist.isEmpty()) { + return true; + } + + final Collection> blocks = getBlocks(); + final int actualSize = blocks.size(); + if (seenTargets.size() != actualSize) { + String info = + blocks.stream() + .filter(n -> !seenTargets.contains(n)) + .map(BasicBlock::getStmts) + .collect(Collectors.toList()) + .toString(); + throw new IllegalStateException( + "There are " + + (actualSize - seenTargets.size()) + + " Blocks that are not iterated! i.e. the StmtGraph is not connected from its startingStmt!" + + info + + DotExporter.createUrlToWebeditor(StmtGraph.this)); + } + + return false; + } + } + + /** + * Returns the result of iterating through all Stmts in this body. All Stmts thus found are + * returned. Branching Stmts and statements which use PhiExpr will have Stmts; a Stmt contains a + * Stmt that is either a target of a branch or is being used as a pointer to the end of a CFG + * block. + * + *

This method was typically used for pointer patching, e.g. when the unit chain is cloned. + * + * @return A collection of all the Stmts that are targets of a BranchingStmt + */ + @Nonnull + public Collection getLabeledStmts() { + Set stmtList = new HashSet<>(); + for (Stmt stmt : getNodes()) { + if (stmt instanceof BranchingStmt) { + if (stmt instanceof JIfStmt) { + stmtList.add(getBranchTargetsOf((JIfStmt) stmt).get(JIfStmt.FALSE_BRANCH_IDX)); + } else if (stmt instanceof JGotoStmt) { + // [ms] bounds are validated in Body if its a valid StmtGraph + stmtList.add(getBranchTargetsOf((JGotoStmt) stmt).get(JGotoStmt.BRANCH_IDX)); + } else if (stmt instanceof JSwitchStmt) { + stmtList.addAll(getBranchTargetsOf((BranchingStmt) stmt)); + } + } + } - return stmtList; + for (Trap trap : buildTraps()) { + stmtList.add(trap.getBeginStmt()); + stmtList.add(trap.getEndStmt()); + stmtList.add(trap.getHandlerStmt()); } - @Override - public String toString() { - StringWriter writer = new StringWriter(); - try (PrintWriter writerOut = new PrintWriter(new EscapedWriter(writer))) { - new JimplePrinter().printTo(this, writerOut); - } - return writer.toString(); + return stmtList; + } + + @Override + public String toString() { + StringWriter writer = new StringWriter(); + try (PrintWriter writerOut = new PrintWriter(new EscapedWriter(writer))) { + new JimplePrinter().printTo(this, writerOut); } + return writer.toString(); + } } diff --git a/sootup.core/src/main/java/sootup/core/jimple/common/stmt/FallsThroughStmt.java b/sootup.core/src/main/java/sootup/core/jimple/common/stmt/FallsThroughStmt.java index 0568cdcaa4a..a205a0ecb3e 100644 --- a/sootup.core/src/main/java/sootup/core/jimple/common/stmt/FallsThroughStmt.java +++ b/sootup.core/src/main/java/sootup/core/jimple/common/stmt/FallsThroughStmt.java @@ -25,6 +25,8 @@ /** as an equivalent to BranchingStmt */ public interface FallsThroughStmt extends Stmt { + int FALLTSTHROUH_IDX = 0; + // has to return true in subclasses! // hint: this class can't be abstract and method final because of JIfStmt which would need // FallsThrough and BranchingStmt as parent. diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java index 766b9308c6e..ff7d789d752 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java @@ -19,7 +19,8 @@ public class TryCatchFinallyTests { @Test public void testTryWithResourcesFinally() { - Path classFilePath = Paths.get("../shared-test-resources/bugfixes/TryWithResourcesFinally.class"); + Path classFilePath = + Paths.get("../shared-test-resources/bugfixes/TryWithResourcesFinally.class"); AnalysisInputLocation inputLocation = new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( @@ -37,13 +38,13 @@ public void testNestedTryCatchFinally() { Path classFilePath = Paths.get("../shared-test-resources/bugfixes/NestedTryCatchFinally.class"); AnalysisInputLocation inputLocation = - new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( - classFilePath, "", SourceType.Application); + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( + classFilePath, "", SourceType.Application); JavaView view = new JavaView(Collections.singletonList(inputLocation)); MethodSignature methodSignature = - view.getIdentifierFactory() - .parseMethodSignature(""); + view.getIdentifierFactory() + .parseMethodSignature(""); List traps = view.getMethod(methodSignature).get().getBody().getTraps(); } } diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java index 41a2e49dc75..a4ad3f61580 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/IfElseStatementTest.java @@ -256,29 +256,18 @@ public List expectedBodyStmtsIfElseCascadingElseIfStatement() { "l1 := @parameter0: int", "l2 = 0", "if l1 >= 42 goto label3", - - "if l1 >= 42 goto label1", - - "l2 = 11", - "goto label4", - + "if l1 >= 42 goto label1", + "l2 = 11", + "goto label4", "label1:", "if l1 <= 123 goto label2", - "l2 = 12", "goto label4", - - - "label2:", "l2 = 13", "goto label4", - - - "label3:", "l2 = 2", - "label4:", "return l2") .collect(Collectors.toList()); @@ -311,25 +300,18 @@ public List expectedBodyStmtsIfElseCascadingElseIfInElseStatement() { "l1 := @parameter0: int", "l2 = 0", "if l1 >= 42 goto label1", - - "l2 = 1", - "goto label4", - + "l2 = 1", + "goto label4", "label1:", "if l1 >= 42 goto label2", - - "l2 = 21", - "goto label4", - + "l2 = 21", + "goto label4", "label2:", "if l1 <= 123 goto label3", - - "l2 = 22", - "goto label4", - + "l2 = 22", + "goto label4", "label3:", "l2 = 23", - "label4:", "return l2") .collect(Collectors.toList()); From 2424032c9bb90f83aa16de067b19b4682de730d0 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Sun, 22 Sep 2024 19:05:52 +0200 Subject: [PATCH 10/14] added tc --- .../bugfixes/LineIterator.class | Bin 0 -> 1954 bytes .../bugfixes/LineIterator.java | 85 ++++++++++++++++++ .../java/bytecode/TryCatchFinallyTests.java | 21 +++++ 3 files changed, 106 insertions(+) create mode 100644 shared-test-resources/bugfixes/LineIterator.class create mode 100644 shared-test-resources/bugfixes/LineIterator.java diff --git a/shared-test-resources/bugfixes/LineIterator.class b/shared-test-resources/bugfixes/LineIterator.class new file mode 100644 index 0000000000000000000000000000000000000000..d8195b9951107107fcaabb3fd37799faf945a4a2 GIT binary patch literal 1954 zcmZuyZBrXn6n<_J*sv^tq?9*>rt}RGD6Os57K*%-wrB{JP&DY5C0t>)&5~1@IJK>xdw#A!eWn z%>o^7%(v#4Z91D{i)(LW$roszupG;u6o?KDuV{!14Azb2ZCh@d_O!QI*_Mt!w_B1s zzU4Y`=mOnK(p;Bbdb?8g(~j$>*JRqM*tQN0h6>mwaCInG_g3&d%h?=PVcG?{f(^?Z zo2hJUNKdYZ?plyUO2ZWcowzDsJ0pyKoycn|Lj=q0nXCD&Cbfz)Nh%67oOHKYaN8|14@YXPyUf(#nCft%d$NJr?R zb7=Uo6P-?O8@PiZfmW-$V%k>C{C`PW)sP|bQB5AitnHSi%AKcX z-F1p~m2tkZy(YcoaHEYpmcCgqcY<#XIX2zt`LKD|FK0N*8WzuuRoLSKO(hkJ;}zz> zMh+CODwg!^?}%wAr$V3NWHIRWncVSYiDhJS^)6K$-`bWdR@oxrX@@KX9F;jb4c%2g zGfu|NHC{O4RJRnYO~>>r9sw;hoUIvznxLeocqSUGYGXS&9_ku-q;I|8R=kpYZYfri zjXB*_wBb5S72)q0o+FBsnrdE65jA*$i{nyppA|ah@+i>lj!AolK}29 z(5Ku@B6>nqL_<=;XZ&u(0@X2M(fDuzxGFsFh&)O;mH8RHf1oM0&zJr^G)2`*$04rg zh*HnQ4$x0k_7J!BPX5kh56~Oi&t(^mkS&f>F?vjJ3c^DS;}#wSGKBktFhNcf+3w(3N0HY8hrSyl`r1 z^ie8WBe8w1BH*c+5_BchFmYAs_ERctQ|1u&e!O7%m|MIsYNmPeqFnaV^uhP4P>+hK z$5o6UE2?rCK?<_UMVMw6i>&^aG)xlU6uL0Yq-TN@q7cN4&(Ta{1tUwTV6+Cj2Ch_n zL)?j=ATQ|eC3K!YE!;5oU(rRF$snzi3;hTa#nfaKQ^#i>!ps*)_Yw-UQz3hUUB;*f p3eA?W%q`$GAEj9VI;Kh47qvi*lucixEY3u}, Closeable { + private final BufferedReader bufferedReader; + private String cachedLine; + private boolean finished; + + public LineIterator(Reader reader) throws IllegalArgumentException { + if (reader == null) { + throw new IllegalArgumentException("Reader must not be null"); + } else { + if (reader instanceof BufferedReader) { + this.bufferedReader = (BufferedReader)reader; + } else { + this.bufferedReader = new BufferedReader(reader); + } + + } + } + + public boolean hasNext() { + if (this.cachedLine != null) { + return true; + } else if (this.finished) { + return false; + } else { + try { + String line; + do { + line = this.bufferedReader.readLine(); + if (line == null) { + this.finished = true; + return false; + } + } while(!this.isValidLine(line)); + + this.cachedLine = line; + return true; + } catch (IOException var2) { + IOException ioe = var2; + // IOUtils.closeQuietly(this, ioe::addSuppressed); + throw new IllegalStateException(ioe); + } + } + } + + protected boolean isValidLine(String line) { + return true; + } + + public String next() { + return this.nextLine(); + } + + public String nextLine() { + if (!this.hasNext()) { + throw new NoSuchElementException("No more lines"); + } else { + String currentLine = this.cachedLine; + this.cachedLine = null; + return currentLine; + } + } + + public void close() throws IOException { + this.finished = true; + this.cachedLine = null; + // IOUtils.close(this.bufferedReader); + } + + public void remove() { + throw new UnsupportedOperationException("remove not supported"); + } + + /** @deprecated */ + @Deprecated + public static void closeQuietly(LineIterator iterator) { + // IOUtils.closeQuietly(iterator); + } +} diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java index ff7d789d752..5c0dd9f2195 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/TryCatchFinallyTests.java @@ -3,8 +3,11 @@ import categories.TestCategories; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.List; + +import com.google.common.collect.Lists; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import sootup.core.inputlocation.AnalysisInputLocation; @@ -12,6 +15,8 @@ import sootup.core.model.SourceType; import sootup.core.signatures.MethodSignature; import sootup.java.bytecode.inputlocation.PathBasedAnalysisInputLocation; +import sootup.java.core.interceptors.LocalSplitter; +import sootup.java.core.interceptors.TypeAssigner; import sootup.java.core.views.JavaView; @Tag(TestCategories.JAVA_8_CATEGORY) @@ -47,4 +52,20 @@ public void testNestedTryCatchFinally() { .parseMethodSignature(""); List traps = view.getMethod(methodSignature).get().getBody().getTraps(); } + + @Test + public void testTryCatchFinallyIterator() { + Path classFilePath = Paths.get("../shared-test-resources/bugfixes/LineIterator.class"); + + AnalysisInputLocation inputLocation = + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( + classFilePath, "", SourceType.Application, Arrays.asList(new LocalSplitter(), new TypeAssigner())); + JavaView view = new JavaView(Collections.singletonList(inputLocation)); + view.getClasses().forEach(clazz -> { + clazz.getMethods().forEach(method -> { + view.getMethod(method.getSignature()).get().getBody().getTraps(); + }); + }); + } + } From 6577ab86e57d35e114c26cec3e8d0b91c6c8d7ba Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Tue, 17 Sep 2024 10:36:03 +0200 Subject: [PATCH 11/14] just 7 testcases of minimaltestsuite failing --- .../java/sootup/core/graph/StmtGraph.java | 44 ++++++++++++------- .../java6/LabelledLoopBreakTest.java | 3 ++ .../java6/SwitchCaseStatementTest.java | 5 +++ .../java6/TryCatchFinallyTest.java | 12 +++-- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index fd02af1f395..b5d88a1f582 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -23,6 +23,7 @@ import static sootup.core.jimple.common.stmt.FallsThroughStmt.FALLTSTHROUH_IDX; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import java.io.PrintWriter; import java.io.StringWriter; @@ -496,21 +497,27 @@ public List getTraps() { protected static class IteratorFrame implements Comparable, Iterable> { - private final int weight; + private final int weightSum; + private final int weightA; private final Deque> sequence; - protected IteratorFrame(Deque> sequence, int weight) { - this.weight = weight; + protected IteratorFrame(Deque> sequence, int weightA, int weightSum) { + this.weightA = weightA; + this.weightSum = weightSum; this.sequence = sequence; } @Override public int compareTo(IteratorFrame o) { - return Integer.compare(weight, o.weight); + return Integer.compare(weightSum, o.weightSum); } - public int getWeight() { - return weight; + public int getWeightA() { + return weightA; + } + + public int getWeightSum() { + return weightSum; } public int size() { @@ -539,12 +546,13 @@ protected class BlockGraphIterator implements Iterator> { private int iterations = 0; public BlockGraphIterator() { + System.out.println("==============0"); final Collection> blocks = getBlocks(); seenTargets = new LinkedHashSet<>(blocks.size(), 1); Stmt startingStmt = getStartingStmt(); if (startingStmt != null) { final BasicBlock startingBlock = getStartingStmtBlock(); - itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0); + itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0, 0); itBlock = itFrame.iterator(); updateFollowingBlocks(startingBlock, 0); } @@ -616,15 +624,14 @@ public BasicBlock next() { } BasicBlock currentBlock = itBlock.next(); - updateFollowingBlocks(currentBlock, itFrame.getWeight()); - System.out.println(iterations + " => " + currentBlock); + System.out.println(++iterations + ": "); + updateFollowingBlocks(currentBlock, itFrame.getWeightA()); return currentBlock; } private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight) { - // collect traps - final Stmt tailStmt = currentBlock.getTail(); + final Stmt tailStmt = currentBlock.getTail(); final List> successors = currentBlock.getSuccessors(); final int startIdx = tailStmt.fallsThrough() ? 1 : 0; @@ -636,7 +643,7 @@ private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight if (!seenTargets.contains(successorBlock)) { // already added / seen - enqueueWeighted(currentWeight, successorBlock); + enqueueWeighted(currentWeight++, successorBlock); } } @@ -648,22 +655,27 @@ private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight } private void enqueueWeighted(int currentWeight, BasicBlock successorBlock) { + iterations++; Deque> blockSequence = calculateFallsThroughSequence(successorBlock); BasicBlock lastBlockOfSequence = blockSequence.getLast(); boolean isSequenceTailExceptionFree = lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); - int newWeight = iterations++; + int newWeightA = currentWeight; + int categoryWeight = 0; if (isSequenceTailExceptionFree) { if (lastBlockOfSequence.getTail() instanceof JReturnStmt || lastBlockOfSequence.getTail() instanceof JReturnVoidStmt) { // biggest number, yet - only bigger weight if there follows another JReturn(Void)Stmt - newWeight += getBlocks().size() * 2; + categoryWeight = 2; } else { - newWeight += getBlocks().size(); + categoryWeight = 1; } } - worklist.add(new IteratorFrame(blockSequence, newWeight)); + + IteratorFrame frame = new IteratorFrame(blockSequence, newWeightA, -iterations + newWeightA * getBlocks().size() + categoryWeight * getBlocks().size() * getBlocks().size()); + worklist.add(frame); + System.out.println(frame.weightA+","+frame.weightSum + "=> " + blockSequence); } @Override diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/LabelledLoopBreakTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/LabelledLoopBreakTest.java index 6e9d946cbe0..cbd72b18ab7 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/LabelledLoopBreakTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/LabelledLoopBreakTest.java @@ -46,12 +46,15 @@ public List expectedBodyStmts() { "if l2 >= 5 goto label4", "if l1 != 1 goto label3", "goto label5", + "label3:", "l2 = l2 + 1", "goto label2", + "label4:", "l1 = l1 + 1", "goto label1", + "label5:", "return") .collect(Collectors.toList()); diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/SwitchCaseStatementTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/SwitchCaseStatementTest.java index 51bad2ddc0a..f2273b7f31d 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/SwitchCaseStatementTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/SwitchCaseStatementTest.java @@ -153,20 +153,25 @@ public void switchCaseStatementCaseIncludingIf() { "case 2: goto label3", "case 3: goto label4", "default: goto label5", + "label1:", "l2 = 1", "if l1 != 666 goto label2", "l2 = 11", "goto label6", + "label2:", "l2 = 12", "goto label6", + "label3:", "l2 = 2", "goto label6", + "label4:", "l2 = 3", "goto label6", + "label5:", "l2 = -1", "label6:", diff --git a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/TryCatchFinallyTest.java b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/TryCatchFinallyTest.java index 9d52baf8fba..691060be2d1 100644 --- a/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/TryCatchFinallyTest.java +++ b/sootup.java.bytecode/src/test/java/sootup/java/bytecode/minimaltestsuite/java6/TryCatchFinallyTest.java @@ -139,23 +139,25 @@ public List expectedBodyStmtsTryCatchFinally() { "label1:", "l1 = \"try\"", "$stack4 = ", - "virtualinvoke $stack4.(l1)", + "virtualinvoke $stack4.(l1)", // <-- endrange:handler:label3,label5 "label2:", "l1 = \"finally\"", "$stack5 = ", "virtualinvoke $stack5.(l1)", - "goto label6", + "goto label6", // <-- label6 + "label3:", "$stack8 := @caughtexception", "l2 = $stack8", "l1 = \"catch\"", "$stack9 = ", - "virtualinvoke $stack9.(l1)", + "virtualinvoke $stack9.(l1)", // <-- endrange:handler:label5 "label4:", "l1 = \"finally\"", "$stack10 = ", "virtualinvoke $stack10.(l1)", - "goto label6", + "goto label6", // <-- label6 + "label5:", "$stack6 := @caughtexception", "l3 = $stack6", @@ -163,8 +165,10 @@ public List expectedBodyStmtsTryCatchFinally() { "$stack7 = ", "virtualinvoke $stack7.(l1)", "throw l3", + "label6:", "return", + "catch java.lang.Exception from label1 to label2 with label3", "catch java.lang.Throwable from label1 to label2 with label5", "catch java.lang.Throwable from label3 to label4 with label5") From 92a3033543867b15a2ed63732eb80f02c7d527b4 Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Thu, 19 Sep 2024 19:28:46 +0200 Subject: [PATCH 12/14] 8 testcases of minimaltestsuite failing - exceptional ones --- .../java/sootup/core/graph/StmtGraph.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index b5d88a1f582..a8a47d9d826 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -23,7 +23,6 @@ import static sootup.core.jimple.common.stmt.FallsThroughStmt.FALLTSTHROUH_IDX; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import java.io.PrintWriter; import java.io.StringWriter; @@ -498,11 +497,9 @@ public List getTraps() { protected static class IteratorFrame implements Comparable, Iterable> { private final int weightSum; - private final int weightA; private final Deque> sequence; - protected IteratorFrame(Deque> sequence, int weightA, int weightSum) { - this.weightA = weightA; + protected IteratorFrame(Deque> sequence, int weightSum) { this.weightSum = weightSum; this.sequence = sequence; } @@ -512,11 +509,7 @@ public int compareTo(IteratorFrame o) { return Integer.compare(weightSum, o.weightSum); } - public int getWeightA() { - return weightA; - } - - public int getWeightSum() { + public int getWeight() { return weightSum; } @@ -544,6 +537,8 @@ protected class BlockGraphIterator implements Iterator> { @Nonnull private final Set> seenTargets; private int iterations = 0; + private int minValue = 0; + private int maxValue = 0; public BlockGraphIterator() { System.out.println("==============0"); @@ -552,7 +547,7 @@ public BlockGraphIterator() { Stmt startingStmt = getStartingStmt(); if (startingStmt != null) { final BasicBlock startingBlock = getStartingStmtBlock(); - itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0, 0); + itFrame = new IteratorFrame(calculateFallsThroughSequence(startingBlock), 0); itBlock = itFrame.iterator(); updateFollowingBlocks(startingBlock, 0); } @@ -625,7 +620,7 @@ public BasicBlock next() { BasicBlock currentBlock = itBlock.next(); System.out.println(++iterations + ": "); - updateFollowingBlocks(currentBlock, itFrame.getWeightA()); + updateFollowingBlocks(currentBlock, itFrame.getWeight()); return currentBlock; } @@ -641,41 +636,49 @@ private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight // FallsThroughStmts) final BasicBlock successorBlock = successors.get(i); - if (!seenTargets.contains(successorBlock)) { - // already added / seen - enqueueWeighted(currentWeight++, successorBlock); - } + enqueueWeighted(currentWeight+=getBlocks().size(), currentBlock, successorBlock); } for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { - if (!seenTargets.contains(trapHandlerBlock)) { - enqueueWeighted(currentWeight, trapHandlerBlock); - } + enqueueWeighted(currentWeight+=getBlocks().size(), currentBlock, trapHandlerBlock); } } - private void enqueueWeighted(int currentWeight, BasicBlock successorBlock) { + private void enqueueWeighted(int currentWeight, BasicBlock currentBlock, BasicBlock successorBlock) { + if (seenTargets.contains(successorBlock)) { + return; + } + iterations++; Deque> blockSequence = calculateFallsThroughSequence(successorBlock); BasicBlock lastBlockOfSequence = blockSequence.getLast(); boolean isSequenceTailExceptionFree = lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); - int newWeightA = currentWeight; - int categoryWeight = 0; + + if(currentBlock.getTail() instanceof JIfStmt && currentBlock.getTail() instanceof JSwitchStmt){ + // currentWeight = minValue-getBlocks().size(); + } else if(currentBlock.getTail() instanceof JGotoStmt){ + // currentWeight++; + } + + + int categoryWeight = 2; if (isSequenceTailExceptionFree) { if (lastBlockOfSequence.getTail() instanceof JReturnStmt || lastBlockOfSequence.getTail() instanceof JReturnVoidStmt) { // biggest number, yet - only bigger weight if there follows another JReturn(Void)Stmt - categoryWeight = 2; + categoryWeight = 0; } else { categoryWeight = 1; } } - IteratorFrame frame = new IteratorFrame(blockSequence, newWeightA, -iterations + newWeightA * getBlocks().size() + categoryWeight * getBlocks().size() * getBlocks().size()); + IteratorFrame frame = new IteratorFrame(blockSequence, (currentWeight-iterations) - categoryWeight*getBlocks().size() ); worklist.add(frame); - System.out.println(frame.weightA+","+frame.weightSum + "=> " + blockSequence); + minValue = Math.min(frame.getWeight(), minValue); + maxValue = Math.max(frame.getWeight(), maxValue); + System.out.println("weight: " +(currentWeight-iterations)+ " it:"+iterations + " sum:" +frame.weightSum + " :: " +currentBlock +" => " + blockSequence); } @Override From 5dad178667aac19b87d24396657c4f6766b212c1 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Mon, 30 Sep 2024 14:08:50 +0200 Subject: [PATCH 13/14] merged with develop --- .../java/sootup/core/graph/StmtGraph.java | 32 ++++++++++++------ .../frontend/TryCatchFinallyTests.java | 33 +++++++++++-------- .../ConditionalBranchFolderTest.java | 15 +++++++-- .../java6/LabelledLoopBreakTest.java | 3 -- .../java6/SwitchCaseStatementTest.java | 5 --- .../java6/TryCatchFinallyTest.java | 12 +++---- 6 files changed, 57 insertions(+), 43 deletions(-) diff --git a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java index a8a47d9d826..28f886b83cd 100644 --- a/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java +++ b/sootup.core/src/main/java/sootup/core/graph/StmtGraph.java @@ -636,15 +636,16 @@ private void updateFollowingBlocks(BasicBlock currentBlock, int currentWeight // FallsThroughStmts) final BasicBlock successorBlock = successors.get(i); - enqueueWeighted(currentWeight+=getBlocks().size(), currentBlock, successorBlock); + enqueueWeighted(currentWeight += getBlocks().size(), currentBlock, successorBlock); } for (BasicBlock trapHandlerBlock : currentBlock.getExceptionalSuccessors().values()) { - enqueueWeighted(currentWeight+=getBlocks().size(), currentBlock, trapHandlerBlock); + enqueueWeighted(currentWeight += getBlocks().size(), currentBlock, trapHandlerBlock); } } - private void enqueueWeighted(int currentWeight, BasicBlock currentBlock, BasicBlock successorBlock) { + private void enqueueWeighted( + int currentWeight, BasicBlock currentBlock, BasicBlock successorBlock) { if (seenTargets.contains(successorBlock)) { return; } @@ -655,14 +656,13 @@ private void enqueueWeighted(int currentWeight, BasicBlock currentBlock, Basi boolean isSequenceTailExceptionFree = lastBlockOfSequence.getExceptionalSuccessors().isEmpty(); - - if(currentBlock.getTail() instanceof JIfStmt && currentBlock.getTail() instanceof JSwitchStmt){ - // currentWeight = minValue-getBlocks().size(); - } else if(currentBlock.getTail() instanceof JGotoStmt){ + if (currentBlock.getTail() instanceof JIfStmt + && currentBlock.getTail() instanceof JSwitchStmt) { + // currentWeight = minValue-getBlocks().size(); + } else if (currentBlock.getTail() instanceof JGotoStmt) { // currentWeight++; } - int categoryWeight = 2; if (isSequenceTailExceptionFree) { if (lastBlockOfSequence.getTail() instanceof JReturnStmt @@ -674,11 +674,23 @@ private void enqueueWeighted(int currentWeight, BasicBlock currentBlock, Basi } } - IteratorFrame frame = new IteratorFrame(blockSequence, (currentWeight-iterations) - categoryWeight*getBlocks().size() ); + IteratorFrame frame = + new IteratorFrame( + blockSequence, (currentWeight - iterations) - categoryWeight * getBlocks().size()); worklist.add(frame); minValue = Math.min(frame.getWeight(), minValue); maxValue = Math.max(frame.getWeight(), maxValue); - System.out.println("weight: " +(currentWeight-iterations)+ " it:"+iterations + " sum:" +frame.weightSum + " :: " +currentBlock +" => " + blockSequence); + System.out.println( + "weight: " + + (currentWeight - iterations) + + " it:" + + iterations + + " sum:" + + frame.weightSum + + " :: " + + currentBlock + + " => " + + blockSequence); } @Override diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/TryCatchFinallyTests.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/TryCatchFinallyTests.java index 5c0dd9f2195..bba6e2d433d 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/TryCatchFinallyTests.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/TryCatchFinallyTests.java @@ -1,4 +1,4 @@ -package sootup.java.bytecode; +package sootup.java.bytecode.frontend; import categories.TestCategories; import java.nio.file.Path; @@ -6,17 +6,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; - -import com.google.common.collect.Lists; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import sootup.core.inputlocation.AnalysisInputLocation; import sootup.core.jimple.basic.Trap; import sootup.core.model.SourceType; import sootup.core.signatures.MethodSignature; -import sootup.java.bytecode.inputlocation.PathBasedAnalysisInputLocation; -import sootup.java.core.interceptors.LocalSplitter; -import sootup.java.core.interceptors.TypeAssigner; +import sootup.interceptors.LocalSplitter; +import sootup.interceptors.TypeAssigner; +import sootup.java.bytecode.frontend.inputlocation.PathBasedAnalysisInputLocation; import sootup.java.core.views.JavaView; @Tag(TestCategories.JAVA_8_CATEGORY) @@ -58,14 +56,21 @@ public void testTryCatchFinallyIterator() { Path classFilePath = Paths.get("../shared-test-resources/bugfixes/LineIterator.class"); AnalysisInputLocation inputLocation = - new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( - classFilePath, "", SourceType.Application, Arrays.asList(new LocalSplitter(), new TypeAssigner())); + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( + classFilePath, + "", + SourceType.Application, + Arrays.asList(new LocalSplitter(), new TypeAssigner())); JavaView view = new JavaView(Collections.singletonList(inputLocation)); - view.getClasses().forEach(clazz -> { - clazz.getMethods().forEach(method -> { - view.getMethod(method.getSignature()).get().getBody().getTraps(); - }); - }); + view.getClasses() + .forEach( + clazz -> { + clazz + .getMethods() + .forEach( + method -> { + view.getMethod(method.getSignature()).get().getBody().getTraps(); + }); + }); } - } diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java index d85b1a3de84..2164c4f1588 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/interceptors/ConditionalBranchFolderTest.java @@ -1,14 +1,21 @@ package sootup.java.bytecode.frontend.interceptors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import categories.TestCategories; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import sootup.core.graph.MutableStmtGraph; +import sootup.core.inputlocation.AnalysisInputLocation; import sootup.core.jimple.Jimple; import sootup.core.jimple.basic.Local; import sootup.core.jimple.basic.StmtPositionInfo; @@ -19,12 +26,15 @@ import sootup.core.jimple.common.stmt.JIfStmt; import sootup.core.jimple.common.stmt.Stmt; import sootup.core.model.Body; +import sootup.core.model.SourceType; import sootup.core.signatures.MethodSignature; import sootup.core.signatures.PackageName; import sootup.core.util.ImmutableUtils; import sootup.core.util.Utils; +import sootup.interceptors.ConditionalBranchFolder; +import sootup.interceptors.CopyPropagator; +import sootup.java.bytecode.frontend.inputlocation.PathBasedAnalysisInputLocation; import sootup.java.core.JavaIdentifierFactory; -import sootup.java.core.interceptors.ConditionalBranchFolder; import sootup.java.core.language.JavaJimple; import sootup.java.core.types.JavaClassType; import sootup.java.core.views.JavaView; @@ -100,8 +110,7 @@ public void testConditionalBranchingWithNoConclusiveIfCondition() { @Test public void testConditionalBranchFolderWithMultipleBranches() { AnalysisInputLocation inputLocation = - new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation - .ClassFileBasedAnalysisInputLocation( + new PathBasedAnalysisInputLocation.ClassFileBasedAnalysisInputLocation( classFilePath, "", SourceType.Application, diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/LabelledLoopBreakTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/LabelledLoopBreakTest.java index eb402f1a8eb..f9568fc13bb 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/LabelledLoopBreakTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/LabelledLoopBreakTest.java @@ -46,15 +46,12 @@ public List expectedBodyStmts() { "if l2 >= 5 goto label4", "if l1 != 1 goto label3", "goto label5", - "label3:", "l2 = l2 + 1", "goto label2", - "label4:", "l1 = l1 + 1", "goto label1", - "label5:", "return") .collect(Collectors.toList()); diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/SwitchCaseStatementTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/SwitchCaseStatementTest.java index 2abd5cdfd8f..25388ea06ba 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/SwitchCaseStatementTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/SwitchCaseStatementTest.java @@ -153,25 +153,20 @@ public void switchCaseStatementCaseIncludingIf() { "case 2: goto label3", "case 3: goto label4", "default: goto label5", - "label1:", "l2 = 1", "if l1 != 666 goto label2", "l2 = 11", "goto label6", - "label2:", "l2 = 12", "goto label6", - "label3:", "l2 = 2", "goto label6", - "label4:", "l2 = 3", "goto label6", - "label5:", "l2 = -1", "label6:", diff --git a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/TryCatchFinallyTest.java b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/TryCatchFinallyTest.java index 8da24d56348..8d7216b5da3 100644 --- a/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/TryCatchFinallyTest.java +++ b/sootup.java.bytecode.frontend/src/test/java/sootup/java/bytecode/frontend/minimaltestsuite/java6/TryCatchFinallyTest.java @@ -139,25 +139,23 @@ public List expectedBodyStmtsTryCatchFinally() { "label1:", "l1 = \"try\"", "$stack4 = ", - "virtualinvoke $stack4.(l1)", // <-- endrange:handler:label3,label5 + "virtualinvoke $stack4.(l1)", // <-- endrange:handler:label3,label5 "label2:", "l1 = \"finally\"", "$stack5 = ", "virtualinvoke $stack5.(l1)", - "goto label6", // <-- label6 - + "goto label6", // <-- label6 "label3:", "$stack8 := @caughtexception", "l2 = $stack8", "l1 = \"catch\"", "$stack9 = ", - "virtualinvoke $stack9.(l1)", // <-- endrange:handler:label5 + "virtualinvoke $stack9.(l1)", // <-- endrange:handler:label5 "label4:", "l1 = \"finally\"", "$stack10 = ", "virtualinvoke $stack10.(l1)", - "goto label6", // <-- label6 - + "goto label6", // <-- label6 "label5:", "$stack6 := @caughtexception", "l3 = $stack6", @@ -165,10 +163,8 @@ public List expectedBodyStmtsTryCatchFinally() { "$stack7 = ", "virtualinvoke $stack7.(l1)", "throw l3", - "label6:", "return", - "catch java.lang.Exception from label1 to label2 with label3", "catch java.lang.Throwable from label1 to label2 with label5", "catch java.lang.Throwable from label3 to label4 with label5") From e0afccaa670fa5de608d2387d6fc72de74b87f07 Mon Sep 17 00:00:00 2001 From: sahilagichani Date: Wed, 2 Oct 2024 14:35:16 +0200 Subject: [PATCH 14/14] use UCE at the end, added a tc --- .../ConditionalBranchFolderTest.class | Bin 1081 -> 1232 bytes .../bugfixes/ConditionalBranchFolderTest.java | 15 ++++++++++ .../core/graph/MutableBlockStmtGraph.java | 1 + .../interceptors/ConditionalBranchFolder.java | 5 ++-- .../UnreachableCodeEliminator.java | 2 +- .../ConditionalBranchFolderTest.java | 27 +++++++++++++++++- 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class b/shared-test-resources/bugfixes/ConditionalBranchFolderTest.class index 6acf22e302b393cc480a526b2f85c8f1e533a84d..a6ef82eb268ee1800a338dd68266983db0ab5fce 100644 GIT binary patch delta 286 zcmXYr!7c+)6o$V$({^-5(srmZMkXnOCbStf)rJPe(%#O(&e}V8fV--A081<+L~JEi z9;h0>Ms9LXPV$}a|6gam%l_Bo?E@Ileexensd#ghU1s*toSTxJm`~evlK74&84*0= zh4mLIe>2idM)ulS>V-JWQDUANb-e{*A{zReBy6)xPD)!LWeQ_zrs4%!GW+ff(NYyZzRdh?a=9h>nO%#EyvFsWQMFQ#C`6q#$X>-n$oPd${x{ l(#L0?kOOrZsKc>Z9kRlaVooGIohoK=e@M;0!$^!G^Ir|aC>;O* delta 188 zcmXBNxeftQ6oBFX&NY^y5c?8i7bbQyg+xQ5Qp^i@tNRiviIPGqQE9vl;)I;)oSZNJ zBXN#9_s``DtO-ueL79u%DceM?o7wB7m%$=Olssw5WGTv4lBqBelSIErt4)k*#DuzF zo_boY38@Qd2", + "virtualinvoke $stack2.(\"False 2\")", + "return") + .collect(Collectors.toList()), + Utils.filterJimple(body1.toString())); } @Test