Skip to content

Commit d6e6825

Browse files
committed
Python: API-graphs: allow class decorators in .getASubclass()
1 parent 5f32f89 commit d6e6825

File tree

3 files changed

+17
-3
lines changed

3 files changed

+17
-3
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: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,18 @@ module API {
591591
or
592592
// Subclassing a node
593593
lbl = Label::subclass() and
594-
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
595-
ref.asExpr().(PY::ClassExpr).getABase() = superclass.asExpr()
594+
exists(PY::ClassExpr clsExpr, DataFlow::Node superclass | pred.flowsTo(superclass) |
595+
clsExpr.getABase() = superclass.asExpr() and
596+
// Potentially a class decorator could do anything, but we assume they are
597+
// "benign" and let subclasses edges flow through anyway.
598+
// see example in https://github.com/django/django/blob/c2250cfb80e27cdf8d098428824da2800a18cadf/tests/auth_tests/test_views.py#L40-L46
599+
(
600+
not exists(clsExpr.getADecorator()) and
601+
ref.asExpr() = clsExpr
602+
or
603+
ref.asExpr() = clsExpr.getADecoratorCall() and
604+
not exists(PY::Call otherDecorator | otherDecorator.getArg(0) = ref.asExpr())
605+
)
596606
)
597607
or
598608
// awaiting

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ def my_class_decorator(cls):
3030
class MyViewWithDecorator(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
3131
pass
3232

33-
class SubclassFromDecorated(MyViewWithDecorator): #$ MISSING: use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getASubclass()
33+
class SubclassFromDecorated(MyViewWithDecorator): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getASubclass()
3434
pass

0 commit comments

Comments
 (0)