Skip to content

Commit

Permalink
fix: Correct switch case behaviours (#5730, #5731) (#5732)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Pine authored Feb 20, 2025
1 parent 513ef9c commit b2b160f
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -390,7 +391,12 @@ public <T> void visitCtBinaryOperator(CtBinaryOperator<T> operator) {

}

private <R> void travelStatementList(List<CtStatement> 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<CtStatement> statements) {
ControlFlowNode begin = new ControlFlowNode(null, result, BranchKind.BLOCK_BEGIN);
tryAddEdge(lastNode, begin);
lastNode = begin;
Expand All @@ -402,6 +408,7 @@ private <R> void travelStatementList(List<CtStatement> statements) {
ControlFlowNode end = new ControlFlowNode(null, result, BranchKind.BLOCK_END);
tryAddEdge(lastNode, end);
lastNode = end;
return begin;
}

@Override
Expand Down Expand Up @@ -768,24 +775,50 @@ public <S> void visitCtSwitch(CtSwitch<S> 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<ControlFlowNode> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ControlFlowNode, List<List<ControlFlowNode>>> 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<List<ControlFlowNode>> paths(ControlFlowNode node) {
if (pathsMemo.containsKey(node)) {
return pathsMemo.get(node);
}

List<List<ControlFlowNode>> 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<List<ControlFlowNode>> paths(List<List<ControlFlowNode>> prior) {
List<List<ControlFlowNode>> result = new ArrayList<>();
boolean extended = false;

for (List<ControlFlowNode> 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<ControlFlowNode> 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<ControlFlowNode> 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;
}
}
Loading

0 comments on commit b2b160f

Please sign in to comment.