Skip to content

Commit e7322d1

Browse files
authored
Merge pull request #4077 from yoff/MagicMethods
Python: Add support for magic methods
2 parents d3175a7 + 09025c2 commit e7322d1

File tree

8 files changed

+1342
-800
lines changed

8 files changed

+1342
-800
lines changed

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

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import python
22
private import DataFlowPublic
3+
import semmle.python.SpecialMethods
34

45
//--------
56
// Data flow graph
@@ -164,17 +165,67 @@ class DataFlowClassValue extends DataFlowCallable, TClassValue {
164165
override string getName() { result = c.getName() }
165166
}
166167

167-
/** Represents a call to a callable */
168-
class DataFlowCall extends CallNode {
169-
DataFlowCallable callable;
168+
newtype TDataFlowCall =
169+
TCallNode(CallNode call) or
170+
TSpecialCall(SpecialMethodCallNode special)
170171

171-
DataFlowCall() { this = callable.getACall() }
172+
abstract class DataFlowCall extends TDataFlowCall {
173+
/** Gets a textual representation of this element. */
174+
abstract string toString();
172175

173176
/** Get the callable to which this call goes. */
174-
DataFlowCallable getCallable() { result = callable }
177+
abstract DataFlowCallable getCallable();
178+
179+
/** Get the specified argument to this call. */
180+
abstract ControlFlowNode getArg(int n);
181+
182+
/** Get the control flow node representing this call. */
183+
abstract ControlFlowNode getNode();
175184

176185
/** Gets the enclosing callable of this call. */
177-
DataFlowCallable getEnclosingCallable() { result.getScope() = this.getNode().getScope() }
186+
abstract DataFlowCallable getEnclosingCallable();
187+
}
188+
189+
/** Represents a call to a callable. */
190+
class CallNodeCall extends DataFlowCall, TCallNode {
191+
CallNode call;
192+
DataFlowCallable callable;
193+
194+
CallNodeCall() {
195+
this = TCallNode(call) and
196+
call = callable.getACall()
197+
}
198+
199+
override string toString() { result = call.toString() }
200+
201+
override ControlFlowNode getArg(int n) { result = call.getArg(n) }
202+
203+
override ControlFlowNode getNode() { result = call }
204+
205+
override DataFlowCallable getCallable() { result = callable }
206+
207+
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() }
208+
}
209+
210+
/** Represents a call to a special method. */
211+
class SpecialCall extends DataFlowCall, TSpecialCall {
212+
SpecialMethodCallNode special;
213+
214+
SpecialCall() { this = TSpecialCall(special) }
215+
216+
override string toString() { result = special.toString() }
217+
218+
override ControlFlowNode getArg(int n) { result = special.(SpecialMethod::Potential).getArg(n) }
219+
220+
override ControlFlowNode getNode() { result = special }
221+
222+
override DataFlowCallable getCallable() {
223+
result = TCallableValue(special.getResolvedSpecialMethod())
224+
}
225+
226+
override DataFlowCallable getEnclosingCallable() {
227+
result.getScope() = special.getNode().getScope()
228+
}
178229
}
179230

180231
/** A data flow node that represents a call argument. */
@@ -227,7 +278,7 @@ class OutNode extends CfgNode {
227278
* `kind`.
228279
*/
229280
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
230-
call = result.getNode() and
281+
call.getNode() = result.getNode() and
231282
kind = TNormalReturnKind()
232283
}
233284

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Provides support for special methods.
3+
* This is done in two steps:
4+
* - A subset of `ControlFlowNode`s are labelled as potentially corresponding to
5+
* a special method call (by being an instance of `SpecialMethod::Potential`).
6+
* - A subset of the potential special method calls are labelled as being actual
7+
* special method calls (`SpecialMethodCallNode`) if the appropriate method is defined.
8+
* Extend `SpecialMethod::Potential` to capture more cases.
9+
*/
10+
11+
import python
12+
13+
/** A control flow node which might correspond to a special method call. */
14+
class PotentialSpecialMethodCallNode extends ControlFlowNode {
15+
PotentialSpecialMethodCallNode() { this instanceof SpecialMethod::Potential }
16+
}
17+
18+
/**
19+
* Machinery for detecting special method calls.
20+
* Extend `SpecialMethod::Potential` to capture more cases.
21+
*/
22+
module SpecialMethod {
23+
/** A control flow node which might correspond to a special method call. */
24+
abstract class Potential extends ControlFlowNode {
25+
/** Gets the name of the method that would be called. */
26+
abstract string getSpecialMethodName();
27+
28+
/** Gets the control flow node that would be passed as the specified argument. */
29+
abstract ControlFlowNode getArg(int n);
30+
31+
/**
32+
* Gets the control flow node corresponding to the instance
33+
* that would define the special method.
34+
*/
35+
ControlFlowNode getSelf() { result = this.getArg(0) }
36+
}
37+
38+
/** A binary expression node that might correspond to a special method call. */
39+
class SpecialBinOp extends Potential, BinaryExprNode {
40+
Operator operator;
41+
42+
SpecialBinOp() { this.getOp() = operator }
43+
44+
override string getSpecialMethodName() { result = operator.getSpecialMethodName() }
45+
46+
override ControlFlowNode getArg(int n) {
47+
n = 0 and result = this.getLeft()
48+
or
49+
n = 1 and result = this.getRight()
50+
}
51+
}
52+
53+
/** A subscript expression node that might correspond to a special method call. */
54+
abstract class SpecialSubscript extends Potential, SubscriptNode {
55+
override ControlFlowNode getArg(int n) {
56+
n = 0 and result = this.getObject()
57+
or
58+
n = 1 and result = this.getIndex()
59+
}
60+
}
61+
62+
/** A subscript expression node that might correspond to a call to __getitem__. */
63+
class SpecialGetItem extends SpecialSubscript {
64+
SpecialGetItem() { this.isLoad() }
65+
66+
override string getSpecialMethodName() { result = "__getitem__" }
67+
}
68+
69+
/** A subscript expression node that might correspond to a call to __setitem__. */
70+
class SpecialSetItem extends SpecialSubscript {
71+
SpecialSetItem() { this.isStore() }
72+
73+
override string getSpecialMethodName() { result = "__setitem__" }
74+
75+
override ControlFlowNode getArg(int n) {
76+
n = 0 and result = this.getObject()
77+
or
78+
n = 1 and result = this.getIndex()
79+
or
80+
n = 2 and result = this.getValueNode()
81+
}
82+
83+
private ControlFlowNode getValueNode() {
84+
exists(AssignStmt a |
85+
a.getATarget() = this.getNode() and
86+
result.getNode() = a.getValue()
87+
)
88+
or
89+
exists(AugAssign a |
90+
a.getTarget() = this.getNode() and
91+
result.getNode() = a.getValue()
92+
)
93+
}
94+
}
95+
96+
/** A subscript expression node that might correspond to a call to __delitem__. */
97+
class SpecialDelItem extends SpecialSubscript {
98+
SpecialDelItem() { this.isDelete() }
99+
100+
override string getSpecialMethodName() { result = "__delitem__" }
101+
}
102+
}
103+
104+
/** A control flow node corresponding to a special method call. */
105+
class SpecialMethodCallNode extends PotentialSpecialMethodCallNode {
106+
Value resolvedSpecialMethod;
107+
108+
SpecialMethodCallNode() {
109+
exists(SpecialMethod::Potential pot |
110+
this.(SpecialMethod::Potential) = pot and
111+
pot.getSelf().pointsTo().getClass().lookup(pot.getSpecialMethodName()) = resolvedSpecialMethod
112+
)
113+
}
114+
115+
/** The method that is called. */
116+
Value getResolvedSpecialMethod() { result = resolvedSpecialMethod }
117+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| classes.py:620:5:620:16 | SSA variable with_getitem | classes.py:614:15:614:18 | ControlFlowNode for self |
2+
| classes.py:637:5:637:16 | SSA variable with_setitem | classes.py:632:15:632:18 | ControlFlowNode for self |
3+
| classes.py:654:5:654:16 | SSA variable with_delitem | classes.py:649:15:649:18 | ControlFlowNode for self |
4+
| classes.py:735:5:735:12 | SSA variable with_add | classes.py:729:15:729:18 | ControlFlowNode for self |
5+
| classes.py:752:5:752:12 | SSA variable with_sub | classes.py:746:15:746:18 | ControlFlowNode for self |
6+
| classes.py:769:5:769:12 | SSA variable with_mul | classes.py:763:15:763:18 | ControlFlowNode for self |
7+
| classes.py:786:5:786:15 | SSA variable with_matmul | classes.py:780:15:780:18 | ControlFlowNode for self |
8+
| classes.py:803:5:803:16 | SSA variable with_truediv | classes.py:797:15:797:18 | ControlFlowNode for self |
9+
| classes.py:820:5:820:17 | SSA variable with_floordiv | classes.py:814:15:814:18 | ControlFlowNode for self |
10+
| classes.py:837:5:837:12 | SSA variable with_mod | classes.py:831:15:831:18 | ControlFlowNode for self |
11+
| classes.py:877:5:877:12 | SSA variable with_pow | classes.py:865:15:865:18 | ControlFlowNode for self |
12+
| classes.py:894:5:894:15 | SSA variable with_lshift | classes.py:888:15:888:18 | ControlFlowNode for self |
13+
| classes.py:911:5:911:15 | SSA variable with_rshift | classes.py:905:15:905:18 | ControlFlowNode for self |
14+
| classes.py:928:5:928:12 | SSA variable with_and | classes.py:922:15:922:18 | ControlFlowNode for self |
15+
| classes.py:945:5:945:12 | SSA variable with_xor | classes.py:939:15:939:18 | ControlFlowNode for self |
16+
| classes.py:962:5:962:11 | SSA variable with_or | classes.py:956:15:956:18 | ControlFlowNode for self |

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ class ArgumentRoutingConfig extends DataFlow::Configuration {
77
ArgumentRoutingConfig() { this = "ArgumentRoutingConfig" }
88

99
override predicate isSource(DataFlow::Node node) {
10-
exists(AssignmentDefinition def |
10+
exists(AssignmentDefinition def, DataFlow::DataFlowCall call |
1111
def.getVariable() = node.(DataFlow::EssaNode).getVar() and
12-
def.getValue().(DataFlow::DataFlowCall).getCallable().getName().matches("With\\_%")
12+
def.getValue() = call.getNode() and
13+
call.getCallable().getName().matches("With\\_%")
1314
) and
1415
node.(DataFlow::EssaNode).getVar().getName().matches("with\\_%")
1516
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| classes.py:622:18:622:21 | ControlFlowNode for arg2 | classes.py:613:15:613:17 | ControlFlowNode for key |
2+
| classes.py:640:18:640:21 | ControlFlowNode for arg2 | classes.py:631:15:631:17 | ControlFlowNode for key |
3+
| classes.py:656:22:656:25 | ControlFlowNode for arg2 | classes.py:648:15:648:17 | ControlFlowNode for key |
4+
| classes.py:737:16:737:19 | ControlFlowNode for arg2 | classes.py:728:15:728:19 | ControlFlowNode for other |
5+
| classes.py:754:16:754:19 | ControlFlowNode for arg2 | classes.py:745:15:745:19 | ControlFlowNode for other |
6+
| classes.py:771:16:771:19 | ControlFlowNode for arg2 | classes.py:762:15:762:19 | ControlFlowNode for other |
7+
| classes.py:788:19:788:22 | ControlFlowNode for arg2 | classes.py:779:15:779:19 | ControlFlowNode for other |
8+
| classes.py:805:20:805:23 | ControlFlowNode for arg2 | classes.py:796:15:796:19 | ControlFlowNode for other |
9+
| classes.py:822:22:822:25 | ControlFlowNode for arg2 | classes.py:813:15:813:19 | ControlFlowNode for other |
10+
| classes.py:839:16:839:19 | ControlFlowNode for arg2 | classes.py:830:15:830:19 | ControlFlowNode for other |
11+
| classes.py:879:17:879:20 | ControlFlowNode for arg2 | classes.py:864:15:864:19 | ControlFlowNode for other |
12+
| classes.py:896:20:896:23 | ControlFlowNode for arg2 | classes.py:887:15:887:19 | ControlFlowNode for other |
13+
| classes.py:913:20:913:23 | ControlFlowNode for arg2 | classes.py:904:15:904:19 | ControlFlowNode for other |
14+
| classes.py:930:16:930:19 | ControlFlowNode for arg2 | classes.py:921:15:921:19 | ControlFlowNode for other |
15+
| classes.py:947:16:947:19 | ControlFlowNode for arg2 | classes.py:938:15:938:19 | ControlFlowNode for other |
16+
| classes.py:964:15:964:18 | ControlFlowNode for arg2 | classes.py:955:15:955:19 | ControlFlowNode for other |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| classes.py:640:26:640:29 | ControlFlowNode for arg3 | classes.py:630:15:630:19 | ControlFlowNode for value |

0 commit comments

Comments
 (0)