Skip to content

Commit 7c45b14

Browse files
thomas-serre-sonarsourcemarc-jasper-sonarsource
authored andcommitted
SONARPY-3782 Store the CFG in the Analysis Context (#948)
Co-authored-by: Marc Jasper <marc.jasper@sonarsource.com> GitOrigin-RevId: 942f25043540a7108504c3bbe83c0a3a0bc6af7f
1 parent f8cf3fd commit 7c45b14

23 files changed

+171
-101
lines changed

python-checks/src/main/java/org/sonar/python/checks/AfterJumpStatementCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ public void initialize(Context context) {
3939
context.registerSyntaxNodeConsumer(Kind.FILE_INPUT, ctx ->
4040
{
4141
FileInput fileInput = (FileInput) ctx.syntaxNode();
42-
checkCfg(ControlFlowGraph.build(fileInput, ctx.pythonFile()), ctx, fileInput.statements());
42+
checkCfg(ctx.cfg(fileInput), ctx, fileInput.statements());
4343
}
4444
);
4545
context.registerSyntaxNodeConsumer(Kind.FUNCDEF, ctx ->
4646
{
4747
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
48-
checkCfg(ControlFlowGraph.build(functionDef, ctx.pythonFile()), ctx, functionDef.body());
48+
checkCfg(ctx.cfg(functionDef), ctx, functionDef.body());
4949
}
5050
);
5151

python-checks/src/main/java/org/sonar/python/checks/ConsistentReturnCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class ConsistentReturnCheck extends PythonSubscriptionCheck {
4040
public void initialize(Context context) {
4141
context.registerSyntaxNodeConsumer(Kind.FUNCDEF, ctx -> {
4242
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
43-
ControlFlowGraph cfg = ControlFlowGraph.build(functionDef, ctx.pythonFile());
43+
ControlFlowGraph cfg = ctx.cfg(functionDef);
4444
if (cfg == null || hasExceptOrFinally(cfg)) {
4545
return;
4646
}

python-checks/src/main/java/org/sonar/python/checks/ConstantConditionCheck.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.sonar.plugins.python.api.tree.IfStatement;
3434
import org.sonar.plugins.python.api.tree.Name;
3535
import org.sonar.plugins.python.api.tree.UnaryExpression;
36-
import org.sonar.python.cfg.fixpoint.ReachingDefinitionsAnalysis;
3736

3837
import static org.sonar.plugins.python.api.tree.Tree.Kind.AND;
3938
import static org.sonar.plugins.python.api.tree.Tree.Kind.NAME;
@@ -48,13 +47,6 @@ public class ConstantConditionCheck extends PythonVisitorCheck {
4847

4948
private static final String MESSAGE = "Replace this expression; used as a condition it will always be constant.";
5049
private static final List<String> ACCEPTED_DECORATORS = List.of("overload", "staticmethod", "classmethod");
51-
private ReachingDefinitionsAnalysis reachingDefinitionsAnalysis;
52-
53-
@Override
54-
public void visitFileInput(FileInput fileInput) {
55-
reachingDefinitionsAnalysis = new ReachingDefinitionsAnalysis(getContext().pythonFile());
56-
super.visitFileInput(fileInput);
57-
}
5850

5951
@Override
6052
public void visitIfStatement(IfStatement ifStatement) {
@@ -143,7 +135,7 @@ private void checkExpression(Expression expression) {
143135
}
144136
}
145137
if (expression.is(NAME)) {
146-
Set<Expression> valuesAtLocation = reachingDefinitionsAnalysis.valuesAtLocation(((Name) expression));
138+
Set<Expression> valuesAtLocation = getContext().valuesAtLocation(((Name) expression));
147139
if (valuesAtLocation.size() == 1) {
148140
Expression lastAssignedValue = valuesAtLocation.iterator().next();
149141
if (isImmutableConstant(lastAssignedValue)) {

python-checks/src/main/java/org/sonar/python/checks/DeadStoreCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void initialize(Context context) {
7070
if (TreeUtils.hasDescendant(functionDef, tree -> tree.is(Tree.Kind.TRY_STMT))) {
7171
return;
7272
}
73-
ControlFlowGraph cfg = ControlFlowGraph.build(functionDef, ctx.pythonFile());
73+
ControlFlowGraph cfg = ctx.cfg(functionDef);
7474
if (cfg == null) {
7575
return;
7676
}

python-checks/src/main/java/org/sonar/python/checks/FloatingPointEqualityCheck.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.sonar.plugins.python.api.tree.ImportName;
3333
import org.sonar.plugins.python.api.tree.Name;
3434
import org.sonar.plugins.python.api.tree.Tree;
35-
import org.sonar.python.cfg.fixpoint.ReachingDefinitionsAnalysis;
3635
import org.sonar.python.quickfix.TextEditUtils;
3736
import org.sonar.python.tree.TreeUtils;
3837
import org.sonar.python.types.v2.TypeChecker;
@@ -52,7 +51,6 @@ public class FloatingPointEqualityCheck extends PythonSubscriptionCheck {
5251

5352
private static final String MATH_MODULE = "math";
5453

55-
private ReachingDefinitionsAnalysis reachingDefinitionsAnalysis;
5654
private static final List<String> SUPPORTED_IS_CLOSE_MODULES = Arrays.asList("numpy", "torch", MATH_MODULE);
5755

5856
private String importedModuleForIsClose;
@@ -71,7 +69,6 @@ public void initialize(Context context) {
7169
}
7270

7371
private void initializeAnalysis(SubscriptionContext ctx) {
74-
reachingDefinitionsAnalysis = new ReachingDefinitionsAnalysis(ctx.pythonFile());
7572
importedModuleForIsClose = null;
7673
importedAlias = null;
7774
typeChecker = ctx.typeChecker();
@@ -80,39 +77,39 @@ private void initializeAnalysis(SubscriptionContext ctx) {
8077
private void checkFloatingPointEquality(SubscriptionContext ctx) {
8178
BinaryExpression binaryExpression = (BinaryExpression) ctx.syntaxNode();
8279
String operator = binaryExpression.operator().value();
83-
if (("==".equals(operator) || "!=".equals(operator)) && isAnyOperandFloatingPoint(binaryExpression)) {
80+
if (("==".equals(operator) || "!=".equals(operator)) && isAnyOperandFloatingPoint(binaryExpression, ctx)) {
8481
PreciseIssue issue = ctx.addIssue(binaryExpression, MESSAGE);
8582
issue.addQuickFix(createQuickFix(binaryExpression, operator));
8683
}
8784
}
8885

89-
private boolean isAnyOperandFloatingPoint(BinaryExpression binaryExpression) {
86+
private boolean isAnyOperandFloatingPoint(BinaryExpression binaryExpression, SubscriptionContext ctx) {
9087
Expression leftOperand = binaryExpression.leftOperand();
9188
Expression rightOperand = binaryExpression.rightOperand();
9289

9390
return isFloat(leftOperand) || isFloat(rightOperand) ||
94-
isAssignedFloat(leftOperand) || isAssignedFloat(rightOperand) ||
95-
isBinaryOperationWithFloat(leftOperand) || isBinaryOperationWithFloat(rightOperand);
91+
isAssignedFloat(leftOperand, ctx) || isAssignedFloat(rightOperand, ctx) ||
92+
isBinaryOperationWithFloat(leftOperand, ctx) || isBinaryOperationWithFloat(rightOperand, ctx);
9693
}
9794

9895
private boolean isFloat(Expression expression) {
9996
TriBool isTypeFloat = typeChecker.typeCheckBuilder().isBuiltinWithName("float").check(expression.typeV2());
10097
return expression.is(Tree.Kind.NUMERIC_LITERAL) && isTypeFloat == TriBool.TRUE;
10198
}
10299

103-
private boolean isAssignedFloat(Expression expression) {
100+
private boolean isAssignedFloat(Expression expression, SubscriptionContext ctx) {
104101
if (expression.is(Tree.Kind.NAME)) {
105-
Set<Expression> values = reachingDefinitionsAnalysis.valuesAtLocation((Name) expression);
102+
Set<Expression> values = ctx.valuesAtLocation((Name) expression);
106103
if (!values.isEmpty()) {
107104
return values.stream().allMatch(this::isFloat);
108105
}
109106
}
110107
return false;
111108
}
112109

113-
private boolean isBinaryOperationWithFloat(Expression expression) {
110+
private boolean isBinaryOperationWithFloat(Expression expression, SubscriptionContext ctx) {
114111
if (expression.is(BINARY_OPERATION_KINDS)) {
115-
return isAnyOperandFloatingPoint((BinaryExpression) expression);
112+
return isAnyOperandFloatingPoint((BinaryExpression) expression, ctx);
116113
}
117114
return false;
118115
}

python-checks/src/main/java/org/sonar/python/checks/HttpNoContentNonEmptyBodyCheck.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.sonar.plugins.python.api.tree.Tree;
3333
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
3434
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatchers;
35-
import org.sonar.python.cfg.fixpoint.ReachingDefinitionsAnalysis;
3635
import org.sonar.python.checks.utils.Expressions;
3736
import org.sonar.python.tree.TreeUtils;
3837

@@ -41,8 +40,6 @@ public class HttpNoContentNonEmptyBodyCheck extends PythonSubscriptionCheck {
4140

4241
private static final String MESSAGE = "Return an empty body for this endpoint returning 204 status.";
4342

44-
private ReachingDefinitionsAnalysis reachingDefinitionsAnalysis;
45-
4643
private static final TypeMatcher FASTAPI_RESPONSE_INSTANCE = TypeMatchers.isObjectOfType("fastapi.Response");
4744
private static final TypeMatcher NONE_TYPE = TypeMatchers.isObjectOfType("NoneType");
4845

@@ -59,9 +56,6 @@ public class HttpNoContentNonEmptyBodyCheck extends PythonSubscriptionCheck {
5956

6057
@Override
6158
public void initialize(Context context) {
62-
context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, ctx ->
63-
reachingDefinitionsAnalysis = new ReachingDefinitionsAnalysis(ctx.pythonFile())
64-
);
6559
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, this::checkFunctionDef);
6660
}
6761

@@ -176,7 +170,7 @@ private ValidationResult isValidResponseObject(SubscriptionContext ctx, Expressi
176170

177171
if (expr.is(Tree.Kind.NAME)) {
178172
Name name = (Name) expr;
179-
var assignedValues = reachingDefinitionsAnalysis.valuesAtLocation(name);
173+
var assignedValues = ctx.valuesAtLocation(name);
180174

181175
boolean anyInvalid = false;
182176
for (Expression assignedValue : assignedValues) {

python-checks/src/main/java/org/sonar/python/checks/IgnoredParameterCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class IgnoredParameterCheck extends PythonSubscriptionCheck {
4343
public void initialize(Context context) {
4444
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, ctx -> {
4545
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
46-
ControlFlowGraph cfg = ControlFlowGraph.build(functionDef, ctx.pythonFile());
46+
ControlFlowGraph cfg = ctx.cfg(functionDef);
4747
if (cfg == null) {
4848
return;
4949
}

python-checks/src/main/java/org/sonar/python/checks/InfiniteRecursionCheck.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import javax.annotation.CheckForNull;
2727
import javax.annotation.Nullable;
2828
import org.sonar.check.Rule;
29-
import org.sonar.plugins.python.api.PythonFile;
3029
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
3130
import org.sonar.plugins.python.api.cfg.CfgBlock;
3231
import org.sonar.plugins.python.api.cfg.ControlFlowGraph;
@@ -62,7 +61,7 @@ public void initialize(Context context) {
6261
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, ctx -> {
6362
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
6463
List<Tree> allRecursiveCalls = new ArrayList<>();
65-
boolean endBlockIsReachable = collectRecursiveCallsAndCheckIfEndBlockIsReachable(functionDef, ctx.pythonFile(), allRecursiveCalls);
64+
boolean endBlockIsReachable = collectRecursiveCallsAndCheckIfEndBlockIsReachable(functionDef, ctx.cfg(functionDef), allRecursiveCalls);
6665
if (!allRecursiveCalls.isEmpty() && !endBlockIsReachable) {
6766
String message = String.format(MESSAGE, functionDef.isMethodDefinition() ? "method" : "function");
6867
PreciseIssue issue = ctx.addIssue(functionDef.name(), message);
@@ -71,13 +70,9 @@ public void initialize(Context context) {
7170
});
7271
}
7372

74-
private static boolean collectRecursiveCallsAndCheckIfEndBlockIsReachable(FunctionDef functionDef, PythonFile pythonFile, List<Tree> allRecursiveCalls) {
73+
private static boolean collectRecursiveCallsAndCheckIfEndBlockIsReachable(FunctionDef functionDef, @Nullable ControlFlowGraph cfg, List<Tree> allRecursiveCalls) {
7574
Symbol functionSymbol = functionDef.name().symbol();
76-
if (functionSymbol == null) {
77-
return true;
78-
}
79-
ControlFlowGraph cfg = ControlFlowGraph.build(functionDef, pythonFile);
80-
if (cfg == null) {
75+
if (functionSymbol == null || cfg == null) {
8176
return true;
8277
}
8378
RecursiveCallCollector recursiveCallCollector = new RecursiveCallCollector(functionDef, functionSymbol);

python-checks/src/main/java/org/sonar/python/checks/InvariantReturnCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public class InvariantReturnCheck extends PythonSubscriptionCheck {
6262
public void initialize(Context context) {
6363
context.registerSyntaxNodeConsumer(Kind.FUNCDEF, ctx -> {
6464
FunctionDef functionDef = (FunctionDef) ctx.syntaxNode();
65-
ControlFlowGraph cfg = ControlFlowGraph.build(functionDef, ctx.pythonFile());
65+
ControlFlowGraph cfg = ctx.cfg(functionDef);
6666
if (cfg != null) {
6767
List<LatestExecutedBlock> latestExecutedBlocks = collectLatestExecutedBlocks(cfg);
6868
boolean allBlocksHaveReturnStatement = latestExecutedBlocks.stream().allMatch(LatestExecutedBlock::hasReturnStatement);

python-checks/src/main/java/org/sonar/python/checks/LoopExecutingAtMostOnceCheck.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
import org.sonar.plugins.python.api.cfg.ControlFlowGraph;
3030
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
3131
import org.sonar.plugins.python.api.SubscriptionContext;
32-
import org.sonar.plugins.python.api.tree.FileInput;
33-
import org.sonar.plugins.python.api.tree.FunctionDef;
3432
import org.sonar.plugins.python.api.tree.Token;
3533
import org.sonar.plugins.python.api.tree.Tree;
3634
import org.sonar.plugins.python.api.tree.Tree.Kind;
@@ -43,10 +41,10 @@ public class LoopExecutingAtMostOnceCheck extends PythonSubscriptionCheck {
4341
@Override
4442
public void initialize(Context context) {
4543
context.registerSyntaxNodeConsumer(Kind.FUNCDEF, ctx ->
46-
checkCfg(ControlFlowGraph.build((FunctionDef) ctx.syntaxNode(), ctx.pythonFile()), ctx)
44+
checkCfg(ctx.cfg(ctx.syntaxNode()), ctx)
4745
);
4846
context.registerSyntaxNodeConsumer(Kind.FILE_INPUT, ctx ->
49-
checkCfg(ControlFlowGraph.build((FileInput) ctx.syntaxNode(), ctx.pythonFile()), ctx)
47+
checkCfg(ctx.cfg(ctx.syntaxNode()), ctx)
5048
);
5149
}
5250

0 commit comments

Comments
 (0)