Skip to content

Commit 20febb6

Browse files
authored
Merge pull request #9567 from RasmusWL/typetracker-decorators
Python: allow class decorators in `.getASubclass()`
2 parents d3ec8a8 + f89b321 commit 20febb6

File tree

4 files changed

+34
-4
lines changed

4 files changed

+34
-4
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+
* Change `.getASubclass()` on `API::Node` so it allows to follow subclasses even if the class has a class decorator.

python/ql/lib/semmle/python/ApiGraphs.qll

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,16 @@ module API {
680680
or
681681
// Subclassing a node
682682
lbl = Label::subclass() and
683-
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
684-
ref.asExpr().(PY::ClassExpr).getABase() = superclass.asExpr()
683+
exists(PY::ClassExpr clsExpr, DataFlow::Node superclass | pred.flowsTo(superclass) |
684+
clsExpr.getABase() = superclass.asExpr() and
685+
// Potentially a class decorator could do anything, but we assume they are
686+
// "benign" and let subclasses edges flow through anyway.
687+
// see example in https://github.com/django/django/blob/c2250cfb80e27cdf8d098428824da2800a18cadf/tests/auth_tests/test_views.py#L40-L46
688+
(
689+
ref.asExpr() = clsExpr
690+
or
691+
ref.asExpr() = clsExpr.getADecoratorCall()
692+
)
685693
)
686694
or
687695
// awaiting

python/ql/test/experimental/dataflow/typetracking/test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def foo():
6565
also_x = foo() # $ MISSING: tracked
6666
print(also_x) # $ MISSING: tracked
6767

68+
# ------------------------------------------------------------------------------
69+
# Function decorator
70+
# ------------------------------------------------------------------------------
6871

6972
def my_decorator(func):
7073
# This part doesn't make any sense in a normal decorator, but just shows how we
@@ -135,7 +138,7 @@ def meth3(self):
135138
def track_self(self): # $ tracked_self
136139
self.meth1() # $ tracked_self
137140
super().meth2()
138-
super(Bar, self).foo3() # $ tracked_self
141+
super(Bar, self).meth3() # $ tracked_self
139142

140143
# ------------------------------------------------------------------------------
141144
# Tracking of attribute lookup after "long" import chain

python/ql/test/library-tests/ApiGraphs/py3/deftest2.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,19 @@ class IntMyView(View): #$ use=moduleImport("pflask").getMember("views").getMembe
1616
def my_internal_method(self): #$ def=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_method")
1717
pass
1818

19-
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
19+
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
20+
21+
# ------------------------------------------------------------------------------
22+
# Class decorator
23+
# ------------------------------------------------------------------------------
24+
25+
def my_class_decorator(cls):
26+
print("dummy decorator")
27+
return cls
28+
29+
@my_class_decorator
30+
class MyViewWithDecorator(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
31+
pass
32+
33+
class SubclassFromDecorated(MyViewWithDecorator): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getASubclass()
34+
pass

0 commit comments

Comments
 (0)