diff --git a/python/ql/lib/change-notes/2025-06-04-call-graph-type-annotations.md b/python/ql/lib/change-notes/2025-06-04-call-graph-type-annotations.md new file mode 100644 index 000000000000..2aa17e576326 --- /dev/null +++ b/python/ql/lib/change-notes/2025-06-04-call-graph-type-annotations.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Type annotations such as `foo : Bar` are now treated by the call graph as an indication that `foo` may be an instance of `Bar`. diff --git a/python/ql/lib/semmle/python/Exprs.qll b/python/ql/lib/semmle/python/Exprs.qll index accc370481aa..a7f67b0b80ea 100644 --- a/python/ql/lib/semmle/python/Exprs.qll +++ b/python/ql/lib/semmle/python/Exprs.qll @@ -762,6 +762,17 @@ class Annotation extends Expr { or this = any(FunctionExpr f).getReturns() } + + /** Gets the expression that this annotation annotates. */ + Expr getAnnotatedExpression() { + result = any(AnnAssign a | a.getAnnotation() = this).getTarget() + or + result = any(Parameter p | p.getAnnotation() = this) + or + exists(FunctionExpr f, Return r | + this = f.getReturns() and r.getScope() = f.getInnerScope() and result = r.getValue() + ) + } } /* Expression Contexts */ diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index 1a38593bce48..781023a9658b 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -580,6 +580,11 @@ private module TrackClassInstanceInput implements CallGraphConstruction::Simple: class State = Class; predicate start(Node start, Class cls) { + exists(Annotation ann | + ann = classTracker(cls).asExpr() and + start.asExpr() = ann.getAnnotatedExpression() + ) + or resolveClassCall(start.(CallCfgNode).asCfgNode(), cls) or // result of `super().__new__` as used in a `__new__` method implementation diff --git a/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected new file mode 100644 index 000000000000..a08ad78be2e1 --- /dev/null +++ b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected @@ -0,0 +1,6 @@ +testFailures +debug_callableNotUnique +pointsTo_found_typeTracker_notFound +typeTracker_found_pointsTo_notFound +| type_annotations.py:6:5:6:14 | ControlFlowNode for Attribute() | Foo.method | +| type_annotations.py:16:5:16:14 | ControlFlowNode for Attribute() | Foo.method | diff --git a/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.qlref b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.qlref new file mode 100644 index 000000000000..25117a4582bd --- /dev/null +++ b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.qlref @@ -0,0 +1 @@ +../CallGraph/InlineCallGraphTest.ql diff --git a/python/ql/test/experimental/library-tests/CallGraph-type-annotations/type_annotations.py b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/type_annotations.py new file mode 100644 index 000000000000..51c08d1675e2 --- /dev/null +++ b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/type_annotations.py @@ -0,0 +1,33 @@ +class Foo: + def method(self): + pass + +def test_parameter_annotation(x: Foo): + x.method() #$ tt=Foo.method + +def test_no_parameter_annotation(x): + x.method() + +def function_with_return_annotation() -> Foo: + return eval("Foo()") + +def test_return_annotation(): + x = function_with_return_annotation() #$ pt,tt=function_with_return_annotation + x.method() #$ tt=Foo.method + +def function_without_return_annotation(): + return eval("Foo()") + +def test_no_return_annotation(): + x = function_without_return_annotation() #$ pt,tt=function_without_return_annotation + x.method() + +def test_variable_annotation(): + x = eval("Foo()") + x : Foo + # Currently fails because there is no flow from the class definition to the type annotation. + x.method() #$ MISSING: tt=Foo.method + +def test_no_variable_annotation(): + x = eval("Foo()") + x.method() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy