Skip to content

Commit 826f44d

Browse files
committed
Python: Share implementation of awaited
1 parent 01ad19b commit 826f44d

File tree

4 files changed

+55
-78
lines changed

4 files changed

+55
-78
lines changed

python/ql/lib/semmle/python/ApiGraphs.qll

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ module API {
302302
* API graph node for the prefix `foo`), in accordance with the usual semantics of Python.
303303
*/
304304

305+
private import semmle.python.internal.Awaited
306+
305307
cached
306308
newtype TApiNode =
307309
/** The root of the API graph. */
@@ -485,43 +487,6 @@ module API {
485487
)
486488
}
487489

488-
/**
489-
* Holds if `result` is the result of awaiting `awaitedValue`.
490-
*/
491-
cached
492-
DataFlow::Node awaited(DataFlow::Node awaitedValue) {
493-
// `await` x
494-
// - `awaitedValue` is `x`
495-
// - `result` is `await x`
496-
exists(Await await |
497-
await.getValue() = awaitedValue.asExpr() and
498-
result.asExpr() = await
499-
)
500-
or
501-
// `async for x in l`
502-
// - `awaitedValue` is `l`
503-
// - `result` is `l` (`x` is behind a read step)
504-
exists(AsyncFor asyncFor |
505-
// To consider `x` the result of awaiting, we would use asyncFor.getTarget() = awaitedValue.asExpr(),
506-
// but that is behind a read step rather than a flow step.
507-
asyncFor.getIter() = awaitedValue.asExpr() and
508-
result.asExpr() = asyncFor.getIter()
509-
)
510-
or
511-
// `async with x as y`
512-
// - `awaitedValue` is `x`
513-
// - `result` is `x` and `y` if it exists
514-
exists(AsyncWith asyncWith |
515-
awaitedValue.asExpr() = asyncWith.getContextExpr() and
516-
result.asExpr() in [
517-
// `x`
518-
asyncWith.getContextExpr(),
519-
// `y`, if it exists
520-
asyncWith.getOptionalVars()
521-
]
522-
)
523-
}
524-
525490
/**
526491
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
527492
* `lbl` in the API graph.

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

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ private import semmle.python.ApiGraphs
1010

1111
/** Provides models for the `asyncpg` PyPI package. */
1212
private module Asyncpg {
13+
private import semmle.python.internal.Awaited
14+
1315
/** A `ConectionPool` is created when the result of `asyncpg.create_pool()` is awaited. */
1416
API::Node connectionPool() {
1517
result = API::moduleImport("asyncpg").getMember("create_pool").getReturn().getAwaited()
@@ -63,41 +65,6 @@ private module Asyncpg {
6365
}
6466
}
6567

66-
/**
67-
* Holds if `result` is the result of awaiting `awaitedValue`.
68-
*
69-
* Internal helper predicate to achieve the same as `.awaited()` does for API graphs,
70-
* but sutiable for use with type-tracking.
71-
*/
72-
pragma[inline]
73-
DataFlow::Node awaited(DataFlow::Node awaitedValue) {
74-
// `await` x
75-
// - `awaitedValue` is `x`
76-
// - `result` is `await x`
77-
exists(Await await |
78-
await.getValue() = awaitedValue.asExpr() and
79-
result.asExpr() = await
80-
)
81-
or
82-
// `async for x in l`
83-
// - `awaitedValue` is local source of `l`
84-
// - `result` is `l`
85-
exists(AsyncFor asyncFor, DataFlow::Node awaited |
86-
asyncFor.getIter() = awaited.asExpr() and
87-
awaited.getALocalSource() = awaitedValue and
88-
result.asExpr() = asyncFor.getIter()
89-
)
90-
or
91-
// `async with x as y`
92-
// - `awaitedValue` is local source of `x`
93-
// - `result` is `x` and `y`
94-
exists(AsyncWith asyncWith, DataFlow::Node awaited |
95-
awaited.asExpr() = asyncWith.getContextExpr() and
96-
awaited.getALocalSource() = awaitedValue and
97-
result.asExpr() in [asyncWith.getContextExpr(), asyncWith.getOptionalVars()]
98-
)
99-
}
100-
10168
/**
10269
* Provides models of the `PreparedStatement` class in `asyncpg`.
10370
* `PreparedStatement`s are created when the result of calling `prepare(query)` on a connection is awaited.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* INTERNAL: Do not use.
3+
*
4+
* Provides helper class for defining additional API graph edges.
5+
*/
6+
7+
private import python
8+
private import semmle.python.dataflow.new.DataFlow
9+
10+
/**
11+
* Holds if `result` is the result of awaiting `awaitedValue`.
12+
*/
13+
cached
14+
DataFlow::Node awaited(DataFlow::Node awaitedValue) {
15+
// `await` x
16+
// - `awaitedValue` is `x`
17+
// - `result` is `await x`
18+
exists(Await await |
19+
await.getValue() = awaitedValue.asExpr() and
20+
result.asExpr() = await
21+
)
22+
or
23+
// `async for x in l`
24+
// - `awaitedValue` is `l`
25+
// - `result` is `l` (`x` is behind a read step)
26+
exists(AsyncFor asyncFor |
27+
// To consider `x` the result of awaiting, we would use asyncFor.getTarget() = awaitedValue.asExpr(),
28+
// but that is behind a read step rather than a flow step.
29+
asyncFor.getIter() = awaitedValue.asExpr() and
30+
result.asExpr() = asyncFor.getIter()
31+
)
32+
or
33+
// `async with x as y`
34+
// - `awaitedValue` is `x`
35+
// - `result` is `x` and `y` if it exists
36+
exists(AsyncWith asyncWith |
37+
awaitedValue.asExpr() = asyncWith.getContextExpr() and
38+
result.asExpr() in [
39+
// `x`
40+
asyncWith.getContextExpr(),
41+
// `y`, if it exists
42+
asyncWith.getOptionalVars()
43+
]
44+
)
45+
}

python/ql/test/experimental/dataflow/ApiGraphs/awaited.ql

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ class AwaitedTest extends InlineExpectationsTest {
99
override string getARelevantTag() { result = "awaited" }
1010

1111
override predicate hasActualResult(Location location, string element, string tag, string value) {
12-
exists(API::Node a, DataFlow::Node n, API::Node pred |
13-
a = pred.getAwaited() and
14-
n = a.getAUse() and
15-
location = n.getLocation() and
12+
exists(API::Node awaited, DataFlow::Node use, API::Node pred |
13+
awaited = pred.getAwaited() and
14+
use = awaited.getAUse() and
15+
location = use.getLocation() and
1616
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
1717
// from the inline tests.
18-
not n instanceof DataFlow::ModuleVariableNode and
18+
not use instanceof DataFlow::ModuleVariableNode and
1919
exists(location.getFile().getRelativePath())
2020
|
2121
tag = "awaited" and
2222
value = pred.getPath() and
23-
element = n.toString()
23+
element = use.toString()
2424
)
2525
}
2626
}

0 commit comments

Comments
 (0)