Skip to content

Commit b6007cf

Browse files
authored
Merge pull request github#5023 from yoff/python-unify-synthetic-post-update-nodes
Python: Only generate one post-update node, even if there are multiple reasons for doing so.
2 parents 1068ede + ae2c122 commit b6007cf

File tree

1 file changed

+103
-82
lines changed

1 file changed

+103
-82
lines changed

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

Lines changed: 103 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,117 +11,138 @@ private import semmle.python.essa.SsaCompute
1111
//--------
1212
predicate isExpressionNode(ControlFlowNode node) { node.getNode() instanceof Expr }
1313

14-
/** A data flow node for which we should synthesise an associated pre-update node. */
15-
abstract class NeedsSyntheticPreUpdateNode extends Node {
16-
/** A label for this kind of node. This will figure in the textual representation of the synthesized pre-update node. */
17-
abstract string label();
18-
}
14+
/** A module collecting the different reasons for synthesising a pre-update node. */
15+
module syntheticPreUpdateNode {
16+
class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
17+
NeedsSyntheticPreUpdateNode post;
1918

20-
class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
21-
NeedsSyntheticPreUpdateNode post;
19+
SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(post) }
2220

23-
SyntheticPreUpdateNode() { this = TSyntheticPreUpdateNode(post) }
21+
/** Gets the node for which this is a synthetic pre-update node. */
22+
Node getPostUpdateNode() { result = post }
2423

25-
/** Gets the node for which this is a synthetic pre-update node. */
26-
Node getPostUpdateNode() { result = post }
24+
override string toString() { result = "[pre " + post.label() + "] " + post.toString() }
2725

28-
override string toString() { result = "[pre " + post.label() + "] " + post.toString() }
26+
override Scope getScope() { result = post.getScope() }
2927

30-
override Scope getScope() { result = post.getScope() }
28+
override Location getLocation() { result = post.getLocation() }
29+
}
3130

32-
override Location getLocation() { result = post.getLocation() }
33-
}
31+
/** A data flow node for which we should synthesise an associated pre-update node. */
32+
class NeedsSyntheticPreUpdateNode extends PostUpdateNode {
33+
NeedsSyntheticPreUpdateNode() { this = objectCreationNode() }
34+
35+
override Node getPreUpdateNode() { result.(SyntheticPreUpdateNode).getPostUpdateNode() = this }
36+
37+
/**
38+
* A label for this kind of node. This will figure in the textual representation of the synthesized pre-update node.
39+
*
40+
* There is currently only one reason for needing a pre-update node, so we always use that as the label.
41+
*/
42+
string label() { result = "objCreate" }
43+
}
3444

35-
/** A data flow node for which we should synthesise an associated post-update node. */
36-
abstract class NeedsSyntheticPostUpdateNode extends Node {
37-
/** A label for this kind of node. This will figure in the textual representation of the synthesized post-update node. */
38-
abstract string label();
45+
/**
46+
* Calls to constructors are treated as post-update nodes for the synthesized argument
47+
* that is mapped to the `self` parameter. That way, constructor calls represent the value of the
48+
* object after the constructor (currently only `__init__`) has run.
49+
*/
50+
CfgNode objectCreationNode() { result.getNode().(CallNode) = any(ClassCall c).getNode() }
3951
}
4052

41-
/** An argument might have its value changed as a result of a call. */
42-
class ArgumentPreUpdateNode extends NeedsSyntheticPostUpdateNode, ArgumentNode {
43-
// Certain arguments, such as implicit self arguments are already post-update nodes
44-
// and should not have an extra node synthesised.
45-
ArgumentPreUpdateNode() {
46-
this = any(FunctionCall c).getArg(_)
53+
import syntheticPreUpdateNode
54+
55+
/** A module collecting the different reasons for synthesising a post-update node. */
56+
module syntheticPostUpdateNode {
57+
/** A post-update node is synthesized for all nodes which satisfy `NeedsSyntheticPostUpdateNode`. */
58+
class SyntheticPostUpdateNode extends PostUpdateNode, TSyntheticPostUpdateNode {
59+
NeedsSyntheticPostUpdateNode pre;
60+
61+
SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(pre) }
62+
63+
override Node getPreUpdateNode() { result = pre }
64+
65+
override string toString() { result = "[post " + pre.label() + "] " + pre.toString() }
66+
67+
override Scope getScope() { result = pre.getScope() }
68+
69+
override Location getLocation() { result = pre.getLocation() }
70+
}
71+
72+
/** A data flow node for which we should synthesise an associated post-update node. */
73+
class NeedsSyntheticPostUpdateNode extends Node {
74+
NeedsSyntheticPostUpdateNode() {
75+
this = argumentPreUpdateNode()
76+
or
77+
this = storePreUpdateNode()
78+
or
79+
this = readPreUpdateNode()
80+
}
81+
82+
/**
83+
* A label for this kind of node. This will figure in the textual representation of the synthesized post-update node.
84+
* We favour being an arguments as the reason for the post-update node in case multiple reasons apply.
85+
*/
86+
string label() {
87+
if this = argumentPreUpdateNode()
88+
then result = "arg"
89+
else
90+
if this = storePreUpdateNode()
91+
then result = "store"
92+
else result = "read"
93+
}
94+
}
95+
96+
/**
97+
* An argument might have its value changed as a result of a call.
98+
* Certain arguments, such as implicit self arguments are already post-update nodes
99+
* and should not have an extra node synthesised.
100+
*/
101+
ArgumentNode argumentPreUpdateNode() {
102+
result = any(FunctionCall c).getArg(_)
47103
or
48104
// Avoid argument 0 of method calls as those have read post-update nodes.
49-
exists(MethodCall c, int n | n > 0 | this = c.getArg(n))
105+
exists(MethodCall c, int n | n > 0 | result = c.getArg(n))
50106
or
51-
this = any(SpecialCall c).getArg(_)
107+
result = any(SpecialCall c).getArg(_)
52108
or
53109
// Avoid argument 0 of class calls as those have non-synthetic post-update nodes.
54-
exists(ClassCall c, int n | n > 0 | this = c.getArg(n))
110+
exists(ClassCall c, int n | n > 0 | result = c.getArg(n))
55111
}
56112

57-
override string label() { result = "arg" }
58-
}
59-
60-
/** An object might have its value changed after a store. */
61-
class StorePreUpdateNode extends NeedsSyntheticPostUpdateNode, CfgNode {
62-
StorePreUpdateNode() {
113+
/** An object might have its value changed after a store. */
114+
CfgNode storePreUpdateNode() {
63115
exists(Attribute a |
64-
node = a.getObject().getAFlowNode() and
116+
result.getNode() = a.getObject().getAFlowNode() and
65117
a.getCtx() instanceof Store
66118
)
67119
}
68120

69-
override string label() { result = "store" }
70-
}
71-
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-
*/
83-
class ReadPreUpdateNode extends NeedsSyntheticPostUpdateNode, CfgNode {
84-
ReadPreUpdateNode() {
121+
/**
122+
* A node marking the state change of an object after a read.
123+
*
124+
* A reverse read happens when the result of a read is modified, e.g. in
125+
* ```python
126+
* l = [ mutable ]
127+
* l[0].mutate()
128+
* ```
129+
* we may now have changed the content of `l`. To track this, there must be
130+
* a postupdate node for `l`.
131+
*/
132+
CfgNode readPreUpdateNode() {
85133
exists(Attribute a |
86-
node = a.getObject().getAFlowNode() and
134+
result.getNode() = a.getObject().getAFlowNode() and
87135
a.getCtx() instanceof Load
88136
)
89137
or
90-
node = any(SubscriptNode s).getObject()
138+
result.getNode() = any(SubscriptNode s).getObject()
91139
or
92-
node.getNode() = any(Call call).getKwargs()
140+
// The dictionary argument is read from if the callable has parameters matching the keys.
141+
result.getNode().getNode() = any(Call call).getKwargs()
93142
}
94-
95-
override string label() { result = "read" }
96143
}
97144

98-
/** A post-update node is synthesized for all nodes which satisfy `NeedsSyntheticPostUpdateNode`. */
99-
class SyntheticPostUpdateNode extends PostUpdateNode, TSyntheticPostUpdateNode {
100-
NeedsSyntheticPostUpdateNode pre;
101-
102-
SyntheticPostUpdateNode() { this = TSyntheticPostUpdateNode(pre) }
103-
104-
override Node getPreUpdateNode() { result = pre }
105-
106-
override string toString() { result = "[post " + pre.label() + "] " + pre.toString() }
107-
108-
override Scope getScope() { result = pre.getScope() }
109-
110-
override Location getLocation() { result = pre.getLocation() }
111-
}
112-
113-
/**
114-
* Calls to constructors are treated as post-update nodes for the synthesized argument
115-
* that is mapped to the `self` parameter. That way, constructor calls represent the value of the
116-
* object after the constructor (currently only `__init__`) has run.
117-
*/
118-
class ObjectCreationNode extends PostUpdateNode, NeedsSyntheticPreUpdateNode, CfgNode {
119-
ObjectCreationNode() { node.(CallNode) = any(ClassCall c).getNode() }
120-
121-
override Node getPreUpdateNode() { result.(SyntheticPreUpdateNode).getPostUpdateNode() = this }
122-
123-
override string label() { result = "objCreate" }
124-
}
145+
import syntheticPostUpdateNode
125146

126147
class DataFlowExpr = Expr;
127148

0 commit comments

Comments
 (0)