Skip to content

Commit a58c47b

Browse files
committed
Python: model aiopg.sa
1 parent f533140 commit a58c47b

File tree

2 files changed

+47
-1
lines changed
  • python/ql

2 files changed

+47
-1
lines changed

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ private module Aiopg {
4141
result = connection().getMember("cursor").getReturn().getAwaited()
4242
}
4343

44+
/** Calling `execute` on a `Cursor` constructs a query. */
4445
class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
4546
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
4647

@@ -71,4 +72,49 @@ private module Aiopg {
7172

7273
override DataFlow::Node getSql() { result = sql }
7374
}
75+
76+
/** An `Engine` is created when the result of calling `aiopg.sa.create_engine` is awaited. */
77+
API::Node engine() {
78+
result =
79+
API::moduleImport("aiopg").getMember("sa").getMember("create_engine").getReturn().getAwaited()
80+
}
81+
82+
/**
83+
* A `SAConnection` is created when the result of calling `aquire` on an `Engine` is awaited.
84+
*/
85+
API::Node saConnection() { result = engine().getMember("acquire").getReturn().getAwaited() }
86+
87+
/** Calling `execute` on a `SAConnection` constructs a query. */
88+
class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
89+
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
90+
91+
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
92+
}
93+
94+
/**
95+
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
96+
* It should be obsolete once we have `API::CallNode` available.
97+
*/
98+
private DataFlow::TypeTrackingNode saConnectionExecuteCall(
99+
DataFlow::TypeTracker t, DataFlow::Node sql
100+
) {
101+
// saConnection created from engine
102+
t.start() and
103+
sql = result.(SAConnectionExecuteCall).getSql()
104+
or
105+
exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
106+
}
107+
108+
DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
109+
saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
110+
}
111+
112+
/** Awaiting the result of calling `execute` executes the query. */
113+
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
114+
DataFlow::Node sql;
115+
116+
AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
117+
118+
override DataFlow::Node getSql() { result = sql }
119+
}
74120
}

python/ql/test/library-tests/frameworks/aiopg/test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ async def test_cursor():
2323
async def test_engine():
2424
engine = await create_engine()
2525
conn = await engine.acquire()
26-
await conn.execute("sql") # $ MISSING: getSql="sql" constructedSql="sql"
26+
await conn.execute("sql") # $ getSql="sql" constructedSql="sql"

0 commit comments

Comments
 (0)