mirror of
https://github.com/github/codeql.git
synced 2026-04-26 09:15:12 +02:00
Merge pull request #15044 from RasmusWL/automated-subclass-models
Python: Automated subclass models
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Captured subclass relationships ahead-of-time for most popular PyPI packages so we are able to resolve subclass relationships even without having the packages installed. For example we have captured that `flask_restful.Resource` is a subclass of `flask.views.MethodView`, so our Flask modeling will still consider a function named `post` on a `class Foo(flask_restful.Resource):` as a HTTP request handler.
|
||||
@@ -177,7 +177,7 @@ private predicate legalDottedName(string name) {
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
private predicate legalShortName(string name) { name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") }
|
||||
predicate legalShortName(string name) { name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") }
|
||||
|
||||
private string moduleNameFromBase(Container file) {
|
||||
// We used to also require `isPotentialPackage(f)` to hold in this case,
|
||||
|
||||
@@ -9,6 +9,7 @@ private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.ClickhouseDriver
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -24,6 +25,8 @@ module Aioch {
|
||||
/** Gets a reference to the `aioch.Client` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("aioch").getMember("Client").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("aioch.Client~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `clickhouse_driver.Client` or any subclass. */
|
||||
|
||||
@@ -14,6 +14,7 @@ private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
private import semmle.python.frameworks.Multidict
|
||||
private import semmle.python.frameworks.Yarl
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -31,6 +32,8 @@ module AiohttpWebModel {
|
||||
/** Gets a reference to the `aiohttp.web.View` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("aiohttp").getMember("web").getMember("View").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("aiohttp.web.View~Subclass").getASubclass*()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,10 +709,12 @@ module AiohttpWebModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the web server part (`aiohttp.client`) of the `aiohttp` PyPI package.
|
||||
* See https://docs.aiohttp.org/en/stable/client.html
|
||||
*/
|
||||
private module AiohttpClientModel {
|
||||
module AiohttpClientModel {
|
||||
/**
|
||||
* Provides models for the `aiohttp.ClientSession` class
|
||||
*
|
||||
@@ -717,8 +722,10 @@ private module AiohttpClientModel {
|
||||
*/
|
||||
module ClientSession {
|
||||
/** Gets a reference to the `aiohttp.ClientSession` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("aiohttp").getMember("ClientSession")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("aiohttp.ClientSession~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.ClientSession`. */
|
||||
|
||||
@@ -9,6 +9,7 @@ private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.PEP249
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -37,6 +38,9 @@ module ClickhouseDriver {
|
||||
or
|
||||
// commonly used alias
|
||||
classRef = API::moduleImport("clickhouse_driver").getMember("Client")
|
||||
or
|
||||
// Models-as-Data subclass
|
||||
classRef = ModelOutput::getATypeNode("clickhouse_driver.client.Client~Subclass")
|
||||
|
|
||||
result = classRef.getASubclass*()
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.security.dataflow.UrlRedirectCustomizations
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -85,6 +86,10 @@ module Django {
|
||||
}
|
||||
}
|
||||
|
||||
private class MaDSubclass extends ModeledSubclass {
|
||||
MaDSubclass() { this = ModelOutput::getATypeNode("Django.Views.View~Subclass") }
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.views.generic.View` class or any subclass. */
|
||||
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
|
||||
}
|
||||
@@ -185,6 +190,10 @@ module Django {
|
||||
}
|
||||
}
|
||||
|
||||
private class MaDSubclass extends ModeledSubclass {
|
||||
MaDSubclass() { this = ModelOutput::getATypeNode("django.forms.BaseForm~Subclass") }
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
|
||||
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
|
||||
}
|
||||
@@ -290,6 +299,10 @@ module Django {
|
||||
}
|
||||
}
|
||||
|
||||
private class MaDSubclass extends ModeledSubclass {
|
||||
MaDSubclass() { this = ModelOutput::getATypeNode("Django.Forms.Field~Subclass") }
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
|
||||
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
|
||||
}
|
||||
@@ -596,6 +609,8 @@ module PrivateDjango {
|
||||
.getMember("models")
|
||||
.getMember("PolymorphicModel")
|
||||
.getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("Django.db.models.Model~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,6 +781,9 @@ module PrivateDjango {
|
||||
.getMember(className)
|
||||
.getASubclass*()
|
||||
)
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.db.models.FileField~Subclass").getASubclass*()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,6 +841,10 @@ module PrivateDjango {
|
||||
or
|
||||
// Commonly used alias
|
||||
result = models().getMember("RawSQL")
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.db.models.expressions.RawSQL~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1157,6 +1179,9 @@ module PrivateDjango {
|
||||
or
|
||||
// handle django.http.HttpRequest alias
|
||||
result = http().getMember("HttpRequest")
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.request.HttpRequest~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1322,7 +1347,13 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponse` class or any subclass. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*()
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponse~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponse`, extend this class to model new instances.
|
||||
@@ -1383,7 +1414,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to a subclass of the `django.http.response.HttpResponseRedirect` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseRedirect~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseRedirect`, extend this class to model new instances.
|
||||
@@ -1446,7 +1482,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponsePermanentRedirect` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponsePermanentRedirect~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponsePermanentRedirect`, extend this class to model new instances.
|
||||
@@ -1510,7 +1551,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseNotModified` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseNotModified~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseNotModified`, extend this class to model new instances.
|
||||
@@ -1562,7 +1608,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseBadRequest` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseBadRequest~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseBadRequest`, extend this class to model new instances.
|
||||
@@ -1616,7 +1667,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseNotFound` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseNotFound~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseNotFound`, extend this class to model new instances.
|
||||
@@ -1670,7 +1726,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseForbidden` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseForbidden~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseForbidden`, extend this class to model new instances.
|
||||
@@ -1724,7 +1785,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseNotAllowed` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseNotAllowed~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseNotAllowed`, extend this class to model new instances.
|
||||
@@ -1779,7 +1845,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseGone` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseGone~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseGone`, extend this class to model new instances.
|
||||
@@ -1833,7 +1904,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponseServerError` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.HttpResponseServerError~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.HttpResponseServerError`, extend this class to model new instances.
|
||||
@@ -1887,7 +1963,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.JsonResponse` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.JsonResponse~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.JsonResponse`, extend this class to model new instances.
|
||||
@@ -1944,7 +2025,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.StreamingHttpResponse` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.StreamingHttpResponse~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.StreamingHttpResponse`, extend this class to model new instances.
|
||||
@@ -1998,7 +2084,12 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.FileResponse` class. */
|
||||
API::Node classRef() { result = baseClassRef().getASubclass*() }
|
||||
API::Node classRef() {
|
||||
result = baseClassRef().getASubclass*() or
|
||||
result =
|
||||
ModelOutput::getATypeNode("django.http.response.FileResponse~Subclass")
|
||||
.getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `django.http.response.FileResponse`, extend this class to model new instances.
|
||||
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
|
||||
@@ -65,12 +66,14 @@ private module FabricV1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
|
||||
* version 2.x.
|
||||
*
|
||||
* See http://docs.fabfile.org/en/2.5/getting-st arted.html.
|
||||
*/
|
||||
private module FabricV2 {
|
||||
module FabricV2 {
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
API::Node fabric() { result = API::moduleImport("fabric") }
|
||||
|
||||
@@ -95,6 +98,9 @@ private module FabricV2 {
|
||||
result = fabric().getMember("Connection")
|
||||
or
|
||||
result = connection().getMember("Connection")
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("fabric.connection.Connection~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,18 +11,23 @@ private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.Pydantic
|
||||
private import semmle.python.frameworks.Starlette
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `fastapi` PyPI package.
|
||||
* See https://fastapi.tiangolo.com/.
|
||||
*/
|
||||
private module FastApi {
|
||||
module FastApi {
|
||||
/**
|
||||
* Provides models for FastAPI applications (an instance of `fastapi.FastAPI`).
|
||||
*/
|
||||
module App {
|
||||
API::Node cls() { result = API::moduleImport("fastapi").getMember("FastAPI") }
|
||||
|
||||
/** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */
|
||||
API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() }
|
||||
API::Node instance() { result = cls().getReturn() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,10 +36,14 @@ private module FastApi {
|
||||
* See https://fastapi.tiangolo.com/tutorial/bigger-applications/.
|
||||
*/
|
||||
module ApiRouter {
|
||||
/** Gets a reference to an instance of `fastapi.ApiRouter`. */
|
||||
API::Node instance() {
|
||||
result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*().getReturn()
|
||||
API::Node cls() {
|
||||
result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("fastapi.APIRouter~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fastapi.ApiRouter`. */
|
||||
API::Node instance() { result = cls().getReturn() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -14,6 +14,7 @@ private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.security.dataflow.PathInjectionCustomizations
|
||||
private import semmle.python.dataflow.new.FlowSummary
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides models for the `flask` PyPI package.
|
||||
@@ -39,6 +40,10 @@ module Flask {
|
||||
"MethodView"
|
||||
])
|
||||
.getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("flask.View~Subclass").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("flask.MethodView~Subclass").getASubclass*()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +57,8 @@ module Flask {
|
||||
API::Node subclassRef() {
|
||||
result =
|
||||
API::moduleImport("flask").getMember("views").getMember("MethodView").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("flask.MethodView~Subclass").getASubclass*()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +70,10 @@ module Flask {
|
||||
*/
|
||||
module FlaskApp {
|
||||
/** Gets a reference to the `flask.Flask` class. */
|
||||
API::Node classRef() { result = API::moduleImport("flask").getMember("Flask") }
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("flask").getMember("Flask") or
|
||||
result = ModelOutput::getATypeNode("flask.Flask~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
|
||||
API::Node instance() { result = classRef().getReturn() }
|
||||
@@ -80,6 +90,8 @@ module Flask {
|
||||
result = API::moduleImport("flask").getMember("Blueprint")
|
||||
or
|
||||
result = API::moduleImport("flask").getMember("blueprints").getMember("Blueprint")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("flask.Blueprint~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `flask.Blueprint`. */
|
||||
@@ -87,7 +99,9 @@ module Flask {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `flask.request` object. */
|
||||
API::Node request() { result = API::moduleImport("flask").getMember("request") }
|
||||
API::Node request() {
|
||||
result = API::moduleImport(["flask", "flask_restful"]).getMember("request")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `flask.Response` class
|
||||
@@ -104,6 +118,8 @@ module Flask {
|
||||
result = API::moduleImport("flask").getMember("Response")
|
||||
or
|
||||
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("flask.Response~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,15 +9,18 @@
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `httpx` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/httpx/
|
||||
* - https://www.python-httpx.org/
|
||||
*/
|
||||
private module HttpxModel {
|
||||
module HttpxModel {
|
||||
/**
|
||||
* An outgoing HTTP request, from the `httpx` library.
|
||||
*
|
||||
@@ -59,8 +62,10 @@ private module HttpxModel {
|
||||
*/
|
||||
module Client {
|
||||
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
|
||||
or
|
||||
result = ModelOutput::getATypeNode("httpx.Client~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A method call on a Client that sends off a request */
|
||||
|
||||
@@ -7,12 +7,15 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `invoke` PyPI package.
|
||||
* See https://www.pyinvoke.org/.
|
||||
*/
|
||||
private module Invoke {
|
||||
module Invoke {
|
||||
// ---------------------------------------------------------------------------
|
||||
// invoke
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -30,6 +33,8 @@ private module Invoke {
|
||||
result = API::moduleImport("invoke").getMember("context").getMember("Context")
|
||||
or
|
||||
result = API::moduleImport("invoke").getMember("Context")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("invoke.context.Context~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `invoke.context.Context`. */
|
||||
|
||||
@@ -10,15 +10,25 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides classes modeling security-relevant aspects of the `lxml` PyPI package
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/lxml/
|
||||
* - https://lxml.de/tutorial.html
|
||||
*/
|
||||
private module Lxml {
|
||||
module Lxml {
|
||||
/** Gets a reference to the `lxml.etree` module */
|
||||
API::Node etreeRef() {
|
||||
result = API::moduleImport("lxml").getMember("etree")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("lxml.etree~Alias")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// XPath
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -34,9 +44,7 @@ private module Lxml {
|
||||
* - https://lxml.de/apidoc/lxml.etree.html#lxml.etree.ETXPath
|
||||
*/
|
||||
private class XPathClassCall extends XML::XPathConstruction::Range, DataFlow::CallCfgNode {
|
||||
XPathClassCall() {
|
||||
this = API::moduleImport("lxml").getMember("etree").getMember(["XPath", "ETXPath"]).getACall()
|
||||
}
|
||||
XPathClassCall() { this = etreeRef().getMember(["XPath", "ETXPath"]).getACall() }
|
||||
|
||||
override DataFlow::Node getXPath() { result in [this.getArg(0), this.getArgByName("path")] }
|
||||
|
||||
@@ -62,20 +70,11 @@ private module Lxml {
|
||||
XPathCall() {
|
||||
exists(API::Node parseResult |
|
||||
parseResult =
|
||||
API::moduleImport("lxml")
|
||||
.getMember("etree")
|
||||
.getMember(["parse", "fromstring", "fromstringlist", "HTML", "XML"])
|
||||
.getReturn()
|
||||
etreeRef().getMember(["parse", "fromstring", "fromstringlist", "HTML", "XML"]).getReturn()
|
||||
or
|
||||
// TODO: lxml.etree.parseid(<text>)[0] will contain the root element from parsing <text>
|
||||
// but we don't really have a way to model that nicely.
|
||||
parseResult =
|
||||
API::moduleImport("lxml")
|
||||
.getMember("etree")
|
||||
.getMember("XMLParser")
|
||||
.getReturn()
|
||||
.getMember("close")
|
||||
.getReturn()
|
||||
parseResult = etreeRef().getMember("XMLParser").getReturn().getMember("close").getReturn()
|
||||
|
|
||||
this = parseResult.getMember("xpath").getACall()
|
||||
)
|
||||
@@ -87,14 +86,7 @@ private module Lxml {
|
||||
}
|
||||
|
||||
class XPathEvaluatorCall extends XML::XPathExecution::Range, DataFlow::CallCfgNode {
|
||||
XPathEvaluatorCall() {
|
||||
this =
|
||||
API::moduleImport("lxml")
|
||||
.getMember("etree")
|
||||
.getMember("XPathEvaluator")
|
||||
.getReturn()
|
||||
.getACall()
|
||||
}
|
||||
XPathEvaluatorCall() { this = etreeRef().getMember("XPathEvaluator").getReturn().getACall() }
|
||||
|
||||
override DataFlow::Node getXPath() { result = this.getArg(0) }
|
||||
|
||||
@@ -130,9 +122,7 @@ private module Lxml {
|
||||
* See https://lxml.de/apidoc/lxml.etree.html?highlight=xmlparser#lxml.etree.XMLParser
|
||||
*/
|
||||
private class LxmlParser extends InstanceSource, API::CallNode {
|
||||
LxmlParser() {
|
||||
this = API::moduleImport("lxml").getMember("etree").getMember("XMLParser").getACall()
|
||||
}
|
||||
LxmlParser() { this = etreeRef().getMember("XMLParser").getACall() }
|
||||
|
||||
// NOTE: it's not possible to change settings of a parser after constructing it
|
||||
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) {
|
||||
@@ -162,10 +152,7 @@ private module Lxml {
|
||||
* See https://lxml.de/apidoc/lxml.etree.html?highlight=xmlparser#lxml.etree.get_default_parser
|
||||
*/
|
||||
private class LxmlDefaultParser extends InstanceSource, DataFlow::CallCfgNode {
|
||||
LxmlDefaultParser() {
|
||||
this =
|
||||
API::moduleImport("lxml").getMember("etree").getMember("get_default_parser").getACall()
|
||||
}
|
||||
LxmlDefaultParser() { this = etreeRef().getMember("get_default_parser").getACall() }
|
||||
|
||||
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) {
|
||||
// as highlighted by
|
||||
@@ -240,7 +227,7 @@ private module Lxml {
|
||||
|
||||
LxmlParsing() {
|
||||
functionName in ["fromstring", "fromstringlist", "XML", "XMLID", "parse", "parseid"] and
|
||||
this = API::moduleImport("lxml").getMember("etree").getMember(functionName).getACall()
|
||||
this = etreeRef().getMember(functionName).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
@@ -309,9 +296,7 @@ private module Lxml {
|
||||
private class LxmlIterparseCall extends API::CallNode, XML::XmlParsing::Range,
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
LxmlIterparseCall() {
|
||||
this = API::moduleImport("lxml").getMember("etree").getMember("iterparse").getACall()
|
||||
}
|
||||
LxmlIterparseCall() { this = etreeRef().getMember("iterparse").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("source")] }
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `MarkupSafe` PyPI package.
|
||||
* See https://markupsafe.palletsprojects.com/en/2.0.x/.
|
||||
*/
|
||||
private module MarkupSafeModel {
|
||||
module MarkupSafeModel {
|
||||
/**
|
||||
* Provides models for the `markupsafe.Markup` class
|
||||
*
|
||||
@@ -26,6 +29,8 @@ private module MarkupSafeModel {
|
||||
result = API::moduleImport("markupsafe").getMember("Markup")
|
||||
or
|
||||
result = API::moduleImport("flask").getMember("Markup")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("markupsafe.Markup~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -28,6 +29,8 @@ module Multidict {
|
||||
/** Gets a reference to a `MultiDictProxy` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("multidict").getMember(["MultiDictProxy", "CIMultiDictProxy"])
|
||||
or
|
||||
result = ModelOutput::getATypeNode("multidict.MultiDictProxy~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,14 +11,17 @@ private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.PEP249
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `peewee` PyPI package.
|
||||
* See
|
||||
* - https://pypi.org/project/peewee/
|
||||
* - https://docs.peewee-orm.com/en/latest/index.html
|
||||
*/
|
||||
private module Peewee {
|
||||
module Peewee {
|
||||
/** Provides models for the `peewee.Database` class and subclasses. */
|
||||
module Database {
|
||||
/** Gets a reference to the `peewee.Database` class or any subclass. */
|
||||
@@ -31,7 +34,7 @@ private module Peewee {
|
||||
.getMember(["SqliteDatabase", "MySQLDatabase", "PostgresqlDatabase"])
|
||||
.getASubclass*()
|
||||
or
|
||||
// Ohter known subclasses, semi auto generated by using
|
||||
// Other known subclasses, semi auto generated by using
|
||||
// ```ql
|
||||
// class DBClass extends Class, SelfRefMixin {
|
||||
// DBClass() {
|
||||
@@ -153,6 +156,8 @@ private module Peewee {
|
||||
.getMember("sqliteq")
|
||||
.getMember("SqliteQueueDatabase")
|
||||
.getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("peewee.Database~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `peewee.Database` or any subclass. */
|
||||
|
||||
@@ -9,15 +9,18 @@
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `pycurl` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/pycurl/
|
||||
* - https://pycurl.io/docs/latest/
|
||||
*/
|
||||
private module Pycurl {
|
||||
module Pycurl {
|
||||
/**
|
||||
* Provides models for the `pycurl.Curl` class
|
||||
*
|
||||
@@ -25,7 +28,11 @@ private module Pycurl {
|
||||
*/
|
||||
module Curl {
|
||||
/** Gets a reference to the `pycurl.Curl` class. */
|
||||
private API::Node classRef() { result = API::moduleImport("pycurl").getMember("Curl") }
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("pycurl").getMember("Curl")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("pycurl.Curl~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `pycurl.Curl`. */
|
||||
private API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
@@ -11,6 +11,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -31,6 +32,8 @@ module Pydantic {
|
||||
/** Gets a reference to a `pydantic.BaseModel` subclass (a pydantic model). */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("pydantic").getMember("BaseModel").getASubclass+()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("pydantic.BaseModel~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -22,7 +23,7 @@ private import semmle.python.frameworks.Stdlib
|
||||
* - https://pypi.org/project/requests/
|
||||
* - https://requests.readthedocs.io/en/latest/
|
||||
*/
|
||||
private module Requests {
|
||||
module Requests {
|
||||
/**
|
||||
* An outgoing HTTP request, from the `requests` library.
|
||||
*
|
||||
@@ -91,10 +92,12 @@ private module Requests {
|
||||
*/
|
||||
module Response {
|
||||
/** Gets a reference to the `requests.models.Response` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("requests").getMember("models").getMember("Response")
|
||||
or
|
||||
result = API::moduleImport("requests").getMember("Response")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("requests.models.Response~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.Django
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -27,7 +28,7 @@ private import semmle.python.frameworks.Stdlib
|
||||
* - https://www.django-rest-framework.org/
|
||||
* - https://pypi.org/project/djangorestframework/
|
||||
*/
|
||||
private module RestFramework {
|
||||
module RestFramework {
|
||||
// ---------------------------------------------------------------------------
|
||||
// rest_framework.views.APIView handling
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -215,8 +216,10 @@ private module RestFramework {
|
||||
*/
|
||||
module Request {
|
||||
/** Gets a reference to the `rest_framework.request.Request` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("rest_framework").getMember("request").getMember("Request")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("rest_framework.request.Request~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,8 +302,11 @@ private module RestFramework {
|
||||
*/
|
||||
module Response {
|
||||
/** Gets a reference to the `rest_framework.response.Response` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("rest_framework").getMember("response").getMember("Response")
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("rest_framework.response.Response~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A direct instantiation of `rest_framework.response.Response`. */
|
||||
@@ -328,6 +334,23 @@ private module RestFramework {
|
||||
* See https://www.django-rest-framework.org/api-guide/exceptions/#api-reference
|
||||
*/
|
||||
module ApiException {
|
||||
API::Node classRef() {
|
||||
exists(string className |
|
||||
className in [
|
||||
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
|
||||
"NotAuthenticated", "PermissionDenied", "NotFound", "NotAcceptable"
|
||||
] and
|
||||
result =
|
||||
API::moduleImport("rest_framework")
|
||||
.getMember("exceptions")
|
||||
.getMember(className)
|
||||
.getASubclass*()
|
||||
)
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("rest_framework.exceptions.APIException~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A direct instantiation of `rest_framework.exceptions.ApiException` or subclass. */
|
||||
private class ClassInstantiation extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode
|
||||
@@ -345,6 +368,8 @@ private module RestFramework {
|
||||
.getMember("exceptions")
|
||||
.getMember(className)
|
||||
.getACall()
|
||||
or
|
||||
this = classRef().getACall() and className = "APIException"
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
|
||||
@@ -13,6 +13,7 @@ private import semmle.python.Concepts
|
||||
// This import is done like this to avoid importing the deprecated top-level things that
|
||||
// would pollute the namespace
|
||||
private import semmle.python.frameworks.PEP249::PEP249 as PEP249
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -34,10 +35,12 @@ module SqlAlchemy {
|
||||
*/
|
||||
module Engine {
|
||||
/** Gets a reference to a SQLAlchemy Engine class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("sqlalchemy").getMember("engine").getMember("Engine")
|
||||
or
|
||||
result = API::moduleImport("sqlalchemy").getMember("future").getMember("Engine")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("sqlalchemy.engine.Engine~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +90,7 @@ module SqlAlchemy {
|
||||
*/
|
||||
module Connection {
|
||||
/** Gets a reference to a SQLAlchemy Connection class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result =
|
||||
API::moduleImport("sqlalchemy")
|
||||
.getMember("engine")
|
||||
@@ -95,6 +98,8 @@ module SqlAlchemy {
|
||||
.getMember("Connection")
|
||||
or
|
||||
result = API::moduleImport("sqlalchemy").getMember("future").getMember("Connection")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("sqlalchemy.engine.Connection~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,8 +183,10 @@ module SqlAlchemy {
|
||||
*/
|
||||
module Session {
|
||||
/** Gets a reference to the `sqlalchemy.orm.Session` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("sqlalchemy").getMember("orm").getMember("Session")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("sqlalchemy.orm.Session~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -35,6 +36,8 @@ module Starlette {
|
||||
result = API::moduleImport("starlette").getMember("websockets").getMember("WebSocket")
|
||||
or
|
||||
result = API::moduleImport("fastapi").getMember("WebSocket")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("starlette.websockets.WebSocket~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,8 +103,10 @@ module Starlette {
|
||||
*/
|
||||
module Url {
|
||||
/** Gets a reference to the `starlette.requests.URL` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("requests").getMember("URL")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("starlette.requests.URL~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
// modeling split over multiple files to keep this file from becoming too big
|
||||
private import semmle.python.frameworks.Stdlib.Urllib
|
||||
private import semmle.python.frameworks.Stdlib.Urllib2
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/** Provides models for the Python standard library. */
|
||||
module Stdlib {
|
||||
@@ -181,8 +182,10 @@ module Stdlib {
|
||||
*/
|
||||
module SplitResult {
|
||||
/** Gets a reference to the `urllib.parse.SplitResult` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("urllib").getMember("parse").getMember("SplitResult")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("urllib.parse.SplitResult~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,8 +255,10 @@ module Stdlib {
|
||||
*/
|
||||
module Logger {
|
||||
/** Gets a reference to the `logging.Logger` class or any subclass. */
|
||||
private API::Node subclassRef() {
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("logging").getMember("Logger").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("logging.Logger~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,13 +297,15 @@ module Stdlib {
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the Python standard library.
|
||||
*
|
||||
* This module is marked private as exposing it means committing to 1-year deprecation
|
||||
* policy, and the code is not in a polished enough state that we want to do so -- at
|
||||
* least not without having convincing use-cases for it :)
|
||||
*/
|
||||
private module StdlibPrivate {
|
||||
module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// os
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1293,14 +1300,36 @@ private module StdlibPrivate {
|
||||
// pickle
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to any of the `pickle` modules. */
|
||||
API::Node pickle() { result = API::moduleImport(["pickle", "cPickle", "_pickle"]) }
|
||||
API::Node pickle() {
|
||||
result = API::moduleImport(["pickle", "cPickle", "_pickle"])
|
||||
or
|
||||
result = ModelOutput::getATypeNode("pickle~Alias")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to `pickle.load`
|
||||
*/
|
||||
API::Node pickle_load() {
|
||||
result = pickle().getMember("load")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("pickle.load~Alias")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to `pickle.loads`
|
||||
*/
|
||||
API::Node pickle_loads() {
|
||||
result = pickle().getMember("loads")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("pickle.loads~Alias")
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `pickle.load`
|
||||
* See https://docs.python.org/3/library/pickle.html#pickle.load
|
||||
*/
|
||||
private class PickleLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
|
||||
PickleLoadCall() { this = pickle().getMember("load").getACall() }
|
||||
private class PickleLoadCall extends Decoding::Range, API::CallNode {
|
||||
PickleLoadCall() { this = pickle_load().getACall() }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
|
||||
@@ -1315,8 +1344,8 @@ private module StdlibPrivate {
|
||||
* A call to `pickle.loads`
|
||||
* See https://docs.python.org/3/library/pickle.html#pickle.loads
|
||||
*/
|
||||
private class PickleLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
|
||||
PickleLoadsCall() { this = pickle().getMember("loads").getACall() }
|
||||
private class PickleLoadsCall extends Decoding::Range, API::CallNode {
|
||||
PickleLoadsCall() { this = pickle_loads().getACall() }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
|
||||
@@ -1729,8 +1758,21 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/cgi.html.
|
||||
*/
|
||||
module FieldStorage {
|
||||
/** Gets a reference to the `cgi.FieldStorage` class. */
|
||||
API::Node classRef() { result = cgi().getMember("FieldStorage") }
|
||||
/**
|
||||
* DEPRECATED: Use `subclassRef` predicate instead.
|
||||
*
|
||||
* Gets a reference to the `cgi.FieldStorage` class.
|
||||
*/
|
||||
deprecated API::Node classRef() {
|
||||
result = API::moduleImport("cgi").getMember("FieldStorage")
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cgi.FieldStorage` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("cgi").getMember("FieldStorage").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("cgi.FieldStorage~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `cgi.FieldStorage`, extend this class to model new instances.
|
||||
@@ -1753,13 +1795,13 @@ private module StdlibPrivate {
|
||||
private class ClassInstantiation extends InstanceSource, RemoteFlowSource::Range,
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
ClassInstantiation() { this = subclassRef().getACall() }
|
||||
|
||||
override string getSourceType() { result = "cgi.FieldStorage" }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `cgi.FieldStorage`. */
|
||||
API::Node instance() { result = classRef().getReturn() }
|
||||
API::Node instance() { result = subclassRef().getReturn() }
|
||||
|
||||
/** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
|
||||
API::Node getvalueRef() { result = instance().getMember("getvalue") }
|
||||
@@ -2013,7 +2055,7 @@ private module StdlibPrivate {
|
||||
* - https://docs.python.org/3.9/library/http.server.html#http.server.BaseHTTPRequestHandler
|
||||
* - https://docs.python.org/2.7/library/basehttpserver.html#BaseHTTPServer.BaseHTTPRequestHandler
|
||||
*/
|
||||
private module HttpRequestHandler {
|
||||
module BaseHttpRequestHandler {
|
||||
/** Gets a reference to the `BaseHttpRequestHandler` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result =
|
||||
@@ -2027,6 +2069,9 @@ private module StdlibPrivate {
|
||||
API::moduleImport("http").getMember("server").getMember("SimpleHTTPRequestHandler"),
|
||||
API::moduleImport("http").getMember("server").getMember("CGIHTTPRequestHandler"),
|
||||
].getASubclass*()
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("http.server.BaseHTTPRequestHandler~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A HttpRequestHandler class definition (most likely in project code). */
|
||||
@@ -2121,17 +2166,20 @@ private module StdlibPrivate {
|
||||
// wsgiref.simple_server
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Provides models for the `wsgiref.simple_server` module. */
|
||||
private module WsgirefSimpleServer {
|
||||
module WsgirefSimpleServer {
|
||||
API::Node subclassRef() {
|
||||
result =
|
||||
API::moduleImport("wsgiref")
|
||||
.getMember("simple_server")
|
||||
.getMember("WSGIServer")
|
||||
.getASubclass*()
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("wsgiref.simple_server.WSGIServer~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
class WsgiServerSubclass extends Class, SelfRefMixin {
|
||||
WsgiServerSubclass() {
|
||||
this.getParent() =
|
||||
API::moduleImport("wsgiref")
|
||||
.getMember("simple_server")
|
||||
.getMember("WSGIServer")
|
||||
.getASubclass*()
|
||||
.asSource()
|
||||
.asExpr()
|
||||
}
|
||||
WsgiServerSubclass() { this.getParent() = subclassRef().asSource().asExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2148,13 +2196,7 @@ private module StdlibPrivate {
|
||||
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
|
||||
(
|
||||
setAppCall =
|
||||
API::moduleImport("wsgiref")
|
||||
.getMember("simple_server")
|
||||
.getMember("WSGIServer")
|
||||
.getASubclass*()
|
||||
.getReturn()
|
||||
.getMember("set_app")
|
||||
.getACall()
|
||||
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall()
|
||||
or
|
||||
setAppCall
|
||||
.(DataFlow::MethodCallNode)
|
||||
@@ -2292,7 +2334,7 @@ private module StdlibPrivate {
|
||||
*/
|
||||
module HttpConnection {
|
||||
/** Gets a reference to the `http.client.HttpConnection` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
exists(string className | className in ["HTTPConnection", "HTTPSConnection"] |
|
||||
// Python 3
|
||||
result = API::moduleImport("http").getMember("client").getMember(className)
|
||||
@@ -2303,6 +2345,8 @@ private module StdlibPrivate {
|
||||
result =
|
||||
API::moduleImport("six").getMember("moves").getMember("http_client").getMember(className)
|
||||
)
|
||||
or
|
||||
result = ModelOutput::getATypeNode("http.client.HTTPConnection~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2414,8 +2458,10 @@ private module StdlibPrivate {
|
||||
*/
|
||||
module HttpResponse {
|
||||
/** Gets a reference to the `http.client.HttpResponse` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("http").getMember("client").getMember("HTTPResponse")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("http.client.HTTPResponse~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3534,8 +3580,10 @@ private module StdlibPrivate {
|
||||
*/
|
||||
module StringIO {
|
||||
/** Gets a reference to the `io.StringIO` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("io").getMember(["StringIO", "BytesIO"])
|
||||
or
|
||||
result = ModelOutput::getATypeNode("io.StringIO~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3585,6 +3633,12 @@ private module StdlibPrivate {
|
||||
// ---------------------------------------------------------------------------
|
||||
// xml.etree.ElementTree
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `xml.etree.ElementTree` class */
|
||||
API::Node elementTreeClassRef() {
|
||||
result = API::moduleImport("xml").getMember("etree").getMember("ElementTree").getASubclass*() or
|
||||
result = ModelOutput::getATypeNode("xml.etree.ElementTree~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of `xml.etree.ElementTree.ElementTree`.
|
||||
*
|
||||
@@ -3592,20 +3646,10 @@ private module StdlibPrivate {
|
||||
*/
|
||||
private API::Node elementTreeInstance() {
|
||||
//parse to a tree
|
||||
result =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember("parse")
|
||||
.getReturn()
|
||||
result = elementTreeClassRef().getMember("parse").getReturn()
|
||||
or
|
||||
// construct a tree without parsing
|
||||
result =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember("ElementTree")
|
||||
.getReturn()
|
||||
result = elementTreeClassRef().getMember("ElementTree").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3618,21 +3662,9 @@ private module StdlibPrivate {
|
||||
result = elementTreeInstance().getMember(["parse", "getroot"]).getReturn()
|
||||
or
|
||||
// parse directly to an element
|
||||
result =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember(["fromstring", "fromstringlist", "XML"])
|
||||
.getReturn()
|
||||
result = elementTreeClassRef().getMember(["fromstring", "fromstringlist", "XML"]).getReturn()
|
||||
or
|
||||
result =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember("XMLParser")
|
||||
.getReturn()
|
||||
.getMember("close")
|
||||
.getReturn()
|
||||
result = elementTreeClassRef().getMember("XMLParser").getReturn().getMember("close").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3677,12 +3709,7 @@ private module StdlibPrivate {
|
||||
/** A direct instantiation of `xml.etree` parsers. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() {
|
||||
this =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember(["XMLParser", "XMLPullParser"])
|
||||
.getACall()
|
||||
this = elementTreeClassRef().getMember(["XMLParser", "XMLPullParser"]).getACall()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3739,9 +3766,7 @@ private module StdlibPrivate {
|
||||
private class XmlEtreeParsing extends DataFlow::CallCfgNode, XML::XmlParsing::Range {
|
||||
XmlEtreeParsing() {
|
||||
this =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
elementTreeClassRef()
|
||||
.getMember(["fromstring", "fromstringlist", "XML", "XMLID", "parse", "iterparse"])
|
||||
.getACall()
|
||||
or
|
||||
@@ -3789,12 +3814,7 @@ private module StdlibPrivate {
|
||||
*/
|
||||
private class FileAccessFromXmlEtreeParsing extends XmlEtreeParsing, FileSystemAccess::Range {
|
||||
FileAccessFromXmlEtreeParsing() {
|
||||
this =
|
||||
API::moduleImport("xml")
|
||||
.getMember("etree")
|
||||
.getMember("ElementTree")
|
||||
.getMember(["parse", "iterparse"])
|
||||
.getACall()
|
||||
this = elementTreeClassRef().getMember(["parse", "iterparse"]).getACall()
|
||||
or
|
||||
this = elementTreeInstance().getMember("parse").getACall()
|
||||
// I considered whether we should try to reduce FPs from people passing file-like
|
||||
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
|
||||
private import semmle.python.regex
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -87,7 +88,11 @@ module Tornado {
|
||||
*/
|
||||
module RequestHandler {
|
||||
/** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */
|
||||
API::Node subclassRef() { result = web().getMember("RequestHandler").getASubclass*() }
|
||||
API::Node subclassRef() {
|
||||
result = web().getMember("RequestHandler").getASubclass*()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A RequestHandler class (most likely in project code). */
|
||||
class RequestHandlerClass extends Class {
|
||||
@@ -213,7 +218,11 @@ module Tornado {
|
||||
*/
|
||||
module Application {
|
||||
/** Gets a reference to the `tornado.web.Application` class. */
|
||||
API::Node classRef() { result = web().getMember("Application") }
|
||||
API::Node classRef() {
|
||||
result = web().getMember("Application")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("tornado.web.Application~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `tornado.web.Application`, extend this class to model new instances.
|
||||
@@ -270,7 +279,12 @@ module Tornado {
|
||||
*/
|
||||
module HttpServerRequest {
|
||||
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
|
||||
API::Node classRef() { result = httputil().getMember("HttpServerRequest") }
|
||||
API::Node classRef() {
|
||||
result = httputil().getMember("HttpServerRequest")
|
||||
or
|
||||
result =
|
||||
ModelOutput::getATypeNode("tornado.httputil.HttpServerRequest~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances.
|
||||
|
||||
@@ -9,15 +9,18 @@
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `urllib3` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/urllib3/
|
||||
* - https://urllib3.readthedocs.io/en/stable/reference/
|
||||
*/
|
||||
private module Urllib3 {
|
||||
module Urllib3 {
|
||||
/**
|
||||
* Provides models for the `urllib3.request.RequestMethods` class and subclasses, such
|
||||
* as the `urllib3.PoolManager` class
|
||||
@@ -30,7 +33,7 @@ private module Urllib3 {
|
||||
*/
|
||||
module PoolManager {
|
||||
/** Gets a reference to the `urllib3.PoolManager` class. */
|
||||
private API::Node classRef() {
|
||||
API::Node classRef() {
|
||||
result =
|
||||
API::moduleImport("urllib3")
|
||||
.getMember(["PoolManager", "ProxyManager", "HTTPConnectionPool", "HTTPSConnectionPool"])
|
||||
@@ -40,6 +43,8 @@ private module Urllib3 {
|
||||
.getMember("request")
|
||||
.getMember("RequestMethods")
|
||||
.getASubclass+()
|
||||
or
|
||||
result = ModelOutput::getATypeNode("urllib3.PoolManager~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
174831
python/ql/lib/semmle/python/frameworks/data/internal/subclass-capture/ALL.model.yml
generated
Normal file
174831
python/ql/lib/semmle/python/frameworks/data/internal/subclass-capture/ALL.model.yml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
@@ -16,13 +17,16 @@ abstract class SelfRefMixin extends Class {
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode getASelfRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
|
||||
exists(Class cls, Function meth |
|
||||
cls = getADirectSuperclass*(this) and
|
||||
meth = cls.getAMethod() and
|
||||
not isStaticmethod(meth) and
|
||||
not isClassmethod(meth) and
|
||||
result.(DataFlow::ParameterNode).getParameter() = meth.getArg(0)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
|
||||
}
|
||||
@@ -30,9 +34,6 @@ abstract class SelfRefMixin extends Class {
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
DataFlow::Node getASelfRef() { this.getASelfRef(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
@@ -7,32 +7,23 @@
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.filters.Tests
|
||||
private import semmle.python.Module
|
||||
|
||||
// very much inspired by the draft at https://github.com/github/codeql/pull/5632
|
||||
private module NotExposed {
|
||||
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, _, _, _)
|
||||
newModel(any(FindSubclassesSpec spec), newModelFullyQualified, _, _, _)
|
||||
|
|
||||
fullyQualifiedToApiGraphPath(newModelFullyQualified), " or this = API::"
|
||||
)
|
||||
@@ -75,14 +66,31 @@ private module NotExposed {
|
||||
|
||||
bindingset[this]
|
||||
abstract class FindSubclassesSpec extends string {
|
||||
/**
|
||||
* Gets an API node for a class that has already been modeled. You can include
|
||||
* `.getASubclass*()` without causing problems, but it is not needed.
|
||||
*/
|
||||
abstract API::Node getAlreadyModeledClass();
|
||||
|
||||
/**
|
||||
* Gets the fully qualified name that this spec represents.
|
||||
*
|
||||
* This should be implemented by all classes for which `getSuperClass` is
|
||||
* implemented, at least if they are defined in a different module than what they
|
||||
* subclass.
|
||||
*/
|
||||
string getFullyQualifiedName() { none() }
|
||||
|
||||
FindSubclassesSpec getSuperClass() { none() }
|
||||
|
||||
final FindSubclassesSpec getSubClass() { result.getSuperClass() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
predicate newModel(
|
||||
FindSubclassesSpec spec, string newModelFullyQualified, AstNode ast, Module mod, Location loc
|
||||
) {
|
||||
(
|
||||
@@ -90,7 +98,8 @@ private module NotExposed {
|
||||
or
|
||||
newDirectAlias(spec, newModelFullyQualified, ast, mod, loc)
|
||||
or
|
||||
newImportStar(spec, newModelFullyQualified, ast, mod, _, _, loc)
|
||||
newImportAlias(spec, newModelFullyQualified, mod, _, _, loc) and
|
||||
ast = mod
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,15 +112,34 @@ private module NotExposed {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fullyQualifiedName` is already explicitly modeled in the `spec`.
|
||||
*
|
||||
* For specs that do `.getASubclass*()`, items found by following a `.getASubclass`
|
||||
* edge will not be considered explicitly modeled.
|
||||
*/
|
||||
bindingset[fullyQualifiedName]
|
||||
predicate alreadyModeled(FindSubclassesSpec spec, string fullyQualifiedName) {
|
||||
predicate alreadyExplicitlyModeled(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 isAllowedModule(Module mod) {
|
||||
// for tests
|
||||
mod.getName() = "find_subclass_test"
|
||||
or
|
||||
// don't include anything found in site-packages
|
||||
exists(mod.getFile().getRelativePath()) and
|
||||
not mod.getFile().getRelativePath().regexpMatch("(?i)((^|/)examples?|^docs)/.*") and
|
||||
// to counter things like `my-example/app/foo.py` being allowed under `app.foo`
|
||||
forall(string part | part = mod.getFile().getParent().getRelativePath().splitAt("/") |
|
||||
legalShortName(part)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isTestCode(AstNode ast) {
|
||||
ast.getScope*() instanceof TestScope
|
||||
or
|
||||
ast.getLocation().getFile().getRelativePath().matches("tests/%")
|
||||
}
|
||||
|
||||
predicate hasAllStatement(Module mod) {
|
||||
@@ -144,40 +172,47 @@ private module NotExposed {
|
||||
* ```
|
||||
*/
|
||||
predicate newDirectAlias(
|
||||
FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod,
|
||||
Location loc
|
||||
FindSubclassesSpec spec, string newAliasFullyQualified, Expr value, Module mod, Location loc
|
||||
) {
|
||||
importMember = newOrExistingModeling(spec).getAValueReachableFromSource().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)
|
||||
exists(Alias alias | value = alias.getValue() |
|
||||
value = newOrExistingModeling(spec).getASubclass*().getAValueReachableFromSource().asExpr() and
|
||||
value.getScope() = mod and
|
||||
loc = value.getLocation() and
|
||||
exists(string base |
|
||||
mod.isPackageInit() and base = mod.getPackageName()
|
||||
or
|
||||
not mod.isPackageInit() and base = mod.getName()
|
||||
|
|
||||
newAliasFullyQualified = base + "." + alias.getAsname().(Name).getId()
|
||||
) and
|
||||
(
|
||||
not hasAllStatement(mod)
|
||||
or
|
||||
mod.declaredInAll(alias.getAsname().(Name).getId())
|
||||
) and
|
||||
not alreadyExplicitlyModeled(spec, newAliasFullyQualified) and
|
||||
not isTestCode(value) and
|
||||
isAllowedModule(mod)
|
||||
)
|
||||
}
|
||||
|
||||
/** 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
|
||||
/**
|
||||
* same as `newDirectAlias` predicate, but written in a generic way to handle any import (also import *).
|
||||
*
|
||||
* it might be safe to delete `newDirectAlias` with this in place, but have not done the testing yet.
|
||||
*/
|
||||
predicate newImportAlias(
|
||||
FindSubclassesSpec spec, string newAliasFullyQualified, Module mod, DataFlow::Node def,
|
||||
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().getAValueReachableFromSource().asExpr() = importStar.getModule() and
|
||||
loc = mod.getLocation() and
|
||||
exists(API::Node relevantClass, ControlFlowNode value |
|
||||
relevantClass = newOrExistingModeling(spec).getASubclass*() and
|
||||
ImportResolution::module_export(mod, relevantName, def) and
|
||||
value = relevantClass.getAValueReachableFromSource().asCfgNode() and
|
||||
value = def.asCfgNode()
|
||||
// value could be a ClassExpr if a new class is defined, or a Name if defining an alias
|
||||
) and
|
||||
(
|
||||
mod.isPackageInit() and
|
||||
newAliasFullyQualified = mod.getPackageName() + "." + relevantName
|
||||
@@ -190,8 +225,9 @@ private module NotExposed {
|
||||
or
|
||||
mod.declaredInAll(relevantName)
|
||||
) and
|
||||
not alreadyModeled(spec, newAliasFullyQualified) and
|
||||
isNonTestProjectCode(importStar)
|
||||
not alreadyExplicitlyModeled(spec, newAliasFullyQualified) and
|
||||
not isTestCode(mod) and
|
||||
isAllowedModule(mod)
|
||||
}
|
||||
|
||||
/** Holds if `classExpr` defines a new subclass that belongs to `spec`, which has the fully qualified name `newSubclassQualified`. */
|
||||
@@ -203,7 +239,8 @@ private module NotExposed {
|
||||
classExpr.getScope() = mod and
|
||||
newSubclassQualified = mod.getName() + "." + classExpr.getName() and
|
||||
loc = classExpr.getLocation() and
|
||||
not alreadyModeled(spec, newSubclassQualified) and
|
||||
isNonTestProjectCode(classExpr)
|
||||
not alreadyExplicitlyModeled(spec, newSubclassQualified) and
|
||||
not isTestCode(classExpr) and
|
||||
isAllowedModule(mod)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user