Skip to content

Commit 0d20a4c

Browse files
committed
Python: Simplify modelling
1 parent f948ef8 commit 0d20a4c

3 files changed

Lines changed: 311 additions & 295 deletions

File tree

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

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,71 +1023,77 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
10231023
* sequence = iterable
10241024
* ```
10251025
* where `sequence` is either a tuple or a list and it can contain wildcards.
1026-
* The iterable can be any iterable, which means that (CodeQL modeling of) content will need to change type
1027-
* if it should be transferred from the LHS to the RHS.
1026+
* The iterable can be any iterable, which means that (CodeQL modeling of) content
1027+
* will need to change type if it should be transferred from the LHS to the RHS.
1028+
*
1029+
* Note that (CodeQL modeling of) content does not have to change type on data-flow
1030+
* path _inside_ the LHS, as the different allowed syntaxes here are merely a convenience.
1031+
* Consequently, we model all LHS sequences as tuples, which have the more precise content
1032+
* model, making flow to the elements more precise. If an element is a starred varibale,
1033+
* we will have to mutate the content type to be list content.
10281034
*
10291035
* We may for instance have
10301036
* ```python
1031-
* (a, b) = ["a", "tainted string"] # RHS has content `ListElementContent`
1037+
* (a, b) = ["a", SOURCE] # RHS has content `ListElementContent`
10321038
* ```
1033-
* Due to the abstraction for list content, we do not know whether `"tainted string"`
1039+
* Due to the abstraction for list content, we do not know whether `SOURCE`
10341040
* ends up in `a` or in `b`, so we want to overapproximate and see it in both.
10351041
*
10361042
* Using wildcards we may have
10371043
* ```python
1038-
* (a, *b) = ("a", "b", "tainted string") # RHS has content `TupleElementContent(2)`
1044+
* (a, *b) = ("a", "b", SOURCE) # RHS has content `TupleElementContent(2)`
10391045
* ```
1040-
* Since the starred variables are always assigned type list, `*b` will be
1041-
* `["b", "tainted string]`, and we will again overapproximate and assign it
1046+
* Since the starred variables are always assigned (Python-)type list, `*b` will be
1047+
* `["b", SOURCE]`, and we will again overapproximate and assign it
10421048
* content corresponding to anything found in the RHS.
10431049
*
10441050
* For a precise transfer
10451051
* ```python
1046-
* (a, b) = ("a", "tainted string") # RHS has content `TupleElementContent(1)`
1052+
* (a, b) = ("a", SOURCE) # RHS has content `TupleElementContent(1)`
10471053
* ```
10481054
* we wish to keep the precision, so only `b` receives the tuple content at index 1.
10491055
*
10501056
* Finally, `sequence` is actually a pattern and can have a more complicated structure,
10511057
* such as
10521058
* ```python
1053-
* (a, [b, *c]) = ("a", ("tainted string", "c")) # RHS has content `TupleElementContent(1); TupleElementContent(0)`
1059+
* (a, [b, *c]) = ("a", ["b", SOURCE]) # RHS has content `TupleElementContent(1); ListElementContent`
10541060
* ```
1055-
* where `a` should not receive content, but `b` and `c` should. `c` will be `["c"]` so
1061+
* where `a` should not receive content, but `b` and `c` should. `c` will be `[SOURCE]` so
10561062
* should have the content converted and transferred, while `b` should read it.
10571063
*
10581064
* The strategy for converting content type is to break the transfer up into a read step
10591065
* and a store step, together creating a converting transfer step.
10601066
* For this we need a synthetic node in the middle, which we call `TIterableElement(receiver)`.
1061-
* It is associated with the receiver of the transfer, because we know the receiver type from the syntax.
1067+
* It is associated with the receiver of the transfer, because we know the receiver type (tuple) from the syntax.
10621068
* Since we sometimes need a converting read step (in the example above, `[b, *c]` reads the content
1063-
* `TupleElementContent(0)` but should have content `ListElementContent`), we actually need a second synthetic node.
1064-
* A converting read step is a read step followed by a converting transfer.
1069+
* `ListElementContent` but should have content `TupleElementContent(0)` and `TupleElementContent(0)`),
1070+
* we actually need a second synthetic node. A converting read step is a read step followed by a
1071+
* converting transfer.
1072+
*
10651073
* We can have a uniform treatment by always having two synthetic nodes and so we can view it as
10661074
* two stages of the same node. So we read into (or transfer to) `TIterableSequence(receiver)`,
10671075
* from which we take a read step to `TIterableElement(receiver)` and then a store step to `receiver`.
1076+
*
10681077
* In order to preserve precise content, we also take a flow step from `TIterableSequence(receiver)`
10691078
* directly to `receiver`.
10701079
*
1071-
* The strategy is then via several read-, store-, and flow steps, illustrated on the assignment
1072-
*
1073-
* ```python
1074-
* (a, [b, *c]) = ["a", [SOURCE]]
1075-
* ```
1076-
*
1080+
* The strategy is then via several read-, store-, and flow steps:
10771081
* 1. [Flow] Content is transferred from `iterable` to `TIterableSequence(sequence)` via a
10781082
* flow step. From here, everything happens on the LHS.
10791083
*
10801084
* 2. [Flow] Content is transferred from `TIterableSequence(sequence)` to `sequence` via a
10811085
* flow step.
10821086
*
10831087
* 3. [Read] Content is read from `TIterableSequence(sequence)` into `TIterableElement(sequence)`.
1084-
* If `sequence` is of type tuple, we will not read tuple content as that would allow
1088+
* As `sequence` is modelled as a tuple, we will not read tuple content as that would allow
10851089
* cross talk.
10861090
*
10871091
* 4. [Store] Content is stored from `TIterableElement(sequence)` to `sequence`.
1088-
* Here the content type is chosen according to the type of sequence.
1092+
* Content type is `TupleElementContent` with indices taken from the syntax.
1093+
* For instance, if `sequence` is `(a, *b, c)`, content is written to index 0, 1, and 2.
1094+
* This is adequate as the route through `TIterableElement(sequence)` does not transfer precise content.
10891095
*
1090-
* 5. [Read] Content is read from `sequence` to its elements according to the type of `sequence`.
1096+
* 5. [Read] Content is read from `sequence` to its elements.
10911097
* a) If the element is a plain variable, the target is the corresponding essa node.
10921098
*
10931099
* b) If the element is itelf a sequence, with control-flow node `seq`, the target is `TIterableSequence(seq)`.
@@ -1130,7 +1136,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
11301136
*
11311137
* --Step 4-->
11321138
*
1133-
* `[b, *c]`: [ListElementContent]
1139+
* `[b, *c]`: [TupleElementContent(1)]
11341140
*
11351141
* --Step 5c-->
11361142
*
@@ -1185,7 +1191,7 @@ module UnpackingAssignment {
11851191
/**
11861192
* Step 3
11871193
* Data flows from `TIterableSequence(sequence)` into `TIterableElement(sequence)`.
1188-
* If `sequence` is of type tuple, we will not read tuple content as that would allow
1194+
* As `sequence` is modelled as a tuple, we will not read tuple content as that would allow
11891195
* cross talk.
11901196
*/
11911197
predicate unpackingAssignmentConvertingReadStep(Node nodeFrom, Content c, Node nodeTo) {
@@ -1196,13 +1202,6 @@ module UnpackingAssignment {
11961202
c instanceof ListElementContent
11971203
or
11981204
c instanceof SetElementContent
1199-
or
1200-
// do not lose precision by routing tuple content through the `IterableElement`
1201-
not target instanceof TupleNode and
1202-
// `index` refers to `nodeFrom`, but only the ones in `target` are relevant.
1203-
exists(int index | exists(target.getElement(index)) |
1204-
c.(TupleElementContent).getIndex() = index
1205-
)
12061205
// TODO: dict content in iterable unpacking not handled
12071206
)
12081207
)
@@ -1211,20 +1210,15 @@ module UnpackingAssignment {
12111210
/**
12121211
* Step 4
12131212
* Data flows from `TIterableElement(sequence)` to `sequence`.
1214-
* The content type is chosen according to the type of sequence.
1213+
* Content type is `TupleElementContent` with indices taken from the syntax.
1214+
* For instance, if `sequence` is `(a, *b, c)`, content is written to index 0, 1, and 2.
12151215
*/
12161216
predicate unpackingAssignmentConvertingStoreStep(Node nodeFrom, Content c, Node nodeTo) {
12171217
exists(UnpackingAssignmentSequenceTarget target |
12181218
nodeFrom = TIterableElementNode(target) and
12191219
nodeTo.asCfgNode() = target and
1220-
(
1221-
target instanceof ListNode and
1222-
c instanceof ListElementContent
1223-
or
1224-
target instanceof TupleNode and
1225-
exists(int index | exists(target.getElement(index)) |
1226-
c.(TupleElementContent).getIndex() = index
1227-
)
1220+
exists(int index | exists(target.getElement(index)) |
1221+
c.(TupleElementContent).getIndex() = index
12281222
)
12291223
)
12301224
}
@@ -1241,36 +1235,37 @@ module UnpackingAssignment {
12411235
*/
12421236
predicate unpackingAssignmentElementReadStep(Node nodeFrom, Content c, Node nodeTo) {
12431237
exists(
1244-
UnpackingAssignmentSequenceTarget target, int index, ControlFlowNode element, boolean precise
1238+
UnpackingAssignmentSequenceTarget target, int index, ControlFlowNode element, int starIndex
1239+
|
1240+
target.getElement(starIndex) instanceof StarredNode
1241+
or
1242+
not exists(target.getAnElement().(StarredNode)) and
1243+
starIndex = -1
12451244
|
12461245
nodeFrom.asCfgNode() = target and
12471246
element = target.getElement(index) and
12481247
(
1249-
target instanceof ListNode and
1250-
c instanceof ListElementContent
1251-
or
1252-
target instanceof TupleNode and
1253-
if precise = true
1248+
if starIndex = -1 or index < starIndex
12541249
then c.(TupleElementContent).getIndex() = index
1255-
else c instanceof TupleElementContent // This could get big if big tuples exist
1250+
else
1251+
// This could get big if big tuples exist
1252+
if index = starIndex
1253+
then c.(TupleElementContent).getIndex() >= index
1254+
else c.(TupleElementContent).getIndex() >= index - 1
12561255
) and
12571256
(
12581257
if element instanceof SequenceNode
12591258
then
12601259
// Step 5b
1261-
nodeTo = TIterableSequenceNode(element) and
1262-
precise = true
1260+
nodeTo = TIterableSequenceNode(element)
12631261
else
1264-
if element.getNode() instanceof Starred
1262+
if element instanceof StarredNode
12651263
then
12661264
// Step 5c
1267-
nodeTo = TIterableElementNode(element) and
1268-
precise = false
1269-
else (
1265+
nodeTo = TIterableElementNode(element)
1266+
else
12701267
// Step 5a
1271-
nodeTo.asVar().getDefinition().(MultiAssignmentDefinition).getDefiningNode() = element and
1272-
precise = true
1273-
)
1268+
nodeTo.asVar().getDefinition().(MultiAssignmentDefinition).getDefiningNode() = element
12741269
)
12751270
)
12761271
}

0 commit comments

Comments
 (0)