Skip to content

Commit a04fbc5

Browse files
Update tests
1 parent f3ce578 commit a04fbc5

File tree

4 files changed

+75
-34
lines changed

4 files changed

+75
-34
lines changed

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,21 @@ import python
1515
import semmle.python.dataflow.new.DataFlow
1616
import semmle.python.dataflow.new.internal.DataFlowDispatch
1717

18-
predicate initSelfCall(Function init, DataFlow::MethodCallNode call) {
19-
init.isInitMethod() and
20-
call.getScope() = init and
21-
exists(DataFlow::Node self, DataFlow::ParameterNode selfArg |
22-
call.calls(self, _) and
23-
selfArg.getParameter() = init.getArg(0) and
24-
DataFlow::localFlow(selfArg, self)
25-
)
26-
}
27-
2818
predicate initSelfCallOverridden(
29-
Function init, DataFlow::MethodCallNode call, Function target, Function override
19+
Function init, DataFlow::Node self, DataFlow::MethodCallNode call, Function target,
20+
Function override
3021
) {
3122
init.isInitMethod() and
3223
call.getScope() = init and
33-
exists(Class superclass, Class subclass, DataFlow::Node self, DataFlow::ParameterNode selfArg |
24+
exists(Class superclass, Class subclass, DataFlow::ParameterNode selfArg |
3425
superclass = init.getScope() and
3526
subclass = override.getScope() and
3627
subclass = getADirectSubclass+(superclass) and
3728
selfArg.getParameter() = init.getArg(0) and
3829
DataFlow::localFlow(selfArg, self) and
3930
call.calls(self, override.getName()) and
4031
target = superclass.getAMethod() and
41-
target.getName() = override.getName() and
42-
not lastUse(self)
32+
target.getName() = override.getName()
4333
)
4434
}
4535

@@ -58,10 +48,14 @@ predicate readsFromSelf(Function method) {
5848
)
5949
}
6050

61-
from Function init, DataFlow::MethodCallNode call, Function target, Function override
51+
from
52+
Function init, DataFlow::Node self, DataFlow::MethodCallNode call, Function target,
53+
Function override
6254
where
63-
initSelfCallOverridden(init, call, target, override) and
55+
initSelfCallOverridden(init, self, call, target, override) and
6456
readsFromSelf(override) and
65-
not isClassmethod(override)
57+
not isClassmethod(override) and
58+
not lastUse(self) and
59+
not target.getName().matches("\\_%")
6660
select call, "This call to $@ in an initialization method is overridden by $@.", target,
6761
target.getQualifiedName(), override, override.getQualifiedName()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
| init_calls_subclass.py:7:9:7:24 | Attribute() | Call to self.$@ in __init__ method, which is overridden by $@. | init_calls_subclass.py:10:5:10:26 | Function set_up | set_up | init_calls_subclass.py:19:5:19:26 | Function set_up | method Sub.set_up |
1+
| init_calls_subclass.py:8:13:8:28 | ControlFlowNode for Attribute() | This call to $@ in an initialization method is overridden by $@. | init_calls_subclass.py:11:9:11:30 | Function set_up | bad1.Super.set_up | init_calls_subclass.py:20:9:20:30 | Function set_up | bad1.Sub.set_up |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Classes/InitCallsSubclassMethod.ql
1+
Classes/InitCallsSubclass/InitCallsSubclassMethod.ql
Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,69 @@
11
#Superclass __init__ calls subclass method
22

3-
class Super(object):
3+
def bad1():
4+
class Super:
45

5-
def __init__(self, arg):
6-
self._state = "Not OK"
7-
self.set_up(arg)
8-
self._state = "OK"
6+
def __init__(self, arg):
7+
self._state = "Not OK"
8+
self.set_up(arg) # BAD: set_up is overriden.
9+
self._state = "OK"
910

10-
def set_up(self, arg):
11-
"Do some set up"
11+
def set_up(self, arg):
12+
"Do some set up"
1213

13-
class Sub(Super):
14+
class Sub(Super):
1415

15-
def __init__(self, arg):
16-
Super.__init__(self, arg)
17-
self.important_state = "OK"
16+
def __init__(self, arg):
17+
super().__init__(arg)
18+
self.important_state = "OK"
1819

19-
def set_up(self, arg):
20-
Super.set_up(self, arg)
21-
"Do some more set up" # Dangerous as self._state is "Not OK" and
22-
# self.important_state is uninitialized
20+
def set_up(self, arg):
21+
super().set_up(arg)
22+
"Do some more set up" # `self` is partially initialized
23+
if self.important_state == "OK":
24+
pass
25+
26+
def good2():
27+
class Super:
28+
def __init__(self, arg):
29+
self.a = arg
30+
self.postproc() # OK: Here `postproc` is called after initialisation.
31+
32+
def postproc(self):
33+
if self.a == 1:
34+
pass
35+
36+
class Sub(Super):
37+
def postproc(self):
38+
if self.a == 2:
39+
pass
40+
41+
def good3():
42+
class Super:
43+
def __init__(self, arg):
44+
self.a = arg
45+
self.set_b() # OK: Here `set_b` is used for initialisation, but does not read the partialy initialized state of `self`.
46+
self.c = 1
47+
48+
def set_b(self):
49+
self.b = 3
50+
51+
class Sub(Super):
52+
def set_b(self):
53+
self.b = 4
54+
55+
def good4():
56+
class Super:
57+
def __init__(self, arg):
58+
self.a = arg
59+
# OK: Here `_set_b` is likely an internal method (as indicated by the _ prefix).
60+
# We assume thus that regular consumers of the library will not override it, and classes that do are internal and account for `self`'s partially initialised state.
61+
self._set_b()
62+
self.c = 1
63+
64+
def _set_b(self):
65+
self.b = 3
66+
67+
class Sub(Super):
68+
def _set_b(self):
69+
self.b = self.a+1

0 commit comments

Comments
 (0)