Skip to content

Commit 90eccbd

Browse files
authored
Merge pull request #836 from markshannon/python-mutating-descriptor
Python: Fix up mutating-descriptor query
2 parents 566eafc + 7fe3c3d commit 90eccbd

File tree

4 files changed

+25
-7
lines changed

4 files changed

+25
-7
lines changed

change-notes/1.20/analysis-python.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Removes false positives seen when using Python 3.6, but not when using earlier v
2424
| **Query** | **Expected impact** | **Change** |
2525
|----------------------------|------------------------|------------------------------------------------------------------|
2626
| Comparison using is when operands support \_\_eq\_\_ (`py/comparison-using-is`) | Fewer false positive results | Results where one of the objects being compared is an enum member are no longer reported. |
27+
| Mutation of descriptor in \_\_get\_\_ or \_\_set\_\_ method (`py/mutable-descriptor`) | Fewer false positive results | Results where the mutation does not occur when calling one of the `__get__`, `__set__` or `__delete__` methods are no longer reported. |
2728
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a `doctest` string are no longer reported. |
2829
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a type-hint comment are no longer reported. |
2930

python/ql/src/Classes/MutatingDescriptor.ql

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ import python
1414

1515
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
1616
cls.isDescriptorType() and
17-
exists(PyFunctionObject f |
18-
cls.lookupAttribute(_) = f and
17+
exists(PyFunctionObject f, PyFunctionObject get_set |
18+
exists(string name |
19+
cls.lookupAttribute(name) = get_set |
20+
name = "__get__" or name = "__set__" or name = "__delete__"
21+
) and
22+
cls.lookupAttribute(_) = f and
23+
get_set.getACallee*() = f and
1924
not f.getName() = "__init__" and
2025
s.getScope() = f.getFunction()
2126
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
| test.py:10:9:10:19 | Attribute | Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties. | test.py:3:1:3:33 | class MutatingDescriptor | MutatingDescriptor |
2+
| test.py:25:9:25:19 | Attribute | Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties. | test.py:3:1:3:33 | class MutatingDescriptor | MutatingDescriptor |
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11

22
#This is prone to strange side effects and race conditions.
33
class MutatingDescriptor(object):
4-
4+
55
def __init__(self, func):
66
self.my_func = func
7-
7+
88
def __get__(self, obj, obj_type):
9-
#Modified state is visible to all instances of C that might call "show".
9+
#Modified state is visible to all instances.
1010
self.my_obj = obj
1111
return self
12-
12+
1313
def __call__(self, *args):
14-
return self.my_func(self.my_obj, *args)
14+
return self.my_func(self.my_obj, *args)
15+
16+
#Not call from __get__, __set__ or __delete__
17+
def ok(self, func):
18+
self.my_func = func
19+
20+
def __set__(self, obj, value):
21+
self.not_ok(value)
22+
23+
def not_ok(self, value):
24+
#Modified state is visible to all instances.
25+
self.my_obj = value

0 commit comments

Comments
 (0)