Skip to content

Commit 5fcaac7

Browse files
authored
Merge pull request #21869 from yoff/python/support-flask-subclasses
Python: Support Flask subclasses
2 parents 336df3c + f7c4e61 commit 5fcaac7

13 files changed

Lines changed: 74 additions & 12 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* `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.

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,21 @@ module Flask {
7171
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
7272
*/
7373
module FlaskApp {
74-
/** Gets a reference to the `flask.Flask` class. */
75-
API::Node classRef() {
76-
result = API::moduleImport("flask").getMember("Flask") or
74+
/**
75+
* Gets a reference to the `flask.Flask` class or any subclass.
76+
*
77+
* Deprecated: Use `subclassRef()` instead, this predicate always returned some subclasses.
78+
*/
79+
deprecated API::Node classRef() { result = subclassRef() }
80+
81+
/** Gets a reference to the `flask.Flask` class or any subclass. */
82+
API::Node subclassRef() {
83+
result = API::moduleImport("flask").getMember("Flask").getASubclass*() or
7784
result = ModelOutput::getATypeNode("flask.Flask~Subclass").getASubclass*()
7885
}
7986

8087
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
81-
API::Node instance() { result = classRef().getReturn() }
88+
API::Node instance() { result = subclassRef().getReturn() }
8289
}
8390

8491
/**
@@ -132,7 +139,7 @@ module Flask {
132139
API::Node classRef() {
133140
result = API::moduleImport("flask").getMember("Response")
134141
or
135-
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
142+
result = [FlaskApp::subclassRef(), FlaskApp::instance()].getMember("response_class")
136143
or
137144
result = ModelOutput::getATypeNode("flask.Response~Subclass").getASubclass*()
138145
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ class DjangoHttpRequest extends FindSubclassesSpec {
351351
class FlaskClass extends FindSubclassesSpec {
352352
FlaskClass() { this = "flask.Flask~Subclass" }
353353

354-
override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::classRef() }
354+
override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::subclassRef() }
355355
}
356356

357357
class FlaskBlueprint extends FindSubclassesSpec {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Defines an InlineExpectationsTest for class instances, that is,
3+
* for any API::Node that is an instance of a class (e.g. `Flask`).
4+
*/
5+
6+
import python
7+
import semmle.python.ApiGraphs
8+
import utils.test.InlineExpectationsTest
9+
private import semmle.python.dataflow.new.internal.PrintNode
10+
11+
signature API::Node getInstanceSig();
12+
13+
module MakeInlineInstanceTest<getInstanceSig/0 getInstance> {
14+
private module InlineInstanceTest implements TestSig {
15+
string getARelevantTag() { result = "instance" }
16+
17+
predicate hasActualResult(Location location, string element, string tag, string value) {
18+
exists(location.getFile().getRelativePath()) and
19+
exists(API::Node instance | instance = getInstance() |
20+
location = instance.getLocation() and
21+
element = prettyNode(instance.asSource()) and
22+
value = "" and
23+
tag = "instance"
24+
)
25+
}
26+
}
27+
28+
import MakeTest<InlineInstanceTest>
29+
}

python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.expected

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import python
2+
import semmle.python.frameworks.Flask
3+
import semmle.python.ApiGraphs
4+
import experimental.meta.InlineInstanceTest
5+
6+
API::Node getInstance() { result = Flask::FlaskApp::instance() }
7+
8+
import MakeInlineInstanceTest<getInstance/0>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from flask import Flask
2+
3+
4+
class Sub(Flask):
5+
def __init__(self, *args, **kwargs):
6+
Flask.__init__(self, *args, **kwargs)
7+
8+
9+
app = Sub(__name__) # $ instance
10+
11+
12+
@app.route("/") # $ routeSetup="/"
13+
def hello(): # $ requestHandler
14+
return "world" # $ HttpResponse

python/ql/test/library-tests/frameworks/flask/old_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import flask
22

33
from flask import Flask, request, make_response
4-
app = Flask(__name__)
4+
app = Flask(__name__) # $ instance
55

66
@app.route("/") # $ routeSetup="/"
77
def hello_world(): # $ requestHandler

python/ql/test/library-tests/frameworks/flask/response_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from flask import Flask, make_response, jsonify, Response, request, redirect
44
from werkzeug.datastructures import Headers
55

6-
app = Flask(__name__)
6+
app = Flask(__name__) # $ instance
77

88

99
@app.route("/html1") # $ routeSetup="/html1"

python/ql/test/library-tests/frameworks/flask/routing_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import flask
22

33
from flask import Flask, make_response
4-
app = Flask(__name__)
4+
app = Flask(__name__) # $ instance
55

66

77
SOME_ROUTE = "/some/route"

0 commit comments

Comments
 (0)