Skip to content

Commit feb2303

Browse files
committed
Python: Model the underlying DB-API connection
1 parent 1ab04a7 commit feb2303

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ private import semmle.python.dataflow.new.TaintTracking
1111
private import semmle.python.ApiGraphs
1212
private import semmle.python.Concepts
1313
private import experimental.semmle.python.Concepts
14+
// This import is done like this to avoid importing the deprecated top-level things that
15+
// would pollute the namespace
16+
private import semmle.python.frameworks.PEP249::PEP249 as PEP249
1417

1518
/**
1619
* Provides models for the `SQLAlchemy` PyPI package.
@@ -128,6 +131,43 @@ private module SqlAlchemy {
128131
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
129132
}
130133

134+
/**
135+
* Provides models for the underlying DB-API Connection of a SQLAlchemy Connection.
136+
*
137+
* See https://docs.sqlalchemy.org/en/14/core/connections.html#dbapi-connections.
138+
*/
139+
module DBAPIConnection {
140+
/**
141+
* A source of instances of DB-API Connections, extend this class to model new instances.
142+
*
143+
* This can include instantiations of the class, return values from function
144+
* calls, or a special parameter that will be set when functions are called by an external
145+
* library.
146+
*
147+
* Use the predicate `DBAPIConnection::instance()` to get references to instances of DB-API Connections.
148+
*/
149+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
150+
151+
private class DBAPIConnectionSources extends InstanceSource, PEP249::Connection::InstanceSource {
152+
DBAPIConnectionSources() {
153+
this.(DataFlow::MethodCallNode).calls(Engine::instance(), "raw_connection")
154+
or
155+
this.(DataFlow::AttrRead).accesses(Connection::instance(), "connection")
156+
}
157+
}
158+
159+
/** Gets a reference to an instance of DB-API Connections. */
160+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
161+
t.start() and
162+
result instanceof InstanceSource
163+
or
164+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
165+
}
166+
167+
/** Gets a reference to an instance of DB-API Connections. */
168+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
169+
}
170+
131171
/**
132172
* Provides models for the `sqlalchemy.orm.Session` class
133173
*

python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@
8484

8585
# raw connection
8686
raw_conn = conn.connection
87-
result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
87+
result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
8888
assert result.fetchall() == [("FOO",)]
8989

9090
cursor = raw_conn.cursor()
91-
cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
91+
cursor.execute(raw_sql) # $ getSql=raw_sql
9292
assert cursor.fetchall() == [("FOO",)]
9393
cursor.close()
9494

9595
raw_conn = engine.raw_connection()
96-
result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
96+
result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
9797
assert result.fetchall() == [("FOO",)]
9898

9999
# connection with custom execution options
@@ -288,7 +288,7 @@ class For14(Base):
288288
assert result.fetchall() == [("FOO",)]
289289

290290
raw_conn = conn.connection
291-
result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
291+
result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
292292
assert result.fetchall() == [("FOO",)]
293293

294294
# branching not allowed in 2.0
@@ -341,11 +341,11 @@ class For14(Base):
341341
# raw_connection
342342

343343
raw_conn = engine.raw_connection()
344-
result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
344+
result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
345345
assert result.fetchall() == [("FOO",)]
346346

347347
cursor = raw_conn.cursor()
348-
cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
348+
cursor.execute(raw_sql) # $ getSql=raw_sql
349349
assert cursor.fetchall() == [("FOO",)]
350350
cursor.close()
351351

0 commit comments

Comments
 (0)