Skip to content

Commit 344535b

Browse files
authored
Merge pull request #19672 from github/tausbn/python-support-type-annotations-in-call-graph
Python: Support type annotations in call graph
2 parents 88b4f97 + c6c6a85 commit 344535b

File tree

6 files changed

+60
-0
lines changed

6 files changed

+60
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Type annotations such as `foo : Bar` are now treated by the call graph as an indication that `foo` may be an instance of `Bar`.

python/ql/lib/semmle/python/Exprs.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,17 @@ class Annotation extends Expr {
762762
or
763763
this = any(FunctionExpr f).getReturns()
764764
}
765+
766+
/** Gets the expression that this annotation annotates. */
767+
Expr getAnnotatedExpression() {
768+
result = any(AnnAssign a | a.getAnnotation() = this).getTarget()
769+
or
770+
result = any(Parameter p | p.getAnnotation() = this)
771+
or
772+
exists(FunctionExpr f, Return r |
773+
this = f.getReturns() and r.getScope() = f.getInnerScope() and result = r.getValue()
774+
)
775+
}
765776
}
766777

767778
/* Expression Contexts */

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,11 @@ private module TrackClassInstanceInput implements CallGraphConstruction::Simple:
580580
class State = Class;
581581

582582
predicate start(Node start, Class cls) {
583+
exists(Annotation ann |
584+
ann = classTracker(cls).asExpr() and
585+
start.asExpr() = ann.getAnnotatedExpression()
586+
)
587+
or
583588
resolveClassCall(start.(CallCfgNode).asCfgNode(), cls)
584589
or
585590
// result of `super().__new__` as used in a `__new__` method implementation
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
testFailures
2+
debug_callableNotUnique
3+
pointsTo_found_typeTracker_notFound
4+
typeTracker_found_pointsTo_notFound
5+
| type_annotations.py:6:5:6:14 | ControlFlowNode for Attribute() | Foo.method |
6+
| type_annotations.py:16:5:16:14 | ControlFlowNode for Attribute() | Foo.method |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../CallGraph/InlineCallGraphTest.ql
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class Foo:
2+
def method(self):
3+
pass
4+
5+
def test_parameter_annotation(x: Foo):
6+
x.method() #$ tt=Foo.method
7+
8+
def test_no_parameter_annotation(x):
9+
x.method()
10+
11+
def function_with_return_annotation() -> Foo:
12+
return eval("Foo()")
13+
14+
def test_return_annotation():
15+
x = function_with_return_annotation() #$ pt,tt=function_with_return_annotation
16+
x.method() #$ tt=Foo.method
17+
18+
def function_without_return_annotation():
19+
return eval("Foo()")
20+
21+
def test_no_return_annotation():
22+
x = function_without_return_annotation() #$ pt,tt=function_without_return_annotation
23+
x.method()
24+
25+
def test_variable_annotation():
26+
x = eval("Foo()")
27+
x : Foo
28+
# Currently fails because there is no flow from the class definition to the type annotation.
29+
x.method() #$ MISSING: tt=Foo.method
30+
31+
def test_no_variable_annotation():
32+
x = eval("Foo()")
33+
x.method()

0 commit comments

Comments
 (0)