Merge pull request #7016 from RasmusWL/django-rest-framework

Python: Model Django REST framework
This commit is contained in:
yoff
2021-11-12 14:27:56 +01:00
committed by GitHub
32 changed files with 1257 additions and 12 deletions

View File

@@ -30,6 +30,7 @@ private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Psycopg2
private import semmle.python.frameworks.Pydantic
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.RestFramework
private import semmle.python.frameworks.Rsa
private import semmle.python.frameworks.RuamelYaml
private import semmle.python.frameworks.Simplejson

View File

@@ -17,10 +17,12 @@ private import semmle.python.frameworks.internal.SelfRefMixin
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
/**
* INTERNAL: Do not use.
*
* Provides models for the `django` PyPI package.
* See https://www.djangoproject.com/.
*/
private module Django {
module Django {
/** Provides models for the `django.views` module */
module Views {
/**
@@ -367,6 +369,52 @@ private module Django {
}
}
/**
* Provides models for the `django.contrib.auth.models.User` class
*
* See https://docs.djangoproject.com/en/3.2/ref/contrib/auth/#user-model.
*/
module User {
/**
* A source of instances of `django.contrib.auth.models.User`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `User::instance()` to get references to instances of `django.contrib.auth.models.User`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** Gets a reference to an instance of `django.contrib.auth.models.User`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `django.contrib.auth.models.User`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Taint propagation for `django.contrib.auth.models.User`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "django.contrib.auth.models.User" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() {
result in ["username", "first_name", "last_name", "email"]
}
override string getMethodName() { none() }
override string getAsyncMethodName() { none() }
}
}
/**
* Provides models for the `django.core.files.uploadedfile.UploadedFile` class
*
@@ -466,10 +514,12 @@ private module Django {
}
/**
* INTERNAL: Do not use.
*
* Provides models for the `django` PyPI package (that we are not quite ready to publicly expose yet).
* See https://www.djangoproject.com/.
*/
private module PrivateDjango {
module PrivateDjango {
// ---------------------------------------------------------------------------
// django
// ---------------------------------------------------------------------------
@@ -496,6 +546,7 @@ private module PrivateDjango {
/** Gets a reference to the `django.db.connection` object. */
API::Node connection() { result = db().getMember("connection") }
/** A `django.db.connection` is a PEP249 compliant DB connection. */
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
DjangoDbConnection() { this = connection().getAUse() }
}
@@ -692,6 +743,7 @@ private module PrivateDjango {
/** Provides models for the `django.conf` module */
module conf {
/** Provides models for the `django.conf.urls` module */
module conf_urls {
// -------------------------------------------------------------------------
// django.conf.urls
@@ -890,6 +942,7 @@ private module PrivateDjango {
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.
*/
module HttpResponse {
/** Gets a reference to the `django.http.response.HttpResponse` class. */
API::Node baseClassRef() {
result = response().getMember("HttpResponse")
or
@@ -897,7 +950,7 @@ private module PrivateDjango {
result = http().getMember("HttpResponse")
}
/** Gets a reference to the `django.http.response.HttpResponse` class. */
/** Gets a reference to the `django.http.response.HttpResponse` class or any subclass. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
/**
@@ -1893,14 +1946,11 @@ private module PrivateDjango {
* with the django framework.
*
* Most functions take a django HttpRequest as a parameter (but not all).
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `DjangoRouteHandler::Range` instead.
*/
private class DjangoRouteHandler extends Function {
DjangoRouteHandler() {
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
or
any(DjangoViewClass vc).getARequestHandler() = this
}
class DjangoRouteHandler extends Function instanceof DjangoRouteHandler::Range {
/**
* Gets the index of the parameter where the first routed parameter can be passed --
* that is, the one just after any possible `self` or HttpRequest parameters.
@@ -1920,6 +1970,24 @@ private module PrivateDjango {
Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) }
}
/** Provides a class for modeling new django route handlers. */
module DjangoRouteHandler {
/**
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `DjangoRouteHandler` instead.
*/
abstract class Range extends Function { }
/** Route handlers from normal usage of django. */
private class StandardDjangoRouteHandlers extends Range {
StandardDjangoRouteHandlers() {
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
or
any(DjangoViewClass vc).getARequestHandler() = this
}
}
}
/**
* A method named `get_redirect_url` on a django view class.
*
@@ -1941,7 +2009,7 @@ private module PrivateDjango {
}
/** A data-flow node that sets up a route on a server, using the django framework. */
abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
abstract class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
/** Gets the data-flow node that is used as the argument for the view handler. */
abstract DataFlow::Node getViewArg();

View File

@@ -0,0 +1,369 @@
/**
* Provides classes modeling security-relevant aspects of the `djangorestframework` PyPI package
* (imported as `rest_framework`)
*
* See
* - https://www.django-rest-framework.org/
* - https://pypi.org/project/djangorestframework/
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.Django
private import semmle.python.frameworks.Stdlib
/**
* INTERNAL: Do not use.
*
* Provides models for the `djangorestframework` PyPI package
* (imported as `rest_framework`)
*
* See
* - https://www.django-rest-framework.org/
* - https://pypi.org/project/djangorestframework/
*/
private module RestFramework {
// ---------------------------------------------------------------------------
// rest_framework.views.APIView handling
// ---------------------------------------------------------------------------
/**
* An `API::Node` representing the `rest_framework.views.APIView` class or any subclass
* that has explicitly been modeled in the CodeQL libraries.
*/
private class ModeledApiViewClasses extends Django::Views::View::ModeledSubclass {
ModeledApiViewClasses() {
this = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
or
// imports generated by python/frameworks/internal/SubclassFinder.qll
this =
API::moduleImport("rest_framework")
.getMember("authtoken")
.getMember("views")
.getMember("APIView")
or
this =
API::moduleImport("rest_framework")
.getMember("authtoken")
.getMember("views")
.getMember("ObtainAuthToken")
or
this = API::moduleImport("rest_framework").getMember("decorators").getMember("APIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("CreateAPIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("DestroyAPIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("GenericAPIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("ListAPIView")
or
this =
API::moduleImport("rest_framework").getMember("generics").getMember("ListCreateAPIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("RetrieveAPIView")
or
this =
API::moduleImport("rest_framework")
.getMember("generics")
.getMember("RetrieveDestroyAPIView")
or
this =
API::moduleImport("rest_framework").getMember("generics").getMember("RetrieveUpdateAPIView")
or
this =
API::moduleImport("rest_framework")
.getMember("generics")
.getMember("RetrieveUpdateDestroyAPIView")
or
this = API::moduleImport("rest_framework").getMember("generics").getMember("UpdateAPIView")
or
this = API::moduleImport("rest_framework").getMember("routers").getMember("APIRootView")
or
this = API::moduleImport("rest_framework").getMember("routers").getMember("SchemaView")
or
this =
API::moduleImport("rest_framework")
.getMember("schemas")
.getMember("views")
.getMember("APIView")
or
this =
API::moduleImport("rest_framework")
.getMember("schemas")
.getMember("views")
.getMember("SchemaView")
or
this = API::moduleImport("rest_framework").getMember("viewsets").getMember("GenericViewSet")
or
this = API::moduleImport("rest_framework").getMember("viewsets").getMember("ModelViewSet")
or
this =
API::moduleImport("rest_framework").getMember("viewsets").getMember("ReadOnlyModelViewSet")
or
this = API::moduleImport("rest_framework").getMember("viewsets").getMember("ViewSet")
}
}
/**
* A class that has a super-type which is a rest_framework APIView class, therefore also
* becoming a APIView class.
*/
class RestFrameworkApiViewClass extends PrivateDjango::DjangoViewClassFromSuperClass {
RestFrameworkApiViewClass() {
this.getABase() = any(ModeledApiViewClasses c).getASubclass*().getAUse().asExpr()
}
override Function getARequestHandler() {
result = super.getARequestHandler()
or
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
result = this.getAMethod() and
result.getName() in [
// these method names where found by looking through the APIView
// implementation in
// https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L104
"initial", "http_method_not_allowed", "permission_denied", "throttled",
"get_authenticate_header", "perform_content_negotiation", "perform_authentication",
"check_permissions", "check_object_permissions", "check_throttles", "determine_version",
"initialize_request", "finalize_response", "dispatch", "options"
]
}
}
// ---------------------------------------------------------------------------
// rest_framework.decorators.api_view handling
// ---------------------------------------------------------------------------
/**
* A function that is a request handler since it is decorated with `rest_framework.decorators.api_view`
*/
class RestFrameworkFunctionBasedView extends PrivateDjango::DjangoRouteHandler::Range {
RestFrameworkFunctionBasedView() {
this.getADecorator() =
API::moduleImport("rest_framework")
.getMember("decorators")
.getMember("api_view")
.getACall()
.asExpr()
}
}
/**
* Ensuring that all `RestFrameworkFunctionBasedView` are also marked as a
* `HTTP::Server::RequestHandler`. We only need this for the ones that doesn't have a
* known route setup.
*/
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
RestFrameworkFunctionBasedViewWithoutKnownRoute() {
not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
}
override Parameter getARoutedParameter() {
// Since we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
result in [this.getArg(_), this.getArgByName(_)] and
not result = any(int i | i < this.getFirstPossibleRoutedParamIndex() | this.getArg(i))
}
override string getFramework() { result = "Django (rest_framework)" }
}
// ---------------------------------------------------------------------------
// request modeling
// ---------------------------------------------------------------------------
/**
* A parameter that will receive a `rest_framework.request.Request` instance when a
* request handler is invoked.
*/
private class RestFrameworkRequestHandlerRequestParam extends Request::InstanceSource,
RemoteFlowSource::Range, DataFlow::ParameterNode {
RestFrameworkRequestHandlerRequestParam() {
// rest_framework.views.APIView subclass
exists(RestFrameworkApiViewClass vc |
this.getParameter() =
vc.getARequestHandler().(PrivateDjango::DjangoRouteHandler).getRequestParam()
)
or
// annotated with @api_view decorator
exists(PrivateDjango::DjangoRouteHandler rh | rh instanceof RestFrameworkFunctionBasedView |
this.getParameter() = rh.getRequestParam()
)
}
override string getSourceType() { result = "rest_framework.request.HttpRequest" }
}
/**
* Provides models for the `rest_framework.request.Request` class
*
* See https://www.django-rest-framework.org/api-guide/requests/.
*/
module Request {
/** Gets a reference to the `rest_framework.request.Request` class. */
private API::Node classRef() {
result = API::moduleImport("rest_framework").getMember("request").getMember("Request")
}
/**
* A source of instances of `rest_framework.request.Request`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Request::instance()` to get references to instances of `rest_framework.request.Request`.
*/
abstract class InstanceSource extends PrivateDjango::django::http::request::HttpRequest::InstanceSource {
}
/** A direct instantiation of `rest_framework.request.Request`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
}
/** Gets a reference to an instance of `rest_framework.request.Request`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `rest_framework.request.Request`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Taint propagation for `rest_framework.request.Request`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "rest_framework.request.Request" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() {
result in ["data", "query_params", "user", "auth", "content_type", "stream"]
}
override string getMethodName() { none() }
override string getAsyncMethodName() { none() }
}
/** An attribute read that is a `MultiValueDict` instance. */
private class MultiValueDictInstances extends Django::MultiValueDict::InstanceSource {
MultiValueDictInstances() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "query_params"
}
}
/** An attribute read that is a `User` instance. */
private class UserInstances extends Django::User::InstanceSource {
UserInstances() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "user"
}
}
/** An attribute read that is a file-like instance. */
private class FileLikeInstances extends Stdlib::FileLikeObject::InstanceSource {
FileLikeInstances() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "stream"
}
}
}
// ---------------------------------------------------------------------------
// response modeling
// ---------------------------------------------------------------------------
/**
* Provides models for the `rest_framework.response.Response` class
*
* See https://www.django-rest-framework.org/api-guide/responses/.
*/
module Response {
/** Gets a reference to the `rest_framework.response.Response` class. */
private API::Node classRef() {
result = API::moduleImport("rest_framework").getMember("response").getMember("Response")
}
/**
* A source of instances of `rest_framework.response.Response`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Response::instance()` to get references to instances of `rest_framework.response.Response`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** A direct instantiation of `rest_framework.response.Response`. */
private class ClassInstantiation extends PrivateDjango::django::http::response::HttpResponse::InstanceSource,
DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
override DataFlow::Node getMimetypeOrContentTypeArg() {
result in [this.getArg(5), this.getArgByName("content_type")]
}
override string getMimetypeDefault() { none() }
}
}
// ---------------------------------------------------------------------------
// Exception response modeling
// ---------------------------------------------------------------------------
/**
* Provides models for the `rest_framework.exceptions.APIException` class and subclasses
*
* See https://www.django-rest-framework.org/api-guide/exceptions/#api-reference
*/
module APIException {
/** A direct instantiation of `rest_framework.exceptions.APIException` or subclass. */
private class ClassInstantiation extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
string className;
ClassInstantiation() {
className in [
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
"NotAuthenticated", "PermissionDenied", "NotFound", "MethodNotAllowed", "NotAcceptable",
"UnsupportedMediaType", "Throttled"
] and
this =
API::moduleImport("rest_framework")
.getMember("exceptions")
.getMember(className)
.getACall()
}
override DataFlow::Node getBody() {
className in [
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
"NotAuthenticated", "PermissionDenied", "NotFound", "NotAcceptable"
] and
result = this.getArg(0)
or
className in ["MethodNotAllowed", "UnsupportedMediaType", "Throttled"] and
result = this.getArg(1)
or
result = this.getArgByName("detail")
}
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { none() }
}
}
}

View File

@@ -0,0 +1,209 @@
/**
* INTERNAL: Do not use.
*
* Has predicates to help find subclasses in library code. Should only be used to aid in
* the manual library modeling process,
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.ApiGraphs
private import semmle.python.filters.Tests
// very much inspired by the draft at https://github.com/github/codeql/pull/5632
private module NotExposed {
// Instructions:
// This needs to be automated better, but for this prototype, here are some rough instructions:
// 0) get a database of the library you are about to model
// 1) fill out the `getAlreadyModeledClass` body below
// 2) quick-eval the `quickEvalMe` predicate below, and copy the output to your modeling predicate
class MySpec extends FindSubclassesSpec {
MySpec() { this = "MySpec" }
override API::Node getAlreadyModeledClass() {
// FILL ME OUT ! (but don't commit with any changes)
none()
// for example
// result = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
}
}
predicate quickEvalMe(string newImport) {
newImport =
"// imports generated by python/frameworks/internal/SubclassFinder.qll\n" + "this = API::" +
concat(string newModelFullyQualified |
newModel(any(MySpec spec), newModelFullyQualified, _, _, _)
|
fullyQualifiedToAPIGraphPath(newModelFullyQualified), " or this = API::"
)
}
// ---------------------------------------------------------------------------
// Implementation below
// ---------------------------------------------------------------------------
//
// We are looking to find all subclassed of the already modelled classes, and ideally
// we would identify an `API::Node` for each (then `toString` would give the API
// path).
//
// An inherent problem with API graphs is that there doesn't need to exist a result
// for the API graph path that we want to add to our modeling (the path to the new
// subclass). As an example, the following query has no results when evaluated against
// a django/django DB.
//
// select API::moduleImport("django") .getMember("contrib") .getMember("admin")
// .getMember("views") .getMember("main") .getMember("ChangeListSearchForm")
//
//
// Since it is a Form subclass that we would want to capture for our Django modeling,
// we want to extend our modeling (that is written in a qll file) with exactly that
// piece of code, but since the API::Node doesn't exist, we can't select that from a
// predicate and print its path. We need a different approach, and for that we use
// fully qualified names to capture new classes/new aliases, and transform these into
// API paths (to be included in the modeling that is inserted into the `.qll` files),
// see `fullyQualifiedToAPIGraphPath`.
//
// NOTE: this implementation was originally created to help with automatically
// modeling packages in mind, and has been adjusted to help with manual library
// modeling. See https://github.com/github/codeql/pull/5632 for more discussion.
//
//
bindingset[fullyQaulified]
string fullyQualifiedToAPIGraphPath(string fullyQaulified) {
result = "moduleImport(\"" + fullyQaulified.replaceAll(".", "\").getMember(\"") + "\")"
}
bindingset[this]
abstract class FindSubclassesSpec extends string {
abstract API::Node getAlreadyModeledClass();
}
/**
* Holds if `newModelFullyQualified` describes either a new subclass, or a new alias, belonging to `spec` that we should include in our automated modeling.
* This new element is defined by `ast`, which is defined at `loc` in the module `mod`.
*/
query predicate newModel(
FindSubclassesSpec spec, string newModelFullyQualified, AstNode ast, Module mod, Location loc
) {
(
newSubclass(spec, newModelFullyQualified, ast, mod, loc)
or
newDirectAlias(spec, newModelFullyQualified, ast, mod, loc)
or
newImportStar(spec, newModelFullyQualified, ast, mod, _, _, loc)
)
}
API::Node newOrExistingModeling(FindSubclassesSpec spec) {
result = spec.getAlreadyModeledClass()
or
exists(string newSubclassName |
newModel(spec, newSubclassName, _, _, _) and
result.getPath() = fullyQualifiedToAPIGraphPath(newSubclassName)
)
}
bindingset[fullyQualifiedName]
predicate alreadyModeled(FindSubclassesSpec spec, string fullyQualifiedName) {
fullyQualifiedToAPIGraphPath(fullyQualifiedName) = spec.getAlreadyModeledClass().getPath()
}
predicate isNonTestProjectCode(AstNode ast) {
not ast.getScope*() instanceof TestScope and
not ast.getLocation().getFile().getRelativePath().matches("tests/%") and
exists(ast.getLocation().getFile().getRelativePath())
}
predicate hasAllStatement(Module mod) {
exists(AssignStmt a, GlobalVariable all |
a.defines(all) and
a.getScope() = mod and
all.getId() = "__all__"
)
}
/**
* Holds if `newAliasFullyQualified` describes new alias originating from the import
* `from <module> import <member> [as <new-name>]`, where `<module>.<member>` belongs to
* `spec`.
* So if this import happened in module `foo.bar`, `newAliasFullyQualified` would be
* `foo.bar.<member>` (or `foo.bar.<new-name>`).
*
* Note that this predicate currently respects `__all__` in sort of a backwards fashion.
* - if `__all__` is defined in module `foo.bar`, we only allow new aliases where the member name is also in `__all__`. (this doesn't map 100% to the semantics of imports though)
* - If `__all__` is not defined we don't impose any limitations.
*
* Also note that we don't currently consider deleting module-attributes at all, so in the code snippet below, we would consider that `my_module.foo` is a
* reference to `django.foo`, although `my_module.foo` isn't even available at runtime. (there currently also isn't any code to discover that `my_module.bar`
* is an alias to `django.foo`)
* ```py
* # module my_module
* from django import foo
* bar = foo
* del foo
* ```
*/
predicate newDirectAlias(
FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod,
Location loc
) {
importMember = newOrExistingModeling(spec).getAUse().asExpr() and
importMember.getScope() = mod and
loc = importMember.getLocation() and
(
mod.isPackageInit() and
newAliasFullyQualified = mod.getPackageName() + "." + importMember.getName()
or
not mod.isPackageInit() and
newAliasFullyQualified = mod.getName() + "." + importMember.getName()
) and
(
not hasAllStatement(mod)
or
mod.declaredInAll(importMember.getName())
) and
not alreadyModeled(spec, newAliasFullyQualified) and
isNonTestProjectCode(importMember)
}
/** same as `newDirectAlias` predicate, but handling `from <module> import *`, considering all `<member>`, where `<module>.<member>` belongs to `spec`. */
predicate newImportStar(
FindSubclassesSpec spec, string newAliasFullyQualified, ImportStar importStar, Module mod,
API::Node relevantClass, string relevantName, Location loc
) {
relevantClass = newOrExistingModeling(spec) and
loc = importStar.getLocation() and
importStar.getScope() = mod and
// WHAT A HACK :D :D
relevantClass.getPath() =
relevantClass.getAPredecessor().getPath() + ".getMember(\"" + relevantName + "\")" and
relevantClass.getAPredecessor().getAUse().asExpr() = importStar.getModule() and
(
mod.isPackageInit() and
newAliasFullyQualified = mod.getPackageName() + "." + relevantName
or
not mod.isPackageInit() and
newAliasFullyQualified = mod.getName() + "." + relevantName
) and
(
not hasAllStatement(mod)
or
mod.declaredInAll(relevantName)
) and
not alreadyModeled(spec, newAliasFullyQualified) and
isNonTestProjectCode(importStar)
}
/** Holds if `classExpr` defines a new subclass that belongs to `spec`, which has the fully qualified name `newSubclassQualified`. */
predicate newSubclass(
FindSubclassesSpec spec, string newSubclassQualified, ClassExpr classExpr, Module mod,
Location loc
) {
classExpr = newOrExistingModeling(spec).getASubclass*().getAUse().asExpr() and
classExpr.getScope() = mod and
newSubclassQualified = mod.getName() + "." + classExpr.getName() and
loc = classExpr.getLocation() and
not alreadyModeled(spec, newSubclassQualified) and
isNonTestProjectCode(classExpr)
}
}