diff --git a/spoon-control-flow/src/main/java/fr/inria/controlflow/ControlFlowBuilder.java b/spoon-control-flow/src/main/java/fr/inria/controlflow/ControlFlowBuilder.java index aef9d87689b..1e474176a8e 100644 --- a/spoon-control-flow/src/main/java/fr/inria/controlflow/ControlFlowBuilder.java +++ b/spoon-control-flow/src/main/java/fr/inria/controlflow/ControlFlowBuilder.java @@ -114,6 +114,7 @@ import spoon.reflect.visitor.CtAbstractVisitor; import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Stack; @@ -390,7 +391,12 @@ public void visitCtBinaryOperator(CtBinaryOperator operator) { } - private void travelStatementList(List statements) { + /** + * Add a list of statements as a block to the current CFG. + * @param statements The list of statements + * @return The start node of the block + */ + private ControlFlowNode travelStatementList(List statements) { ControlFlowNode begin = new ControlFlowNode(null, result, BranchKind.BLOCK_BEGIN); tryAddEdge(lastNode, begin); lastNode = begin; @@ -402,6 +408,7 @@ private void travelStatementList(List statements) { ControlFlowNode end = new ControlFlowNode(null, result, BranchKind.BLOCK_END); tryAddEdge(lastNode, end); lastNode = end; + return begin; } @Override @@ -768,24 +775,50 @@ public void visitCtSwitch(CtSwitch switchStatement) { //Push the convergence node so all non labeled breaks jumps there breakingBad.push(convergenceNode); + boolean hasDefaultCase = false; lastNode = switchNode; - for (CtCase caseStatement : switchStatement.getCases()) { + for (CtCase caseStatement : switchStatement.getCases()) { //Visit Case registerStatementLabel(caseStatement); - ControlFlowNode cn = new ControlFlowNode(caseStatement.getCaseExpression(), result, BranchKind.STATEMENT); - tryAddEdge(lastNode, cn); + var caseExpressions = caseStatement.getCaseExpressions(); + List caseExpressionNodes = new ArrayList<>(); + for (CtExpression expression : caseExpressions) { + ControlFlowNode caseNode = new ControlFlowNode(expression, result, BranchKind.STATEMENT); + caseExpressionNodes.add(caseNode); + tryAddEdge(switchNode, caseNode); + } + + if (caseExpressionNodes.isEmpty()) { + hasDefaultCase = true; + ControlFlowNode defaultNode = new ControlFlowNode(null, result, BranchKind.STATEMENT); + caseExpressionNodes.add(defaultNode); + tryAddEdge(switchNode, defaultNode); + } + + ControlFlowNode fallThroughEnd = null; if (lastNode != switchNode) { - tryAddEdge(switchNode, cn); + fallThroughEnd = lastNode; } - lastNode = cn; - travelStatementList(caseStatement.getStatements()); + lastNode = null; + + ControlFlowNode blockStart = travelStatementList(caseStatement.getStatements()); + tryAddEdge(fallThroughEnd, blockStart); + + for (ControlFlowNode expressionNode : caseExpressionNodes) { + tryAddEdge(expressionNode, blockStart); + } + if (lastNode.getStatement() instanceof CtBreak) { lastNode = switchNode; } } tryAddEdge(lastNode, convergenceNode); + if (!hasDefaultCase) { + tryAddEdge(switchNode, convergenceNode); + } + //Return as last node the convergence node lastNode = convergenceNode; breakingBad.pop(); diff --git a/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java new file mode 100644 index 00000000000..1ccaf7787cd --- /dev/null +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java @@ -0,0 +1,131 @@ +package fr.inria.controlflow; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A helper class for analyzing paths in the control flow graph + */ +public class ControlFlowPathHelper { + /** + * Memoization of paths. + */ + private final Map>> pathsMemo = new HashMap<>(); + + /** + * Get a list of possible paths to the exit node from a given starting node. + * + * @param node Starting node + * @return List of possible paths + */ + List> paths(ControlFlowNode node) { + if (pathsMemo.containsKey(node)) { + return pathsMemo.get(node); + } + + List> result = new ArrayList<>(); + + for (ControlFlowNode nextNode : node.next()) { + result.add(new ArrayList<>(Arrays.asList(node, nextNode))); + } + + result = paths(result); + pathsMemo.put(node, result); + return result; + } + + /** + * Get a list of possible paths to the exit node given a set of potentially incomplete paths. + * + * @param prior Set of potentially incomplete paths + * @return List of possible paths + */ + private List> paths(List> prior) { + List> result = new ArrayList<>(); + boolean extended = false; + + for (List path : prior) { + ControlFlowNode lastNode = path.get(path.size() - 1); + + if (lastNode.getKind() == BranchKind.EXIT) { + result.add(new ArrayList<>(path)); + } else { + for (ControlFlowNode nextNode : lastNode.next()) { + extended = true; + List thisPath = new ArrayList<>(path); + thisPath.add(nextNode); + result.add(thisPath); + } + } + } + + if (extended) { + return paths(result); + } else { + return result; + } + } + + /** + * Check whether a path contains a catch block node. + * + * @param nodes Path to check + * @return True if path contains a catch block node, false otherwise + */ + private boolean containsCatchBlockNode(List nodes) { + return nodes.stream().anyMatch(node -> node.getKind() == BranchKind.CATCH); + } + + /** + * Check whether a node has a path to the exit node that does not enter a catch block. + * + * @param node Node to check + * @return True if node has path to exit that does not enter any catch block, false otherwise + */ + boolean canReachExitWithoutEnteringCatchBlock(ControlFlowNode node) { + return paths(node).stream().anyMatch(xs -> !containsCatchBlockNode(xs)); + } + + /** + * Check whether a node has a path to another node. + * + * @param source Starting node + * @param target Target node + * @return True if there is a path from source to target, false otherwise + */ + boolean canReachNode(ControlFlowNode source, ControlFlowNode target) { + return paths(source).stream().anyMatch(xs -> xs.contains(target)); + } + + /** + * Check whether a node can reach the exit without crossing a certain node. + * + * @param source Starting node + * @param avoid Avoid node + * @return True if there exists a path between source and exit that does not include avoid, false otherwise + */ + boolean canAvoidNode(ControlFlowNode source, ControlFlowNode avoid) { + return !paths(source).stream().allMatch(xs -> xs.contains(avoid)); + } + + /** + * Find a node in a ControlFlowGraph by matching on the string representation of the statement + * stored in the node (if any). + * + * @param graph Graph to search + * @param s String to match against statement + * @return First node found with statement matching string, or null if none was found + */ + ControlFlowNode findNodeByString(ControlFlowGraph graph, String s) { + for (ControlFlowNode node : graph.vertexSet()) { + if (node.getStatement() != null && node.getStatement().toString().equals(s)) { + return node; + } + } + + return null; + } +} diff --git a/spoon-control-flow/src/test/java/fr/inria/controlflow/ExceptionFlowTests.java b/spoon-control-flow/src/test/java/fr/inria/controlflow/ExceptionFlowTests.java index 18dbcc678db..677142565cd 100644 --- a/spoon-control-flow/src/test/java/fr/inria/controlflow/ExceptionFlowTests.java +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ExceptionFlowTests.java @@ -12,6 +12,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class ExceptionFlowTests { + + ControlFlowPathHelper pathHelper = new ControlFlowPathHelper(); + @Test public void testBasicSingle() { @@ -36,24 +39,24 @@ public void testBasicSingle() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode x = findNodeByString(cfg, "x()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode c = findNodeByString(cfg, "c()"); - ControlFlowNode bang = findNodeByString(cfg, "bang()"); + ControlFlowNode x = pathHelper.findNodeByString(cfg, "x()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode c = pathHelper.findNodeByString(cfg, "c()"); + ControlFlowNode bang = pathHelper.findNodeByString(cfg, "bang()"); - assertFalse(canReachNode(x, bang)); + assertFalse(pathHelper.canReachNode(x, bang)); - assertTrue(canReachExitWithoutEnteringCatchBlock(x)); - assertTrue(canReachExitWithoutEnteringCatchBlock(a)); - assertTrue(canReachExitWithoutEnteringCatchBlock(b)); - assertTrue(canReachExitWithoutEnteringCatchBlock(c)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(x)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(a)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(b)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(c)); - assertTrue(canReachNode(a, bang)); - assertTrue(canReachNode(a, b)); - assertTrue(canReachNode(b, bang)); - assertTrue(canReachNode(b, c)); - assertTrue(canReachNode(c, bang)); + assertTrue(pathHelper.canReachNode(a, bang)); + assertTrue(pathHelper.canReachNode(a, b)); + assertTrue(pathHelper.canReachNode(b, bang)); + assertTrue(pathHelper.canReachNode(b, c)); + assertTrue(pathHelper.canReachNode(c, bang)); } @Test @@ -83,23 +86,23 @@ public void testBasicDouble() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode x = findNodeByString(cfg, "x()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode bang = findNodeByString(cfg, "bang()"); - ControlFlowNode boom = findNodeByString(cfg, "boom()"); + ControlFlowNode x = pathHelper.findNodeByString(cfg, "x()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode bang = pathHelper.findNodeByString(cfg, "bang()"); + ControlFlowNode boom = pathHelper.findNodeByString(cfg, "boom()"); - assertFalse(canReachNode(x, bang)); - assertTrue(canReachNode(x, boom)); + assertFalse(pathHelper.canReachNode(x, bang)); + assertTrue(pathHelper.canReachNode(x, boom)); - assertTrue(canReachExitWithoutEnteringCatchBlock(x)); - assertTrue(canReachExitWithoutEnteringCatchBlock(a)); - assertTrue(canReachExitWithoutEnteringCatchBlock(b)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(x)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(a)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(b)); - assertTrue(canReachNode(a, bang)); + assertTrue(pathHelper.canReachNode(a, bang)); - assertTrue(canReachNode(b, boom)); - assertFalse(canReachNode(b, bang)); + assertTrue(pathHelper.canReachNode(b, boom)); + assertFalse(pathHelper.canReachNode(b, bang)); } @Test @@ -129,23 +132,23 @@ public void testBasicNested() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode c = findNodeByString(cfg, "c()"); - ControlFlowNode boom = findNodeByString(cfg, "boom()"); - ControlFlowNode bang = findNodeByString(cfg, "bang()"); - - assertTrue(canReachExitWithoutEnteringCatchBlock(a)); - assertTrue(canReachExitWithoutEnteringCatchBlock(b)); - assertTrue(canReachExitWithoutEnteringCatchBlock(c)); - assertTrue(canReachExitWithoutEnteringCatchBlock(boom)); - - assertTrue(canReachNode(a, boom)); - assertTrue(canReachNode(a, bang)); - assertTrue(canReachNode(b, boom)); - assertTrue(canReachNode(b, bang)); - assertFalse(canReachNode(c, boom)); - assertTrue(canReachNode(c, bang)); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode c = pathHelper.findNodeByString(cfg, "c()"); + ControlFlowNode boom = pathHelper.findNodeByString(cfg, "boom()"); + ControlFlowNode bang = pathHelper.findNodeByString(cfg, "bang()"); + + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(a)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(b)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(c)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(boom)); + + assertTrue(pathHelper.canReachNode(a, boom)); + assertTrue(pathHelper.canReachNode(a, bang)); + assertTrue(pathHelper.canReachNode(b, boom)); + assertTrue(pathHelper.canReachNode(b, bang)); + assertFalse(pathHelper.canReachNode(c, boom)); + assertTrue(pathHelper.canReachNode(c, bang)); } @Test @@ -175,23 +178,23 @@ public void testMultipleCatchers() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode top = findNodeByString(cfg, "top()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode c = findNodeByString(cfg, "c()"); + ControlFlowNode top = pathHelper.findNodeByString(cfg, "top()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode c = pathHelper.findNodeByString(cfg, "c()"); - assertTrue(canReachExitWithoutEnteringCatchBlock(top)); - assertTrue(canReachExitWithoutEnteringCatchBlock(a)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(top)); + assertTrue(pathHelper.canReachExitWithoutEnteringCatchBlock(a)); - assertTrue(canReachNode(top, a)); - assertTrue(canReachNode(top, b)); - assertTrue(canReachNode(top, c)); + assertTrue(pathHelper.canReachNode(top, a)); + assertTrue(pathHelper.canReachNode(top, b)); + assertTrue(pathHelper.canReachNode(top, c)); - assertTrue(canReachNode(a, b)); - assertTrue(canReachNode(a, c)); + assertTrue(pathHelper.canReachNode(a, b)); + assertTrue(pathHelper.canReachNode(a, c)); - assertFalse(canReachNode(b, c)); - assertFalse(canReachNode(c, b)); + assertFalse(pathHelper.canReachNode(b, c)); + assertFalse(pathHelper.canReachNode(c, b)); } @Test @@ -220,16 +223,16 @@ public void testThrowStatement() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - assertNull(findNodeByString(cfg, "unreachable()")); + assertNull(pathHelper.findNodeByString(cfg, "unreachable()")); - ControlFlowNode throwstmt = findNodeByString(cfg, "throw new RuntimeException()"); - ControlFlowNode boom = findNodeByString(cfg, "boom()"); - ControlFlowNode bang = findNodeByString(cfg, "bang()"); + ControlFlowNode throwstmt = pathHelper.findNodeByString(cfg, "throw new RuntimeException()"); + ControlFlowNode boom = pathHelper.findNodeByString(cfg, "boom()"); + ControlFlowNode bang = pathHelper.findNodeByString(cfg, "bang()"); - assertTrue(canReachNode(throwstmt, boom)); - assertTrue(canReachNode(throwstmt, bang)); + assertTrue(pathHelper.canReachNode(throwstmt, boom)); + assertTrue(pathHelper.canReachNode(throwstmt, bang)); - assertFalse(canReachExitWithoutEnteringCatchBlock(throwstmt)); + assertFalse(pathHelper.canReachExitWithoutEnteringCatchBlock(throwstmt)); } @Test @@ -253,7 +256,7 @@ public void testAddPathsForEmptyTryBlocksDisabled() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - assertNull(findNodeByString(cfg, "bang()")); + assertNull(pathHelper.findNodeByString(cfg, "bang()")); } @Test @@ -278,7 +281,7 @@ public void testAddPathsForEmptyTryBlocksEnabled() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - assertNotNull(findNodeByString(cfg, "bang()")); + assertNotNull(pathHelper.findNodeByString(cfg, "bang()")); } @Test @@ -397,18 +400,18 @@ public void testFinalizer() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode top = findNodeByString(cfg, "top()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode c = findNodeByString(cfg, "c()"); - - assertFalse(canAvoidNode(top, c)); - assertTrue(canReachNode(top, b)); - assertTrue(canAvoidNode(top, b)); - assertFalse(canAvoidNode(a, c)); - assertTrue(canReachNode(a, b)); - assertTrue(canAvoidNode(a, b)); - assertFalse(canAvoidNode(b, c)); + ControlFlowNode top = pathHelper.findNodeByString(cfg, "top()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode c = pathHelper.findNodeByString(cfg, "c()"); + + assertFalse(pathHelper.canAvoidNode(top, c)); + assertTrue(pathHelper.canReachNode(top, b)); + assertTrue(pathHelper.canAvoidNode(top, b)); + assertFalse(pathHelper.canAvoidNode(a, c)); + assertTrue(pathHelper.canReachNode(a, b)); + assertTrue(pathHelper.canAvoidNode(a, b)); + assertFalse(pathHelper.canAvoidNode(b, c)); } @Test @@ -436,13 +439,13 @@ public void testCatchlessTry() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode top = findNodeByString(cfg, "top()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); + ControlFlowNode top = pathHelper.findNodeByString(cfg, "top()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); - assertFalse(canAvoidNode(top, a)); - assertFalse(canAvoidNode(top, b)); - assertFalse(canAvoidNode(a, b)); + assertFalse(pathHelper.canAvoidNode(top, a)); + assertFalse(pathHelper.canAvoidNode(top, b)); + assertFalse(pathHelper.canAvoidNode(a, b)); } @Test @@ -476,16 +479,16 @@ public void testMultipleCatchersWithFinalizer() { builder.build(method); ControlFlowGraph cfg = builder.getResult(); - ControlFlowNode top = findNodeByString(cfg, "top()"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - ControlFlowNode c = findNodeByString(cfg, "c()"); - ControlFlowNode breathe = findNodeByString(cfg, "breathe()"); + ControlFlowNode top = pathHelper.findNodeByString(cfg, "top()"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); + ControlFlowNode c = pathHelper.findNodeByString(cfg, "c()"); + ControlFlowNode breathe = pathHelper.findNodeByString(cfg, "breathe()"); - assertFalse(canAvoidNode(top, breathe)); - assertFalse(canAvoidNode(a, breathe)); - assertFalse(canAvoidNode(b, breathe)); - assertFalse(canAvoidNode(c, breathe)); + assertFalse(pathHelper.canAvoidNode(top, breathe)); + assertFalse(pathHelper.canAvoidNode(a, breathe)); + assertFalse(pathHelper.canAvoidNode(b, breathe)); + assertFalse(pathHelper.canAvoidNode(c, breathe)); } @Test @@ -518,130 +521,11 @@ public void testFinalizerReturnStatementWithSimplifyingOption() { ControlFlowGraph cfg = builder.getResult(); System.out.println(cfg.toGraphVisText()); - ControlFlowNode ret = findNodeByString(cfg, "return"); - ControlFlowNode a = findNodeByString(cfg, "a()"); - ControlFlowNode b = findNodeByString(cfg, "b()"); - - assertTrue(canAvoidNode(ret, a)); - assertTrue(canAvoidNode(ret, b)); - } - - /** - * Memoization of paths. - */ - Map>> pathsMemo = new HashMap<>(); - - /** - * Get the set of possible paths to the exit node from a given starting node. - * - * @param node Starting node - * @return Set of possible paths - */ - private List> paths(ControlFlowNode node) { - if (pathsMemo.containsKey(node)) { - return pathsMemo.get(node); - } - - List> result = new ArrayList<>(); - - for (ControlFlowNode nextNode : node.next()) { - result.add(new ArrayList<>(Arrays.asList(node, nextNode))); - } - - result = paths(result); - pathsMemo.put(node, result); - return result; - } - - /** - * Get the set of possible paths to the exit node given a set of potentially incomplete paths. - * - * @param prior Set of potentially incomplete paths - * @return Set of possible paths - */ - private List> paths(List> prior) { - List> result = new ArrayList<>(); - boolean extended = false; - - for (List path : prior) { - ControlFlowNode lastNode = path.get(path.size() - 1); - - if (lastNode.getKind() == BranchKind.EXIT) { - result.add(new ArrayList<>(path)); - } else { - for (ControlFlowNode nextNode : lastNode.next()) { - extended = true; - List thisPath = new ArrayList<>(path); - thisPath.add(nextNode); - result.add(thisPath); - } - } - } - - if (extended) { - return paths(result); - } else { - return result; - } - } - - /** - * Check whether a path contains a catch block node. - * - * @param nodes Path to check - * @return True if path contains a catch block node, false otherwise - */ - private boolean containsCatchBlockNode(List nodes) { - return nodes.stream().anyMatch(node -> node.getKind() == BranchKind.CATCH); - } - - /** - * Check whether a node has a path to the exit node that does not enter a catch block. - * - * @param node Node to check - * @return True if node has path to exit that does not enter any catch block, false otherwise - */ - private boolean canReachExitWithoutEnteringCatchBlock(ControlFlowNode node) { - return paths(node).stream().anyMatch(xs -> !containsCatchBlockNode(xs)); - } - - /** - * Check whether a node has a path to another node. - * - * @param source Starting node - * @param target Target node - * @return True if there is a path from source to target, false otherwise - */ - private boolean canReachNode(ControlFlowNode source, ControlFlowNode target) { - return paths(source).stream().anyMatch(xs -> xs.contains(target)); - } - - /** - * Check whether a node can reach the exit without crossing a certain node. - * - * @param source Starting node - * @param target Target node - * @return True if there exists a path between source and exit that does not include target, false otherwise - */ - private boolean canAvoidNode(ControlFlowNode source, ControlFlowNode target) { - return !paths(source).stream().allMatch(xs -> xs.contains(target)); - } + ControlFlowNode ret = pathHelper.findNodeByString(cfg, "return"); + ControlFlowNode a = pathHelper.findNodeByString(cfg, "a()"); + ControlFlowNode b = pathHelper.findNodeByString(cfg, "b()"); - /** - * Find a node in a ControlFlowGraph by matching on the string representation of the statement - * stored in the node (if any). - * - * @param graph Graph to search - * @param s String to match against statement - * @return First node found with statement matching string, or null if none was found - */ - private ControlFlowNode findNodeByString(ControlFlowGraph graph, String s) { - for (ControlFlowNode node : graph.vertexSet()) { - if (node.getStatement() != null && node.getStatement().toString().equals(s)) { - return node; - } - } - - return null; + assertTrue(pathHelper.canAvoidNode(ret, a)); + assertTrue(pathHelper.canAvoidNode(ret, b)); } } diff --git a/spoon-control-flow/src/test/java/fr/inria/controlflow/ForwardFlowBuilderVisitorTest.java b/spoon-control-flow/src/test/java/fr/inria/controlflow/ForwardFlowBuilderVisitorTest.java index 89f0f387dfd..d29e8d99276 100644 --- a/spoon-control-flow/src/test/java/fr/inria/controlflow/ForwardFlowBuilderVisitorTest.java +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ForwardFlowBuilderVisitorTest.java @@ -30,10 +30,12 @@ import spoon.support.QueueProcessingManager; import java.io.PrintWriter; +import java.util.List; import static fr.inria.controlflow.BranchKind.BRANCH; import static fr.inria.controlflow.BranchKind.STATEMENT; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Created by marodrig on 14/10/2015. @@ -170,6 +172,27 @@ public void testSwitchFallThrough() throws Exception { testMethod("lastCaseFallThrough", false, 1, 4, 12); } + @Test + public void testSwitchImplicitDefault() throws Exception { + ControlFlowGraph graph = testMethod("lastCaseFallThrough", false, 1, 4, 12); + graph.simplify(); + ControlFlowPathHelper pathHelper = new ControlFlowPathHelper(); + ControlFlowNode entryNode = pathHelper.findNodeByString(graph, "int b = 0"); + ControlFlowNode caseNode = pathHelper.findNodeByString(graph, "b = 1"); + boolean canAvoid = pathHelper.canAvoidNode(entryNode, caseNode); + assertTrue(canAvoid, "Path for implicit default case missing"); + } + + @Test + public void testMultipleCaseExpressions() throws Exception { + ControlFlowGraph graph = testMethod("multipleCaseExpressions", true, 1, 8, 17); + graph.simplify(); + ControlFlowPathHelper pathHelper = new ControlFlowPathHelper(); + ControlFlowNode startNode = pathHelper.findNodeByString(graph, "int b = 0"); + List> paths = pathHelper.paths(startNode); + assertTrue(paths.size() > 2, "Not enough paths. Possibly missing different paths from multiple expressions for a case"); + } + //Test some mixed conditions @Test public void testSimple() throws Exception { diff --git a/spoon-control-flow/src/test/resources/control-flow/ControlFlowArithmetic.java b/spoon-control-flow/src/test/resources/control-flow/ControlFlowArithmetic.java index 6f369c94a44..129f51006a7 100644 --- a/spoon-control-flow/src/test/resources/control-flow/ControlFlowArithmetic.java +++ b/spoon-control-flow/src/test/resources/control-flow/ControlFlowArithmetic.java @@ -377,6 +377,18 @@ public int lastCaseFallThrough(int a) { return b; } + public int multipleCaseExpressions(int a) { + int b = 0; + switch (a) { + case 1, 2: + b = 1; + break; + default: + break; + } + return b; + } + //All lines will be tested in this method public int simple(int a) { a = a + a / 2;