Skip to content

Commit b6bd70a

Browse files
committed
Python: Modernise flask library modeling
Two interesting things happened while doing this: 1. I found out that you can't use the same name to define a submodule as any parent module. So we need give unique names to the top-level module, and the module for modeling the `flask.Flask` class. I randomly choose a new name for the top-level module to get things moving (and not be stuck in bikeshedding forever). 2. With this new setup, I wanted to expose the `route` and `add_url_rule` methods on instances of `flask.Flask`. It wasn't quite obvious how to do so. I simply lumped them next to `classRef()` and `instance()`, without too much care. I did consider putting them inside a `instance` module, which would allow you to access them by `flask::Flask::instance::route()`, but I wasn't quite sure, and just did something easy to get moving.
1 parent 62d665e commit b6bd70a

File tree

1 file changed

+95
-63
lines changed
  • python/ql/src/experimental/semmle/python/frameworks

1 file changed

+95
-63
lines changed

python/ql/src/experimental/semmle/python/frameworks/Flask.qll

Lines changed: 95 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private import experimental.semmle.python.frameworks.Werkzeug
1414
* Provides models for the `flask` PyPI package.
1515
* See https://flask.palletsprojects.com/en/1.1.x/.
1616
*/
17-
private module Flask {
17+
private module Flask_Private {
1818
/** Gets a reference to the `flask` module. */
1919
private DataFlow::Node flask(DataFlow::TypeTracker t) {
2020
t.start() and
@@ -42,69 +42,101 @@ private module Flask {
4242
/** Gets a reference to the `flask.request` object. */
4343
DataFlow::Node request() { result = request(DataFlow::TypeTracker::end()) }
4444

45-
/** Gets a reference to the `flask.Flask` class. */
46-
private DataFlow::Node classFlask(DataFlow::TypeTracker t) {
47-
t.start() and
48-
result = DataFlow::importNode("flask.Flask")
49-
or
50-
t.startInAttr("Flask") and
51-
result = flask()
52-
or
53-
exists(DataFlow::TypeTracker t2 | result = classFlask(t2).track(t2, t))
54-
}
55-
56-
/** Gets a reference to the `flask.Flask` class. */
57-
DataFlow::Node classFlask() { result = classFlask(DataFlow::TypeTracker::end()) }
58-
59-
/** Gets a reference to an instance of `flask.Flask` (a Flask application). */
60-
private DataFlow::Node app(DataFlow::TypeTracker t) {
61-
t.start() and
62-
result.asCfgNode().(CallNode).getFunction() = flask::classFlask().asCfgNode()
63-
or
64-
exists(DataFlow::TypeTracker t2 | result = app(t2).track(t2, t))
45+
/**
46+
* Provides models for the `flask.Flask` class
47+
*
48+
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
49+
*/
50+
module Flask {
51+
/** Gets a reference to the `flask.Flask` class. */
52+
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
53+
t.start() and
54+
result = DataFlow::importNode("flask.Flask")
55+
or
56+
t.startInAttr("Flask") and
57+
result = flask()
58+
or
59+
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
60+
}
61+
62+
/** Gets a reference to the `flask.Flask` class. */
63+
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
64+
65+
/**
66+
* A source of an instance of `flask.Flask`.
67+
*
68+
* This can include instantiation of the class, return value from function
69+
* calls, or a special parameter that will be set when functions are call by external
70+
* library.
71+
*
72+
* Use `Flask::instance()` predicate to get references to instances of `flask.Flask`.
73+
*/
74+
abstract class InstanceSource extends DataFlow::Node { }
75+
76+
/** A direct instantiation of `flask.Flask`. */
77+
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
78+
override CallNode node;
79+
80+
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
81+
}
82+
83+
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
84+
private DataFlow::Node instance(DataFlow::TypeTracker t) {
85+
t.start() and
86+
result instanceof InstanceSource
87+
or
88+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
89+
}
90+
91+
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
92+
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
93+
94+
/**
95+
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
96+
* WARNING: Only holds for a few predefined attributes.
97+
*/
98+
private DataFlow::Node instance_attr(DataFlow::TypeTracker t, string attr_name) {
99+
attr_name in ["route", "add_url_rule"] and
100+
t.startInAttr(attr_name) and
101+
result = flask::Flask::instance()
102+
or
103+
// Due to bad performance when using normal setup with `instance_attr(t2, attr_name).track(t2, t)`
104+
// we have inlined that code and forced a join
105+
exists(DataFlow::TypeTracker t2 |
106+
exists(DataFlow::StepSummary summary |
107+
instance_attr_first_join(t2, attr_name, result, summary) and
108+
t = t2.append(summary)
109+
)
110+
)
111+
}
112+
113+
pragma[nomagic]
114+
private predicate instance_attr_first_join(
115+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
116+
DataFlow::StepSummary summary
117+
) {
118+
DataFlow::StepSummary::step(instance_attr(t2, attr_name), res, summary)
119+
}
120+
121+
/**
122+
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
123+
* WARNING: Only holds for a few predefined attributes.
124+
*/
125+
private DataFlow::Node instance_attr(string attr_name) {
126+
result = instance_attr(DataFlow::TypeTracker::end(), attr_name)
127+
}
128+
129+
/** Gets a reference to the `route` method on an instance of `flask.Flask`. */
130+
DataFlow::Node route() { result = instance_attr("route") }
131+
132+
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Flask`. */
133+
DataFlow::Node add_url_rule() { result = instance_attr("add_url_rule") }
65134
}
66-
67-
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
68-
DataFlow::Node app() { result = app(DataFlow::TypeTracker::end()) }
69135
}
70136

71137
// ---------------------------------------------------------------------------
72138
// routing modeling
73139
// ---------------------------------------------------------------------------
74-
/**
75-
* Gets a reference to the attribute `attr_name` of a flask application.
76-
* WARNING: Only holds for a few predefined attributes.
77-
*/
78-
private DataFlow::Node app_attr(DataFlow::TypeTracker t, string attr_name) {
79-
attr_name in ["route", "add_url_rule"] and
80-
t.startInAttr(attr_name) and
81-
result = flask::app()
82-
or
83-
// Due to bad performance when using normal setup with `app_attr(t2, attr_name).track(t2, t)`
84-
// we have inlined that code and forced a join
85-
exists(DataFlow::TypeTracker t2 |
86-
exists(DataFlow::StepSummary summary |
87-
app_attr_first_join(t2, attr_name, result, summary) and
88-
t = t2.append(summary)
89-
)
90-
)
91-
}
92-
93-
pragma[nomagic]
94-
private predicate app_attr_first_join(
95-
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
96-
) {
97-
DataFlow::StepSummary::step(app_attr(t2, attr_name), res, summary)
98-
}
99-
100-
/**
101-
* Gets a reference to the attribute `attr_name` of a flask application.
102-
* WARNING: Only holds for a few predefined attributes.
103-
*/
104-
private DataFlow::Node app_attr(string attr_name) {
105-
result = app_attr(DataFlow::TypeTracker::end(), attr_name)
106-
}
107-
108140
private string werkzeug_rule_re() {
109141
// since flask uses werkzeug internally, we are using its routing rules from
110142
// https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
@@ -132,14 +164,14 @@ private module Flask {
132164
}
133165

134166
/**
135-
* A call to `flask.Flask.route`.
167+
* A call the `route` method on an instance of `flask.Flask`.
136168
*
137169
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.route
138170
*/
139171
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CfgNode {
140172
override CallNode node;
141173

142-
FlaskAppRouteCall() { node.getFunction() = app_attr("route").asCfgNode() }
174+
FlaskAppRouteCall() { node.getFunction() = flask::Flask::route().asCfgNode() }
143175

144176
override DataFlow::Node getUrlPatternArg() {
145177
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
@@ -149,14 +181,14 @@ private module Flask {
149181
}
150182

151183
/**
152-
* A call to `flask.Flask.add_url_rule`.
184+
* A call the `add_url_rule` method on an instance of `flask.Flask`.
153185
*
154186
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
155187
*/
156-
private class FlaskAppAddUrlRule extends FlaskRouteSetup, DataFlow::CfgNode {
188+
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CfgNode {
157189
override CallNode node;
158190

159-
FlaskAppAddUrlRule() { node.getFunction() = app_attr("add_url_rule").asCfgNode() }
191+
FlaskAppAddUrlRuleCall() { node.getFunction() = flask::Flask::add_url_rule().asCfgNode() }
160192

161193
override DataFlow::Node getUrlPatternArg() {
162194
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]

0 commit comments

Comments
 (0)