Skip to content

Commit 902bade

Browse files
authored
Merge pull request #5015 from yoff/python-add-missing-postupdate-nodes
Python: add missing postupdate nodes
2 parents 76e1e4d + e253855 commit 902bade

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,27 @@ class StorePreUpdateNode extends NeedsSyntheticPostUpdateNode, CfgNode {
6969
override string label() { result = "store" }
7070
}
7171

72-
/** A node marking the state change of an object after a read. */
72+
/**
73+
* A node marking the state change of an object after a read.
74+
*
75+
* A reverse read happens when the result of a read is modified, e.g. in
76+
* ```python
77+
* l = [ mutable ]
78+
* l[0].mutate()
79+
* ```
80+
* we may now have changed the content of `l`. To track this, there must be
81+
* a postupdate node for `l`.
82+
*/
7383
class ReadPreUpdateNode extends NeedsSyntheticPostUpdateNode, CfgNode {
7484
ReadPreUpdateNode() {
7585
exists(Attribute a |
7686
node = a.getObject().getAFlowNode() and
7787
a.getCtx() instanceof Load
7888
)
89+
or
90+
node = any(SubscriptNode s).getObject()
91+
or
92+
node.getNode() = any(Call call).getKwargs()
7993
}
8094

8195
override string label() { result = "read" }

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,3 +591,35 @@ def return_from_inner_scope(x):
591591

592592
def test_return_from_inner_scope():
593593
SINK(return_from_inner_scope([])) #$ flow="SOURCE, l:-3 -> return_from_inner_scope(..)"
594+
595+
596+
# Inspired by reverse read inconsistency check
597+
def insertAtA(d):
598+
d["a"] = SOURCE
599+
600+
def test_reverse_read_subscript():
601+
d = {"a": NONSOURCE}
602+
l = [d]
603+
insertAtA(l[0])
604+
SINK(d["a"]) #$ MISSING:flow="SOURCE, l-6 -> d['a']""
605+
606+
def test_reverse_read_dict_arg():
607+
d = {"a": NONSOURCE}
608+
dd = {"d": d}
609+
insertAtA(**dd)
610+
SINK(d["a"]) #$ MISSING:flow="SOURCE, l-12 -> d['a']""
611+
612+
613+
class WithA:
614+
def setA(self, v):
615+
self.a = v
616+
617+
def __init__(self):
618+
self.a = ""
619+
620+
621+
def test_reverse_read_subscript_cls():
622+
withA = WithA()
623+
l = [withA]
624+
l[0].setA(SOURCE)
625+
SINK(withA.a) #$ MISSING:flow="SOURCE, l:-1 -> self.a"

0 commit comments

Comments
 (0)