Skip to content

Commit a78f13c

Browse files
committed
Python: Ignore known subclass models
1 parent 24a3a23 commit a78f13c

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
lines changed

python/ql/lib/semmle/python/frameworks/data/internal/subclass-capture/auto-rest_framework.model.yml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/ql/lib/semmle/python/frameworks/internal/SubclassFinder.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,18 @@ module NotExposed {
7272
*/
7373
abstract API::Node getAlreadyModeledClass();
7474

75+
/**
76+
* Gets the fully qualified name that this spec represents.
77+
*
78+
* This should be implemented by all classes for which `getSuperClass` is
79+
* implemented, at least if they are defined in a different module than what they
80+
* subclass.
81+
*/
82+
string getFullyQualifiedName() { none() }
83+
7584
FindSubclassesSpec getSuperClass() { none() }
85+
86+
final FindSubclassesSpec getSubClass() { result.getSuperClass() = this }
7687
}
7788

7889
/**

python/ql/src/meta/ClassHierarchy/Find.ql

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ class RestFrameworkResponse extends FindSubclassesSpec {
372372
override API::Node getAlreadyModeledClass() { result = RestFramework::Response::classRef() }
373373

374374
override DjangoHttpResponse getSuperClass() { any() }
375+
376+
override string getFullyQualifiedName() { result = "rest_framework.response.Response" }
375377
}
376378

377379
class SqlAlchemyEngine extends FindSubclassesSpec {
@@ -520,21 +522,30 @@ predicate fullyQualifiedToYamlFormat(string fullyQualified, string type2, string
520522
from FindSubclassesSpec spec, string newModelFullyQualified, string type2, string path, Module mod
521523
where
522524
newModel(spec, newModelFullyQualified, _, mod, _) and
523-
// Since a class C which is a subclass for flask.MethodView is always a subclass of
524-
// flask.View, and we chose to care about this distinction, in a naive approach we
525-
// would always record rows for _both_ specs... that's just wasteful, so instead we
526-
// only record the row for the more specific spec -- this is captured by the
527-
// .getSuperClass() method on a spec, which can links specs together in this way.
528-
// However, if the definition actually depends on some logic, like below, we should
529-
// still record both rows
530-
// ```
531-
// if <cond>:
532-
// class C(flask.View): ...
533-
// else:
534-
// class C(flask.MethodView): ...
535-
// ```
536525
not exists(FindSubclassesSpec subclass | subclass.getSuperClass() = spec |
526+
// Since a class C which is a subclass for flask.MethodView is always a subclass of
527+
// flask.View, and we chose to care about this distinction, in a naive approach we
528+
// would always record rows for _both_ specs... that's just wasteful, so instead we
529+
// only record the row for the more specific spec -- this is captured by the
530+
// .getSuperClass() method on a spec, which can links specs together in this way.
531+
// However, if the definition actually depends on some logic, like below, we should
532+
// still record both rows
533+
// ```
534+
// if <cond>:
535+
// class C(flask.View): ...
536+
// else:
537+
// class C(flask.MethodView): ...
538+
// ```
537539
newModel(subclass, newModelFullyQualified, _, mod, _)
540+
or
541+
// When defining specs for both foo.Foo and bar.Bar, and you encounter the class
542+
// definition for Bar as `class Bar(foo.Foo): ...` inside `__init__.py` of the `bar`
543+
// PyPI package, we would normally record this new class as being an unmodeled
544+
// subclass of foo.Foo (since the class definition is not found when using
545+
// API::moduleImport("bar").getMember("Bar")). However, we don't actually want to
546+
// treat this as foo.Foo, since it's actually bar.Bar -- so we use the fully
547+
// qualified name ot ignore cases such as this!
548+
newModelFullyQualified = subclass.getFullyQualifiedName()
538549
) and
539550
fullyQualifiedToYamlFormat(newModelFullyQualified, type2, path) and
540551
not Extensions::typeModel(spec, type2, path) and

0 commit comments

Comments
 (0)