Skip to content

Commit fbe8b64

Browse files
committed
Python: Add support for attribute reads and writes
1 parent 7112aa2 commit fbe8b64

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

python/ql/src/experimental/dataflow/TypeTracker.qll

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ class StepSummary extends TStepSummary {
4848
module StepSummary {
4949
cached
5050
predicate step(Node pred, Node succ, StepSummary summary) {
51-
exists(Node mid | simpleLocalFlowStep(pred, mid) and smallstep(mid, succ, summary))
51+
exists(Node mid | EssaFlow::essaFlowStep*(pred, mid) and smallstep(mid, succ, summary))
5252
}
5353

5454
predicate smallstep(Node pred, Node succ, StepSummary summary) {
55-
simpleLocalFlowStep(pred, succ) and
55+
EssaFlow::essaFlowStep(pred, succ) and
5656
summary = LevelStep()
5757
or
5858
callStep(pred, succ) and summary = CallStep()
@@ -69,22 +69,61 @@ module StepSummary {
6969
}
7070
}
7171

72+
/** Holds if `pred` steps to `succ` by being passed as a parameter in a call. */
7273
predicate callStep(ArgumentNode pred, ParameterNode succ) {
74+
// TODO: Support special methods?
7375
exists(DataFlowCall call, int i |
7476
pred.argumentOf(call, i) and succ.isParameterOf(call.getCallable(), i)
7577
)
7678
}
7779

80+
/** Holds if `pred` steps to `succ` by being returned from a call. */
7881
predicate returnStep(ReturnNode pred, Node succ) {
7982
exists(DataFlowCall call |
80-
pred.getEnclosingCallable() = call.getCallable() and succ = TCfgNode(call)
83+
pred.getEnclosingCallable() = call.getCallable() and succ = TCfgNode(call.getNode())
8184
)
8285
}
8386

84-
/** TODO: Implement these. */
85-
predicate basicStoreStep(Node pred, Node succ, string attr) { none() }
87+
/**
88+
* Holds if `pred` is being written to the `attr` attribute of the object in `succ`.
89+
*
90+
* Note that the choice of `succ` does not have to make sense "chronologically".
91+
* All we care about is whether the `attr` attribute of `succ` can have a specific type,
92+
* and the assumption is that if a specific type appears here, then any access of that
93+
* particular attribute can yield something of that particular type.
94+
*
95+
* Thus, in an example such as
96+
*
97+
* ```python
98+
* def foo(y):
99+
* x = Foo()
100+
* bar(x)
101+
* x.attr = y
102+
* baz(x)
103+
*
104+
* def bar(x):
105+
* z = x.attr
106+
* ```
107+
* for the attribute write `x.attr = y`, we will have `attr` being the literal string `"attr"`,
108+
* `pred` will be `y`, and `succ` will be the object `Foo()` created on the first line of the
109+
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
110+
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
111+
*/
112+
predicate basicStoreStep(Node pred, Node succ, string attr) {
113+
exists(AttributeAssignment a, Node var |
114+
a.getName() = attr and
115+
EssaFlow::essaFlowStep*(succ, var) and
116+
var.asVar() = a.getInput() and
117+
pred.asCfgNode() = a.getValue()
118+
)
119+
}
86120

87-
predicate basicLoadStep(Node pred, Node succ, string attr) { none() }
121+
/**
122+
* Holds if `succ` is the result of accessing the `attr` attribute of `pred`.
123+
*/
124+
predicate basicLoadStep(Node pred, Node succ, string attr) {
125+
exists(AttrNode s | succ.asCfgNode() = s and s.getObject(attr) = pred.asCfgNode())
126+
}
88127

89128
/**
90129
* A utility class that is equivalent to `boolean` but does not require type joining.
@@ -180,6 +219,13 @@ class TypeTracker extends TTypeTracker {
180219
*/
181220
boolean hasCall() { result = hasCall }
182221

222+
/**
223+
* INTERNAL. DO NOT USE.
224+
*
225+
* Gets the property associated with this type tracker.
226+
*/
227+
string getProp() { result = prop }
228+
183229
/**
184230
* Gets a type tracker that starts where this one has left off to allow continued
185231
* tracking.
@@ -231,7 +277,7 @@ class TypeTracker extends TTypeTracker {
231277
result = this.append(summary)
232278
)
233279
or
234-
simpleLocalFlowStep(pred, succ) and
280+
EssaFlow::essaFlowStep(pred, succ) and
235281
result = this
236282
}
237283
}

python/ql/src/experimental/dataflow/internal/DataFlowPublic.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import python
66
private import DataFlowPrivate
7+
import experimental.dataflow.TypeTracker
78

89
/**
910
* IPA type for data flow nodes.
@@ -67,6 +68,14 @@ class Node extends TNode {
6768

6869
/** Convenience method for casting to ExprNode and calling getNode and getNode again. */
6970
Expr asExpr() { none() }
71+
72+
/**
73+
* Gets a node that this node may flow to using one heap and/or interprocedural step.
74+
*
75+
* See `TypeTracker` for more details about how to use this.
76+
*/
77+
pragma[inline]
78+
Node track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
7079
}
7180

7281
class EssaNode extends Node, TEssaNode {

0 commit comments

Comments
 (0)