Skip to content

Commit af4844d

Browse files
SONARPY-1883 Fix FP on S5756 on nested function in presence of try/except (#1813)
1 parent 85a4e1e commit af4844d

File tree

4 files changed

+50
-1
lines changed

4 files changed

+50
-1
lines changed

python-checks/src/test/resources/checks/nonCallableCalled.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,14 @@ def conditionally_reassigned_param_try_except(a, param):
299299
param = [1,2,3]
300300
param()
301301
except:
302-
...
302+
...
303+
304+
305+
def nested_function_in_try_catch():
306+
foo = None
307+
try:
308+
...
309+
except:
310+
...
311+
def bar():
312+
foo()

python-frontend/src/main/java/org/sonar/python/semantic/v2/types/Definition.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
package org.sonar.python.semantic.v2.types;
2121

2222
import org.sonar.plugins.python.api.tree.Name;
23+
import org.sonar.plugins.python.api.tree.Tree;
2324
import org.sonar.python.semantic.v2.SymbolV2;
25+
import org.sonar.python.tree.TreeUtils;
2426
import org.sonar.python.types.v2.PythonType;
2527

2628
/**
@@ -45,4 +47,9 @@ public Name lhsName() {
4547
public PythonType rhsType() {
4648
return lhsName.typeV2();
4749
}
50+
51+
@Override
52+
Tree scopeTree(Name name) {
53+
return TreeUtils.firstAncestor(name, t -> !t.equals(lhsName.parent()) && t.is(Tree.Kind.FUNCDEF, Tree.Kind.FILE_INPUT, Tree.Kind.CLASSDEF));
54+
}
4855
}

python-frontend/src/main/java/org/sonar/python/semantic/v2/types/Propagation.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonar.python.semantic.v2.types;
2121

2222
import java.util.HashSet;
23+
import java.util.Optional;
2324
import java.util.Set;
2425
import java.util.stream.Stream;
2526
import org.sonar.plugins.python.api.tree.Expression;
@@ -29,6 +30,7 @@
2930
import org.sonar.python.semantic.v2.SymbolV2Utils;
3031
import org.sonar.python.semantic.v2.UsageV2;
3132
import org.sonar.python.tree.NameImpl;
33+
import org.sonar.python.tree.TreeUtils;
3234
import org.sonar.python.types.v2.PythonType;
3335
import org.sonar.python.types.v2.UnionType;
3436

@@ -53,10 +55,13 @@ protected Propagation(SymbolV2 lhsSymbol, Name lhsName) {
5355
*/
5456
boolean propagate(Set<SymbolV2> initializedVars) {
5557
PythonType rhsType = rhsType();
58+
Tree scopeTree = scopeTree(lhsName);
5659
if (initializedVars.add(lhsSymbol)) {
5760
getSymbolNonDeclarationUsageTrees(lhsSymbol)
5861
.filter(NameImpl.class::isInstance)
5962
.map(NameImpl.class::cast)
63+
// Avoid propagation to usages in nested scopes, as this may lead to FPs
64+
.filter(n -> isInSameScope(n, scopeTree))
6065
.forEach(n -> n.typeV2(rhsType));
6166
return true;
6267
} else {
@@ -68,11 +73,20 @@ boolean propagate(Set<SymbolV2> initializedVars) {
6873
getSymbolNonDeclarationUsageTrees(lhsSymbol)
6974
.filter(NameImpl.class::isInstance)
7075
.map(NameImpl.class::cast)
76+
.filter(n -> isInSameScope(n, scopeTree))
7177
.forEach(n -> n.typeV2(newType));
7278
return !newType.equals(currentType);
7379
}
7480
}
7581

82+
private boolean isInSameScope(Name n, Tree scopeTree) {
83+
return Optional.ofNullable(scopeTree(n)).filter(scopeTree::equals).isPresent();
84+
}
85+
86+
Tree scopeTree(Name name) {
87+
return TreeUtils.firstAncestor(name, t -> t.is(Tree.Kind.FUNCDEF, Tree.Kind.FILE_INPUT, Tree.Kind.CLASSDEF));
88+
}
89+
7690
public static Stream<Tree> getSymbolNonDeclarationUsageTrees(SymbolV2 symbol) {
7791
return symbol.usages()
7892
.stream()

python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,24 @@ void try_except_list_attributes() {
990990

991991
}
992992

993+
@Test
994+
void tryExceptNestedScope() {
995+
FileInput fileInput = inferTypes("""
996+
try:
997+
...
998+
except:
999+
...
1000+
1001+
do_smth = None
1002+
1003+
def something(param):
1004+
type(do_smth)
1005+
""");
1006+
List<CallExpression> calls = PythonTestUtils.getAllDescendant(fileInput, tree -> tree.is(Tree.Kind.CALL_EXPR));
1007+
RegularArgument doSmthArg = (RegularArgument) calls.get(0).arguments().get(0);
1008+
assertThat(doSmthArg.expression().typeV2()).isEqualTo(PythonType.UNKNOWN);
1009+
}
1010+
9931011
@Test
9941012
void inferTypeForQualifiedExpression() {
9951013
var root = inferTypes("""

0 commit comments

Comments
 (0)