Skip to content

Commit 49f5f1e

Browse files
authored
Merge pull request #6336 from tausbn/python-make-annotated-assignment-a-definitionnode
Python: Two fixes regarding annotated assignments
2 parents 98a12ce + bea8a45 commit 49f5f1e

File tree

5 files changed

+27
-1
lines changed

5 files changed

+27
-1
lines changed

python/ql/lib/semmle/python/Flow.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,8 @@ class DefinitionNode extends ControlFlowNode {
653653
DefinitionNode() {
654654
exists(Assign a | a.getATarget().getAFlowNode() = this)
655655
or
656+
exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue()))
657+
or
656658
exists(Alias a | a.getAsname().getAFlowNode() = this)
657659
or
658660
augstore(_, this)
@@ -795,6 +797,9 @@ private AstNode assigned_value(Expr lhs) {
795797
/* lhs = result */
796798
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
797799
or
800+
/* lhs : annotation = result */
801+
exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue())
802+
or
798803
/* import result as lhs */
799804
exists(Alias a | a.getAsname() = lhs and result = a.getValue())
800805
or

python/ql/src/Variables/UnusedLocalVariable.ql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,25 @@ predicate unused_local(Name unused, LocalVariable v) {
1919
def.getVariable() = v and
2020
def.isUnused() and
2121
not exists(def.getARedef()) and
22+
not exists(annotation_without_assignment(v)) and
2223
def.isRelevant() and
2324
not v = any(Nonlocal n).getAVariable() and
2425
not exists(def.getNode().getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
2526
not exists(def.getNode().getParentNode().(ClassDef).getDefinedClass().getADecorator())
2627
)
2728
}
2829

30+
/**
31+
* Gets any annotation of the local variable `v` that does not also reassign its value.
32+
*
33+
* TODO: This predicate should not be needed. Rather, annotated "assignments" that do not actually
34+
* assign a value should not result in the creation of an SSA variable (which then goes unused).
35+
*/
36+
private AnnAssign annotation_without_assignment(LocalVariable v) {
37+
result.getTarget() = v.getAStore() and
38+
not exists(result.getValue())
39+
}
40+
2941
from Name unused, LocalVariable v
3042
where
3143
unused_local(unused, v) and

python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,8 @@ def avoid_redundant_split(a):
288288
var = False
289289
if var:
290290
foo.bar() #foo is defined here.
291+
292+
def type_annotation_fp():
293+
annotated : annotation = [1,2,3]
294+
for x in annotated:
295+
print(x)

python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
| type_annotation_fp.py:5:5:5:7 | foo | The value assigned to local variable 'foo' is never used. |
21
| variables_test.py:29:5:29:5 | x | The value assigned to local variable 'x' is never used. |
32
| variables_test.py:89:5:89:5 | a | The value assigned to local variable 'a' is never used. |
43
| variables_test.py:89:7:89:7 | b | The value assigned to local variable 'b' is never used. |

python/ql/test/query-tests/Variables/unused/type_annotation_fp.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ def type_annotation(x):
99
else:
1010
foo : float
1111
do_other_stuff_with(foo)
12+
13+
def type_annotation_fn():
14+
# False negative: the value of `bar` is never used, but this is masked by the presence of the type annotation.
15+
bar = 5
16+
bar : int

0 commit comments

Comments
 (0)