Skip to content

Commit 0258f79

Browse files
authored
Merge pull request #1591 from markshannon/python-fix-property-setter-handling
Python: fix property setter handling in points-to.
2 parents 143016e + 59ea825 commit 0258f79

File tree

7 files changed

+182
-9
lines changed

7 files changed

+182
-9
lines changed

python/ql/src/semmle/python/objects/Descriptors.qll

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class PropertyInternal extends ObjectInternal, TProperty {
3232
private Context getContext() { this = TProperty(_,result, _) }
3333

3434
override string toString() {
35-
result = "property" + this.getName()
35+
result = "property " + this.getName()
3636
}
3737

3838
override boolean booleanValue() { result = true }
@@ -63,16 +63,19 @@ class PropertyInternal extends ObjectInternal, TProperty {
6363

6464
override predicate calleeAndOffset(Function scope, int paramOffset) { none() }
6565

66-
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
66+
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) {
67+
value = TPropertySetterOrDeleter(this, name) and origin = CfgOrigin::unknown()
68+
}
6769

68-
pragma [noinline] override predicate attributesUnknown() { none() }
70+
override predicate attributesUnknown() { none() }
6971

7072
override predicate subscriptUnknown() { none() }
7173

7274
override boolean isDescriptor() { result = true }
7375

7476
override int length() { none() }
75-
pragma [noinline] override predicate binds(ObjectInternal cls, string name, ObjectInternal descriptor) { none() }
77+
78+
override predicate binds(ObjectInternal cls, string name, ObjectInternal descriptor) { none() }
7679

7780
pragma [noinline] override predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin) {
7881
any(ObjectInternal obj).binds(cls, _, this) and
@@ -100,6 +103,80 @@ class PropertyInternal extends ObjectInternal, TProperty {
100103

101104
}
102105

106+
private class PropertySetterOrDeleter extends ObjectInternal, TPropertySetterOrDeleter {
107+
108+
override string toString() {
109+
result = this.getProperty().toString() + "." + this.getName()
110+
}
111+
112+
override string getName() {
113+
this = TPropertySetterOrDeleter(_, result)
114+
}
115+
116+
PropertyInternal getProperty() {
117+
this = TPropertySetterOrDeleter(result, _)
118+
}
119+
120+
override predicate callResult(ObjectInternal obj, CfgOrigin origin) {
121+
exists(ControlFlowNode call |
122+
obj = this.getProperty() and obj = TProperty(call, _, _) and
123+
origin = CfgOrigin::fromCfgNode(call)
124+
)
125+
}
126+
127+
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
128+
none()
129+
}
130+
131+
override ClassDecl getClassDeclaration() { none() }
132+
133+
override boolean isClass() { result = false }
134+
135+
override ObjectInternal getClass() {
136+
result = TBuiltinClassObject(Builtin::special("MethodType"))
137+
}
138+
139+
override predicate notTestableForEquality() { none() }
140+
141+
override Builtin getBuiltin() { none() }
142+
143+
override ControlFlowNode getOrigin() { none() }
144+
145+
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) { none() }
146+
147+
override int intValue() { none() }
148+
149+
override string strValue() { none() }
150+
151+
override boolean booleanValue() { result = true }
152+
153+
override predicate calleeAndOffset(Function scope, int paramOffset) { none() }
154+
155+
override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
156+
157+
override predicate attributesUnknown() { none() }
158+
159+
override predicate subscriptUnknown() { none() }
160+
161+
override boolean isDescriptor() { result = true }
162+
163+
override int length() { none() }
164+
165+
override predicate binds(ObjectInternal cls, string name, ObjectInternal descriptor) { none() }
166+
167+
override predicate contextSensitiveCallee() { none() }
168+
169+
override ObjectInternal getIterNext() { none() }
170+
171+
override predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin) { none() }
172+
173+
override predicate descriptorGetInstance(ObjectInternal instance, ObjectInternal value, CfgOrigin origin) { none() }
174+
175+
override predicate useOriginAsLegacyObject() { none() }
176+
177+
}
178+
179+
103180
/** A class representing classmethods in Python */
104181
class ClassMethodObjectInternal extends ObjectInternal, TClassMethod {
105182

@@ -143,9 +220,9 @@ class ClassMethodObjectInternal extends ObjectInternal, TClassMethod {
143220

144221
override predicate calleeAndOffset(Function scope, int paramOffset) { none() }
145222

146-
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
223+
override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
147224

148-
pragma [noinline] override predicate attributesUnknown() { none() }
225+
override predicate attributesUnknown() { none() }
149226

150227
override predicate subscriptUnknown() { none() }
151228

@@ -235,9 +312,9 @@ class StaticMethodObjectInternal extends ObjectInternal, TStaticMethod {
235312
this.getFunction().calleeAndOffset(scope, paramOffset)
236313
}
237314

238-
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
315+
override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) { none() }
239316

240-
pragma [noinline] override predicate attributesUnknown() { none() }
317+
override predicate attributesUnknown() { none() }
241318

242319
override predicate subscriptUnknown() { none() }
243320

@@ -253,7 +330,7 @@ class StaticMethodObjectInternal extends ObjectInternal, TStaticMethod {
253330
value = this.getFunction() and origin = CfgOrigin::unknown()
254331
}
255332

256-
pragma [noinline] override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
333+
override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
257334

258335
override int length() { none() }
259336

python/ql/src/semmle/python/objects/TObject.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ cached newtype TObject =
194194
PointsToInternal::pointsTo(call.getArg(0), ctx, getter, _)
195195
}
196196
or
197+
/* Represents the `setter` or `deleter` method of a property object. */
198+
TPropertySetterOrDeleter(PropertyInternal property, string method) {
199+
exists(AttrNode attr |
200+
PointsToInternal::pointsTo(attr.getObject(method), _, property, _)
201+
) and
202+
( method = "setter" or method = "deleter" )
203+
}
204+
or
197205
/* Represents a dynamically created class */
198206
TDynamicClass(CallNode instantiation, ClassObjectInternal metacls, PointsToContext context) {
199207
PointsToInternal::pointsTo(instantiation.getFunction(), context, metacls, _) and
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| class C | p | property p |
2+
| class D | __init__ | Function D.__init__ |
3+
| class D | q | property q |
4+
| class E | __init__ | Function D.__init__ |
5+
| class E | p | property p |
6+
| class E | q | property q |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import python
3+
import semmle.python.pointsto.PointsTo
4+
import semmle.python.objects.ObjectInternal
5+
6+
from ClassObjectInternal cls, string name, ObjectInternal f
7+
where cls.lookup(name, f, _) and exists(f.getOrigin())
8+
select cls.toString(), name, f.toString()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
| test.py:3:1:3:16 | test.py:3 | ControlFlowNode for ClassExpr | import | class C | builtin-class type |
2+
| test.py:3:9:3:14 | test.py:3 | ControlFlowNode for object | import | builtin-class object | builtin-class type |
3+
| test.py:5:6:5:13 | test.py:5 | ControlFlowNode for property | import | builtin-class property | builtin-class type |
4+
| test.py:5:6:5:13 | test.py:5 | ControlFlowNode for property() | import | property p | builtin-class property |
5+
| test.py:6:5:6:16 | test.py:6 | ControlFlowNode for FunctionExpr | import | Function C.p | builtin-class function |
6+
| test.py:7:16:7:16 | test.py:7 | ControlFlowNode for IntegerLiteral | runtime | int 0 | builtin-class int |
7+
| test.py:9:1:9:16 | test.py:9 | ControlFlowNode for ClassExpr | import | class D | builtin-class type |
8+
| test.py:9:9:9:14 | test.py:9 | ControlFlowNode for object | import | builtin-class object | builtin-class type |
9+
| test.py:11:5:11:23 | test.py:11 | ControlFlowNode for FunctionExpr | import | Function D.__init__ | builtin-class function |
10+
| test.py:12:9:12:12 | test.py:12 | ControlFlowNode for self | runtime | self instance of D | class D |
11+
| test.py:12:18:12:18 | test.py:12 | ControlFlowNode for IntegerLiteral | runtime | int 0 | builtin-class int |
12+
| test.py:14:6:14:13 | test.py:14 | ControlFlowNode for property | import | builtin-class property | builtin-class type |
13+
| test.py:14:6:14:13 | test.py:14 | ControlFlowNode for property() | import | property q | builtin-class property |
14+
| test.py:15:5:15:16 | test.py:15 | ControlFlowNode for FunctionExpr | import | Function D.q | builtin-class function |
15+
| test.py:16:16:16:19 | test.py:16 | ControlFlowNode for self | runtime | self instance of D | class D |
16+
| test.py:18:6:18:6 | test.py:18 | ControlFlowNode for q | import | property q | builtin-class property |
17+
| test.py:18:6:18:13 | test.py:18 | ControlFlowNode for Attribute | import | property q.setter | builtin-class method |
18+
| test.py:18:6:18:13 | test.py:18 | ControlFlowNode for Attribute() | import | property q | builtin-class property |
19+
| test.py:19:5:19:23 | test.py:19 | ControlFlowNode for FunctionExpr | import | Function D.q | builtin-class function |
20+
| test.py:20:9:20:12 | test.py:20 | ControlFlowNode for self | runtime | self instance of D | class D |
21+
| test.py:22:1:22:14 | test.py:22 | ControlFlowNode for ClassExpr | import | class E | builtin-class type |
22+
| test.py:22:9:22:9 | test.py:22 | ControlFlowNode for C | import | class C | builtin-class type |
23+
| test.py:22:12:22:12 | test.py:22 | ControlFlowNode for D | import | class D | builtin-class type |
24+
| test.py:25:1:25:1 | test.py:25 | ControlFlowNode for C | import | class C | builtin-class type |
25+
| test.py:25:1:25:3 | test.py:25 | ControlFlowNode for Attribute | import | property p | builtin-class property |
26+
| test.py:26:1:26:1 | test.py:26 | ControlFlowNode for D | import | class D | builtin-class type |
27+
| test.py:26:1:26:3 | test.py:26 | ControlFlowNode for Attribute | import | property q | builtin-class property |
28+
| test.py:27:1:27:1 | test.py:27 | ControlFlowNode for E | import | class E | builtin-class type |
29+
| test.py:27:1:27:3 | test.py:27 | ControlFlowNode for Attribute | import | property p | builtin-class property |
30+
| test.py:28:1:28:1 | test.py:28 | ControlFlowNode for E | import | class E | builtin-class type |
31+
| test.py:28:1:28:3 | test.py:28 | ControlFlowNode for Attribute | import | property q | builtin-class property |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
import python
3+
import semmle.python.objects.ObjectInternal
4+
5+
string vrepr(Value v) {
6+
/* Work around differing names in 2/3 */
7+
not v = ObjectInternal::boundMethod() and result = v.toString()
8+
or
9+
v = ObjectInternal::boundMethod() and result = "builtin-class method"
10+
}
11+
12+
from ControlFlowNode f, Context ctx, Value v, ControlFlowNode origin
13+
where
14+
f.pointsTo(ctx, v, origin)
15+
select f.getLocation(), f.toString(), ctx, vrepr(v), vrepr(v.getClass())
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
3+
class C(object):
4+
5+
@property
6+
def p(self):
7+
return 0
8+
9+
class D(object):
10+
11+
def __init__(self):
12+
self.v = 0
13+
14+
@property
15+
def q(self):
16+
return self.v
17+
18+
@q.setter
19+
def q(self, value):
20+
self.v = value
21+
22+
class E(C, D):
23+
pass
24+
25+
C.p
26+
D.q
27+
E.p
28+
E.q

0 commit comments

Comments
 (0)