Merge pull request #21869 from yoff/python/support-flask-subclasses

Python: Support Flask subclasses
This commit is contained in:
yoff
2026-06-25 23:42:21 +02:00
committed by GitHub
13 changed files with 74 additions and 12 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `Flask::FlaskApp::instance()` will now also return instances of subclasses defined in the source tree. Previously, these were filtered out. `Flask::FlaskApp::classRef()` has been deprecated in favor of `Flask::FlaskApp::subclassRef()` since it already returned some subclasses.

View File

@@ -71,14 +71,21 @@ module Flask {
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
*/
module FlaskApp {
/** Gets a reference to the `flask.Flask` class. */
API::Node classRef() {
result = API::moduleImport("flask").getMember("Flask") or
/**
* Gets a reference to the `flask.Flask` class or any subclass.
*
* Deprecated: Use `subclassRef()` instead, this predicate always returned some subclasses.
*/
deprecated API::Node classRef() { result = subclassRef() }
/** Gets a reference to the `flask.Flask` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("flask").getMember("Flask").getASubclass*() 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() }
API::Node instance() { result = subclassRef().getReturn() }
}
/**
@@ -132,7 +139,7 @@ module Flask {
API::Node classRef() {
result = API::moduleImport("flask").getMember("Response")
or
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
result = [FlaskApp::subclassRef(), FlaskApp::instance()].getMember("response_class")
or
result = ModelOutput::getATypeNode("flask.Response~Subclass").getASubclass*()
}

View File

@@ -351,7 +351,7 @@ class DjangoHttpRequest extends FindSubclassesSpec {
class FlaskClass extends FindSubclassesSpec {
FlaskClass() { this = "flask.Flask~Subclass" }
override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::classRef() }
override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::subclassRef() }
}
class FlaskBlueprint extends FindSubclassesSpec {

View File

@@ -0,0 +1,29 @@
/**
* Defines an InlineExpectationsTest for class instances, that is,
* for any API::Node that is an instance of a class (e.g. `Flask`).
*/
import python
import semmle.python.ApiGraphs
import utils.test.InlineExpectationsTest
private import semmle.python.dataflow.new.internal.PrintNode
signature API::Node getInstanceSig();
module MakeInlineInstanceTest<getInstanceSig/0 getInstance> {
private module InlineInstanceTest implements TestSig {
string getARelevantTag() { result = "instance" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(API::Node instance | instance = getInstance() |
location = instance.getLocation() and
element = prettyNode(instance.asSource()) and
value = "" and
tag = "instance"
)
}
}
import MakeTest<InlineInstanceTest>
}

View File

@@ -0,0 +1,8 @@
import python
import semmle.python.frameworks.Flask
import semmle.python.ApiGraphs
import experimental.meta.InlineInstanceTest
API::Node getInstance() { result = Flask::FlaskApp::instance() }
import MakeInlineInstanceTest<getInstance/0>

View File

@@ -0,0 +1,14 @@
from flask import Flask
class Sub(Flask):
def __init__(self, *args, **kwargs):
Flask.__init__(self, *args, **kwargs)
app = Sub(__name__) # $ instance
@app.route("/") # $ routeSetup="/"
def hello(): # $ requestHandler
return "world" # $ HttpResponse

View File

@@ -1,7 +1,7 @@
import flask
from flask import Flask, request, make_response
app = Flask(__name__)
app = Flask(__name__) # $ instance
@app.route("/") # $ routeSetup="/"
def hello_world(): # $ requestHandler

View File

@@ -3,7 +3,7 @@ import json
from flask import Flask, make_response, jsonify, Response, request, redirect
from werkzeug.datastructures import Headers
app = Flask(__name__)
app = Flask(__name__) # $ instance
@app.route("/html1") # $ routeSetup="/html1"

View File

@@ -1,7 +1,7 @@
import flask
from flask import Flask, make_response
app = Flask(__name__)
app = Flask(__name__) # $ instance
SOME_ROUTE = "/some/route"

View File

@@ -1,5 +1,5 @@
from flask import Flask, request
app = Flask(__name__)
app = Flask(__name__) # $ instance
@app.route("/save-uploaded-file") # $ routeSetup="/save-uploaded-file"
def test_taint(): # $ requestHandler

View File

@@ -1,5 +1,5 @@
from flask import Flask, request, render_template_string, stream_template_string
app = Flask(__name__)
app = Flask(__name__) # $ instance
@app.route("/test_taint/<name>/<int:number>") # $ routeSetup="/test_taint/<name>/<int:number>"
def test_taint(name = "World!", number="0", foo="foo"): # $ requestHandler routedParameter=name routedParameter=number

View File

@@ -1,5 +1,5 @@
from flask import Flask, Response, stream_with_context, render_template_string, stream_template_string
app = Flask(__name__)
app = Flask(__name__) # $ instance
@app.route("/a") # $ routeSetup="/a"
def a(): # $ requestHandler