Skip to content

Commit eb43fa2

Browse files
committed
Python: Make API graph version of PEP249 modeling
This will allow us to more easily handle the executescript method, which we'll do in next commit.
1 parent 5930499 commit eb43fa2

File tree

3 files changed

+78
-11
lines changed

3 files changed

+78
-11
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,8 @@ module PrivateDjango {
561561
API::Node connection() { result = db().getMember("connection") }
562562

563563
/** A `django.db.connection` is a PEP249 compliant DB connection. */
564-
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
565-
DjangoDbConnection() { this = connection().asSource() }
564+
class DjangoDbConnection extends PEP249::DatabaseConnection {
565+
DjangoDbConnection() { this = connection() }
566566
}
567567

568568
// -------------------------------------------------------------------------

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

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,67 @@ module PEP249 {
2222
override string toString() { result = this.(API::Node).toString() }
2323
}
2424

25+
/**
26+
* An API graph node representing a database connection.
27+
*/
28+
abstract class DatabaseConnection extends API::Node {
29+
/** Gets a string representation of this element. */
30+
override string toString() { result = this.(API::Node).toString() }
31+
}
32+
33+
private class DefaultDatabaseConnection extends DatabaseConnection {
34+
DefaultDatabaseConnection() {
35+
this = any(PEP249ModuleApiNode mod).getMember("connect").getReturn()
36+
}
37+
}
38+
39+
/**
40+
* An API graph node representing a database cursor.
41+
*/
42+
abstract class DatabaseCursor extends API::Node {
43+
/** Gets a string representation of this element. */
44+
override string toString() { result = this.(API::Node).toString() }
45+
}
46+
47+
private class DefaultDatabaseCursor extends DatabaseCursor {
48+
DefaultDatabaseCursor() { this = any(DatabaseConnection conn).getMember("cursor").getReturn() }
49+
}
50+
51+
private string getSqlKwargName() {
52+
result in ["sql", "statement", "operation", "query", "query_string"]
53+
}
54+
55+
/**
56+
* A call to `execute` or `executemany` method on a database cursor or a connection.
57+
*
58+
* See
59+
* - https://peps.python.org/pep-0249/#execute
60+
* - https://peps.python.org/pep-0249/#executemany
61+
*
62+
* Note: While `execute` method on a connection is not part of PEP249, if it is used, we
63+
* recognize it as an alias for constructing a cursor and calling `execute` on it.
64+
*/
65+
private class ExecuteMethodCall extends SqlExecution::Range, API::CallNode {
66+
ExecuteMethodCall() {
67+
exists(API::Node start |
68+
start instanceof DatabaseCursor or start instanceof DatabaseConnection
69+
|
70+
this = start.getMember(["execute", "executemany"]).getACall()
71+
)
72+
}
73+
74+
override DataFlow::Node getSql() {
75+
result in [this.getArg(0), this.getArgByName(getSqlKwargName()),]
76+
}
77+
}
78+
79+
// ---------------------------------------------------------------------------
80+
// old impl
81+
// ---------------------------------------------------------------------------
82+
// the goal is to deprecate it in favour of the API graph version, but currently this
83+
// requires a rewrite of the Peewee modeling, which depends on rewriting the
84+
// instance/instance-source stuff to use API graphs instead.
85+
// so is postponed for now.
2586
/** Gets a reference to the `connect` function of a module that implements PEP 249. */
2687
DataFlow::Node connect() {
2788
result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource()
@@ -147,7 +208,10 @@ module PEP249 {
147208
* recognize it as an alias for constructing a cursor and calling `execute` on it.
148209
*/
149210
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
150-
ExecuteCall() { this.getFunction() = execute() }
211+
ExecuteCall() {
212+
this.getFunction() = execute() and
213+
not this instanceof ExecuteMethodCall
214+
}
151215

152216
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
153217
}
@@ -170,8 +234,13 @@ module PEP249 {
170234
* recognize it as an alias for constructing a cursor and calling `executemany` on it.
171235
*/
172236
private class ExecutemanyCall extends SqlExecution::Range, DataFlow::CallCfgNode {
173-
ExecutemanyCall() { this.getFunction() = executemany() }
237+
ExecutemanyCall() {
238+
this.getFunction() = executemany() and
239+
not this instanceof ExecuteMethodCall
240+
}
174241

175-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
242+
override DataFlow::Node getSql() {
243+
result in [this.getArg(0), this.getArgByName(getSqlKwargName())]
244+
}
176245
}
177246
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,18 @@ private module Peewee {
163163
* A call to the `connection` method on a `peewee.Database` instance.
164164
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.connection.
165165
*/
166-
class PeeweeDatabaseConnectionCall extends PEP249::Connection::InstanceSource,
167-
DataFlow::CallCfgNode
168-
{
166+
class PeeweeDatabaseConnectionCall extends PEP249::DatabaseConnection {
169167
PeeweeDatabaseConnectionCall() {
170-
this = Database::instance().getMember("connection").getACall()
168+
this = Database::instance().getMember("connection").getReturn()
171169
}
172170
}
173171

174172
/**
175173
* A call to the `cursor` method on a `peewee.Database` instance.
176174
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.cursor.
177175
*/
178-
class PeeweeDatabaseCursorCall extends PEP249::Cursor::InstanceSource, DataFlow::CallCfgNode {
179-
PeeweeDatabaseCursorCall() { this = Database::instance().getMember("cursor").getACall() }
176+
class PeeweeDatabaseCursorCall extends PEP249::DatabaseCursor {
177+
PeeweeDatabaseCursorCall() { this = Database::instance().getMember("cursor").getReturn() }
180178
}
181179

182180
/**

0 commit comments

Comments
 (0)