Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review: fix: Correct wrong switch behaviours (#5730, #5731) #5732

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
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();
ArrayList<ControlFlowNode> caseExpressionNodes = new ArrayList<>();
Mr-Pine marked this conversation as resolved.
Show resolved Hide resolved
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