From a0f31187e638b8b4599d6a659550f8a48cf576d1 Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 00:23:54 +0200 Subject: [PATCH 1/9] Add test case for correct switch behaviour with implicit default case --- .../controlflow/ControlFlowPathHelper.java | 128 +++++++ .../inria/controlflow/ExceptionFlowTests.java | 320 ++++++------------ .../ForwardFlowBuilderVisitorTest.java | 12 + 3 files changed, 242 insertions(+), 218 deletions(-) create mode 100644 spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java 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..46e77a47648 --- /dev/null +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java @@ -0,0 +1,128 @@ +package fr.inria.controlflow; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ControlFlowPathHelper { + /** + * 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 + */ + 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..834ba270e8b 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 @@ -34,6 +34,7 @@ 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 +171,17 @@ 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); + } + //Test some mixed conditions @Test public void testSimple() throws Exception { From 958353bf7466a2852940dcbf3afadede0b580e80 Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 00:45:57 +0200 Subject: [PATCH 2/9] Handle implicit default case in control flow graph --- .../java/fr/inria/controlflow/ControlFlowBuilder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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..46cd1de539d 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 @@ -768,12 +768,14 @@ 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); + hasDefaultCase |= caseStatement.getCaseExpressions().isEmpty(); tryAddEdge(lastNode, cn); if (lastNode != switchNode) { tryAddEdge(switchNode, cn); @@ -786,6 +788,10 @@ public void visitCtSwitch(CtSwitch switchStatement) { } tryAddEdge(lastNode, convergenceNode); + if (!hasDefaultCase) { + tryAddEdge(switchNode, convergenceNode); + } + //Return as last node the convergence node lastNode = convergenceNode; breakingBad.pop(); From 7733ff6696ac917924b01e5605867296d6f8413a Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 00:56:06 +0200 Subject: [PATCH 3/9] Add test for correct handling of case statement with multiple expressions --- .../fr/inria/controlflow/ControlFlowPathHelper.java | 2 +- .../controlflow/ForwardFlowBuilderVisitorTest.java | 11 +++++++++++ .../control-flow/ControlFlowArithmetic.java | 12 ++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) 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 index 46e77a47648..3b7bc50bf47 100644 --- a/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java @@ -18,7 +18,7 @@ public class ControlFlowPathHelper { * @param node Starting node * @return Set of possible paths */ - private List> paths(ControlFlowNode node) { + List> paths(ControlFlowNode node) { if (pathsMemo.containsKey(node)) { return pathsMemo.get(node); } 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 834ba270e8b..10bf37001d8 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,6 +30,7 @@ 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; @@ -182,6 +183,16 @@ public void testSwitchImplicitDefault() throws Exception { assertTrue(canAvoid); } + @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); + } + //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; From db600eccfcad0e32f53139387146e5ae71c7b3f0 Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 01:28:41 +0200 Subject: [PATCH 4/9] Handle multiple case expressions As a side effect, fall throughs now go to the next block, not the next case expression --- .../inria/controlflow/ControlFlowBuilder.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) 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 46cd1de539d..d04aeff388a 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 @@ -774,14 +781,34 @@ public void visitCtSwitch(CtSwitch switchStatement) { //Visit Case registerStatementLabel(caseStatement); - ControlFlowNode cn = new ControlFlowNode(caseStatement.getCaseExpression(), result, BranchKind.STATEMENT); - hasDefaultCase |= caseStatement.getCaseExpressions().isEmpty(); - tryAddEdge(lastNode, cn); - if (lastNode != switchNode) { - tryAddEdge(switchNode, cn); + var caseExpressions = caseStatement.getCaseExpressions(); + ArrayList 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) { + 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; } From c8e7675a0e79bedee08655f69de5235fd737409b Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 02:06:55 +0200 Subject: [PATCH 5/9] Control Flow helper top level javaDoc --- .../java/fr/inria/controlflow/ControlFlowPathHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 3b7bc50bf47..9e5edff5e96 100644 --- a/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java @@ -6,11 +6,14 @@ 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. */ - Map>> pathsMemo = new HashMap<>(); + private final Map>> pathsMemo = new HashMap<>(); /** * Get the set of possible paths to the exit node from a given starting node. From 0901815b82a2c533f8b5f3524eaeead7acb8af1c Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 02:09:33 +0200 Subject: [PATCH 6/9] Messages for `assertTrue`s --- .../fr/inria/controlflow/ForwardFlowBuilderVisitorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 10bf37001d8..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 @@ -180,7 +180,7 @@ public void testSwitchImplicitDefault() throws Exception { ControlFlowNode entryNode = pathHelper.findNodeByString(graph, "int b = 0"); ControlFlowNode caseNode = pathHelper.findNodeByString(graph, "b = 1"); boolean canAvoid = pathHelper.canAvoidNode(entryNode, caseNode); - assertTrue(canAvoid); + assertTrue(canAvoid, "Path for implicit default case missing"); } @Test @@ -190,7 +190,7 @@ public void testMultipleCaseExpressions() throws Exception { ControlFlowPathHelper pathHelper = new ControlFlowPathHelper(); ControlFlowNode startNode = pathHelper.findNodeByString(graph, "int b = 0"); List> paths = pathHelper.paths(startNode); - assertTrue(paths.size() > 2); + assertTrue(paths.size() > 2, "Not enough paths. Possibly missing different paths from multiple expressions for a case"); } //Test some mixed conditions From 13bde5c91b96f0a07d2795c775fdab47c35c1e7c Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 12:23:59 +0200 Subject: [PATCH 7/9] Make Checkstyle happy --- .../main/java/fr/inria/controlflow/ControlFlowBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d04aeff388a..78dd9b83666 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 @@ -783,7 +783,7 @@ public void visitCtSwitch(CtSwitch switchStatement) { registerStatementLabel(caseStatement); var caseExpressions = caseStatement.getCaseExpressions(); ArrayList caseExpressionNodes = new ArrayList<>(); - for (CtExpression expression: caseExpressions) { + for (CtExpression expression : caseExpressions) { ControlFlowNode caseNode = new ControlFlowNode(expression, result, BranchKind.STATEMENT); caseExpressionNodes.add(caseNode); tryAddEdge(switchNode, caseNode); @@ -797,7 +797,7 @@ public void visitCtSwitch(CtSwitch switchStatement) { } ControlFlowNode fallThroughEnd = null; - if(lastNode != switchNode) { + if (lastNode != switchNode) { fallThroughEnd = lastNode; } lastNode = null; From 56172172f1ada4d0ecade035098cb429c121fa5c Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Fri, 5 Apr 2024 12:37:42 +0200 Subject: [PATCH 8/9] Properly represent return type in javadoc --- .../java/fr/inria/controlflow/ControlFlowPathHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 9e5edff5e96..1ccaf7787cd 100644 --- a/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java +++ b/spoon-control-flow/src/test/java/fr/inria/controlflow/ControlFlowPathHelper.java @@ -16,10 +16,10 @@ public class ControlFlowPathHelper { private final Map>> pathsMemo = new HashMap<>(); /** - * Get the set of possible paths to the exit node from a given starting node. + * Get a list of possible paths to the exit node from a given starting node. * * @param node Starting node - * @return Set of possible paths + * @return List of possible paths */ List> paths(ControlFlowNode node) { if (pathsMemo.containsKey(node)) { @@ -38,10 +38,10 @@ List> paths(ControlFlowNode node) { } /** - * Get the set of possible paths to the exit node given a set of potentially incomplete paths. + * 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 Set of possible paths + * @return List of possible paths */ private List> paths(List> prior) { List> result = new ArrayList<>(); From 8d56f57b7c5c68ba66155006f833530269f314a8 Mon Sep 17 00:00:00 2001 From: Mr-Pine Date: Sat, 6 Apr 2024 12:26:18 +0200 Subject: [PATCH 9/9] Use List interface --- .../src/main/java/fr/inria/controlflow/ControlFlowBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 78dd9b83666..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 @@ -782,7 +782,7 @@ public void visitCtSwitch(CtSwitch switchStatement) { //Visit Case registerStatementLabel(caseStatement); var caseExpressions = caseStatement.getCaseExpressions(); - ArrayList caseExpressionNodes = new ArrayList<>(); + List caseExpressionNodes = new ArrayList<>(); for (CtExpression expression : caseExpressions) { ControlFlowNode caseNode = new ControlFlowNode(expression, result, BranchKind.STATEMENT); caseExpressionNodes.add(caseNode);