Skip to content

Commit 8afd560

Browse files
committed
Python: ORM: Handle load of PolymorphicModels
1 parent 48fba87 commit 8afd560

File tree

2 files changed

+27
-7
lines changed

2 files changed

+27
-7
lines changed

python/ql/lib/semmle/python/frameworks/Django.qll

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,26 @@ module PrivateDjango {
852852
)
853853
}
854854

855+
/**
856+
* Gets the synthetic node where data could be loaded from, when a fetch is
857+
* made on `modelClass`.
858+
*
859+
* In vanilla Django inheritance, this is simply the model itself, but if a
860+
* model is based on `polymorphic.models.PolymorphicModel`, a fetch of the
861+
* base-class can also yield instances of its subclasses.
862+
*/
863+
SyntheticDjangoOrmModelNode nodeToLoadFrom(API::Node modelClass) {
864+
result.getModelClass() = modelClass
865+
or
866+
exists(API::Node polymorphicModel |
867+
polymorphicModel =
868+
API::moduleImport("polymorphic").getMember("models").getMember("PolymorphicModel")
869+
|
870+
polymorphicModel.getASubclass+() = modelClass and
871+
modelClass.getASubclass+() = result.getModelClass()
872+
)
873+
}
874+
855875
/** Additional data-flow steps for Django ORM models. */
856876
class DjangOrmSteps extends AdditionalOrmSteps {
857877
override predicate storeStep(
@@ -908,15 +928,15 @@ module PrivateDjango {
908928
or
909929
// synthetic -> method-call that returns collection of ORM models (all/filter/...)
910930
exists(API::Node modelClass |
911-
nodeFrom.(SyntheticDjangoOrmModelNode).getModelClass() = modelClass and
931+
nodeFrom = nodeToLoadFrom(modelClass) and
912932
nodeTo.(Model::QuerySetMethodInstanceCollection).getModelClass() = modelClass and
913933
nodeTo.(Model::QuerySetMethodInstanceCollection).isDbFetch() and
914934
c instanceof DataFlow::ListElementContent
915935
)
916936
or
917937
// synthetic -> method-call that returns dictionary with ORM models as values
918938
exists(API::Node modelClass |
919-
nodeFrom.(SyntheticDjangoOrmModelNode).getModelClass() = modelClass and
939+
nodeFrom = nodeToLoadFrom(modelClass) and
920940
nodeTo.(Model::QuerySetMethodInstanceDictValue).getModelClass() = modelClass and
921941
nodeTo.(Model::QuerySetMethodInstanceDictValue).isDbFetch() and
922942
c instanceof DataFlow::DictionaryElementAnyContent
@@ -938,9 +958,9 @@ module PrivateDjango {
938958
or
939959
// synthetic -> method-call that returns single ORM model (get/first/...)
940960
exists(API::Node modelClass |
961+
nodeFrom = nodeToLoadFrom(modelClass) and
941962
nodeTo.(Model::InstanceSource).getModelClass() = modelClass and
942-
nodeTo.(Model::InstanceSource).isDbFetch() and
943-
nodeFrom.(SyntheticDjangoOrmModelNode).getModelClass() = modelClass
963+
nodeTo.(Model::InstanceSource).isDbFetch()
944964
)
945965
}
946966
}

python/ql/test/library-tests/frameworks/django-orm/testapp/orm_inheritance.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ def poly_fetch_book(id, test_for_subclass=True):
154154

155155
if isinstance(book, PolyPhysicalBook):
156156
SINK(book.title) # $ flow="SOURCE, l:+11 -> book.title" SPURIOUS: flow="SOURCE, l:-23 -> book.title"
157-
SINK(book.physical_location) # $ MISSING: flow
158-
SINK(book.same_name_different_value) # $ MISSING: flow
157+
SINK(book.physical_location) # $ flow="SOURCE, l:+11 -> book.physical_location"
158+
SINK(book.same_name_different_value) # $ flow="SOURCE, l:+11 -> book.same_name_different_value"
159159
elif isinstance(book, PolyEBook):
160160
SINK_F(book.title) # $ SPURIOUS: flow="SOURCE, l:-27 -> book.title" flow="SOURCE, l:+7 -> book.title"
161161
SINK_F(book.download_link)
162-
SINK_F(book.same_name_different_value)
162+
SINK_F(book.same_name_different_value) # $ SPURIOUS: flow="SOURCE, l:+7 -> book.same_name_different_value"
163163

164164

165165
def poly_save_physical_book():

0 commit comments

Comments
 (0)