Skip to content

Commit 6568332

Browse files
committed
Python: Add basic flow for class attributes
1 parent 6c50c2b commit 6568332

File tree

2 files changed

+34
-8
lines changed

2 files changed

+34
-8
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -846,10 +846,27 @@ predicate comprehensionStoreStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
846846
* ```
847847
* data flows from `x` to the attribute `foo` of (the post-update node for) `obj`.
848848
*/
849-
predicate attributeStoreStep(Node nodeFrom, AttributeContent c, PostUpdateNode nodeTo) {
850-
exists(AttrWrite write |
851-
write.accesses(nodeTo.getPreUpdateNode(), c.getAttribute()) and
852-
nodeFrom = write.getValue()
849+
predicate attributeStoreStep(Node nodeFrom, AttributeContent c, Node nodeTo) {
850+
exists(Node object |
851+
// normally we target any PostUpdateNode. However, for class definitions the class
852+
// is only constructed after evaluating its' entire scope, so in terms of python
853+
// evaluations there is no post or pre update nodes, just one node for the class
854+
// expression. Therefore we target the class expression directly.
855+
//
856+
// Note: Due to the way we handle decorators, using a class decorator will result in
857+
// there being a post-update node for the class (argument to the decorator). We do
858+
// not want to differentiate between these two cases, so still target the class
859+
// expression directly.
860+
object = nodeTo.(PostUpdateNode).getPreUpdateNode() and
861+
not object.asExpr() instanceof ClassExpr
862+
or
863+
object = nodeTo and
864+
object.asExpr() instanceof ClassExpr
865+
|
866+
exists(AttrWrite write |
867+
write.accesses(object, c.getAttribute()) and
868+
nodeFrom = write.getValue()
869+
)
853870
)
854871
}
855872

python/ql/test/experimental/dataflow/fieldflow/test.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,32 @@ def test_inst(self):
248248
SINK(self.my_tuple[0]) # $ MISSING: flow
249249
SINK_F(self.my_tuple[1])
250250

251+
def test_inst_no_call(self):
252+
SINK(self.my_tuple[0]) # $ MISSING: flow
253+
SINK_F(self.my_tuple[1])
254+
251255
@classmethod
252256
def test_cm(cls):
253-
SINK(cls.my_tuple[0]) # $ MISSING: flow
257+
SINK(cls.my_tuple[0]) # $ flow="SOURCE, l:-12 -> cls.my_tuple[0]"
258+
SINK_F(cls.my_tuple[1])
259+
260+
@classmethod
261+
def test_cm_no_call(cls):
262+
SINK(cls.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-8 -> cls.my_tuple[0]"
254263
SINK_F(cls.my_tuple[1])
255264

256265

257266
@expects(2*4) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
258267
def test_WithTuple():
259-
SINK(WithTuple.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-7 -> WithTuple.my_tuple[0]"
268+
SINK(WithTuple.my_tuple[0]) # $ flow="SOURCE, l:-23 -> WithTuple.my_tuple[0]"
260269
SINK_F(WithTuple.my_tuple[1])
261270

262271
WithTuple.test_cm()
263272

264273
inst = WithTuple()
265274
inst.test_inst()
266275

267-
SINK(inst.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-18 -> inst.my_tuple[0]"
276+
SINK(inst.my_tuple[0]) # $ MISSING: flow
268277
SINK_F(inst.my_tuple[1])
269278

270279

@@ -279,7 +288,7 @@ def test_inst_override():
279288
SINK_F(inst.my_tuple[0])
280289
SINK(inst.my_tuple[1]) # $ flow="SOURCE, l:-3 -> inst.my_tuple[1]"
281290

282-
SINK(WithTuple.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-27 -> WithTuple.my_tuple[0]"
291+
SINK(WithTuple.my_tuple[0]) # $ flow="SOURCE, l:-46 -> WithTuple.my_tuple[0]"
283292
SINK_F(WithTuple.my_tuple[1])
284293

285294

0 commit comments

Comments
 (0)