Skip to content

Commit a2fc14a

Browse files
Update missing call to init
1 parent 271f32e commit a2fc14a

File tree

2 files changed

+86
-44
lines changed

2 files changed

+86
-44
lines changed
Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
deprecated module;
1+
/** Definitions for reasoning about multiple or missing calls to superclass methods. */
22

33
import python
4+
import semmle.python.ApiGraphs
5+
import semmle.python.dataflow.new.internal.DataFlowDispatch
46

57
// Helper predicates for multiple call to __init__/__del__ queries.
68
pragma[noinline]
@@ -36,42 +38,77 @@ predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject m
3638
)
3739
}
3840

39-
/** Holds if all attributes called `name` can be inferred to be methods. */
40-
private predicate named_attributes_not_method(ClassObject cls, string name) {
41-
cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject
41+
predicate nonTrivial(Function meth) {
42+
exists(Stmt s | s = meth.getAStmt() |
43+
not s instanceof Pass and
44+
not exists(DataFlow::Node call | call.asExpr() = s.(ExprStmt).getValue() |
45+
superCall(call, meth.getName())
46+
or
47+
callsMethodOnClassWithSelf(meth, call, _, meth.getName())
48+
)
49+
) and
50+
exists(meth.getANormalExit()) // doesn't always raise an exception
4251
}
4352

44-
/** Holds if `f` actually does something. */
45-
private predicate does_something(FunctionObject f) {
46-
f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__")
47-
or
48-
exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass)
53+
predicate superCall(DataFlow::MethodCallNode call, string name) {
54+
exists(DataFlow::Node sup |
55+
call.calls(sup, name) and
56+
sup = API::builtin("super").getACall()
57+
)
4958
}
5059

51-
/** Holds if `meth` looks like it should have a call to `name`, but does not */
52-
private predicate missing_call(FunctionObject meth, string name) {
53-
exists(CallNode call, AttrNode attr |
54-
call.getScope() = meth.getFunction() and
55-
call.getFunction() = attr and
56-
attr.getName() = name and
57-
not exists(FunctionObject f | f.getACall() = call)
60+
predicate callsSuper(Function meth) {
61+
exists(DataFlow::MethodCallNode call |
62+
call.getScope() = meth and
63+
superCall(call, meth.getName())
5864
)
5965
}
6066

61-
/** Holds if `self.name` does not call `missing`, even though it is expected to. */
62-
predicate missing_call_to_superclass_method(
63-
ClassObject self, FunctionObject top, FunctionObject missing, string name
67+
predicate callsMethodOnClassWithSelf(
68+
Function meth, DataFlow::MethodCallNode call, Class target, string name
6469
) {
65-
missing = self.getASuperType().declaredAttribute(name) and
66-
top = self.lookupAttribute(name) and
67-
/* There is no call to missing originating from top */
68-
not top.getACallee*() = missing and
69-
/* Make sure that all named 'methods' are objects that we can understand. */
70-
not exists(ClassObject sup |
71-
sup = self.getAnImproperSuperType() and
72-
named_attributes_not_method(sup, name)
73-
) and
74-
not self.isAbstract() and
75-
does_something(missing) and
76-
not missing_call(top, name)
70+
exists(DataFlow::Node callTarget, DataFlow::ParameterNode self |
71+
call.calls(callTarget, name) and
72+
self.getParameter() = meth.getArg(0) and
73+
self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and
74+
callTarget = classTracker(target)
75+
)
76+
}
77+
78+
predicate callsMethodOnUnknownClassWithSelf(Function meth, string name) {
79+
exists(DataFlow::MethodCallNode call, DataFlow::Node callTarget, DataFlow::ParameterNode self |
80+
call.calls(callTarget, name) and
81+
self.getParameter() = meth.getArg(0) and
82+
self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and
83+
not exists(Class target | callTarget = classTracker(target))
84+
)
85+
}
86+
87+
predicate mayProceedInMro(Class a, Class b, Class mroStart) {
88+
b = getNextClassInMroKnownStartingClass(a, mroStart)
89+
or
90+
exists(Class mid |
91+
mid = getNextClassInMroKnownStartingClass(a, mroStart) and
92+
mayProceedInMro(mid, b, mroStart)
93+
)
94+
}
95+
96+
predicate missingCallToSuperclassMethod(
97+
Function base, Function shouldCall, Class mroStart, string name
98+
) {
99+
base.getName() = name and
100+
shouldCall.getName() = name and
101+
not callsSuper(base) and
102+
not callsMethodOnUnknownClassWithSelf(base, name) and
103+
nonTrivial(shouldCall) and
104+
base.getScope() = getADirectSuperclass*(mroStart) and
105+
mayProceedInMro(base.getScope(), shouldCall.getScope(), mroStart) and
106+
not exists(Class called |
107+
(
108+
callsMethodOnClassWithSelf(base, _, called, name)
109+
or
110+
callsMethodOnClassWithSelf(findFunctionAccordingToMro(mroStart, name), _, called, name)
111+
) and
112+
shouldCall.getScope() = getADirectSuperclass*(called)
113+
)
77114
}
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @name Missing call to `__init__` during object initialization
2+
* @name Missing call to superclass `__init__` during object initialization
33
* @description An omitted call to a super-class `__init__` method may lead to objects of this class not being fully initialized.
44
* @kind problem
55
* @tags quality
@@ -14,16 +14,21 @@
1414
import python
1515
import MethodCallOrder
1616

17-
from ClassObject self, FunctionObject initializer, FunctionObject missing
17+
predicate missingCallToSuperclassInit(Function base, Function shouldCall, Class mroStart) {
18+
missingCallToSuperclassMethod(base, shouldCall, mroStart, "__init__")
19+
}
20+
21+
from Function base, Function shouldCall, Class mroStart, string msg
1822
where
19-
self.lookupAttribute("__init__") = initializer and
20-
missing_call_to_superclass_method(self, initializer, missing, "__init__") and
21-
// If a superclass is incorrect, don't flag this class as well.
22-
not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and
23-
not missing.neverReturns() and
24-
not self.failedInference() and
25-
not missing.isBuiltin() and
26-
not self.isAbstract()
27-
select self,
28-
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
29-
missing, missing.descriptiveString(), initializer, "__init__ method"
23+
missingCallToSuperclassInit(base, shouldCall, mroStart) and
24+
(
25+
// Simple case: the method that should be called is directly overridden
26+
mroStart = base.getScope() and
27+
msg = "This initialization method does not call $@, which may leave $@ partially initialized."
28+
or
29+
// Only alert for a different mro base if there are no alerts for direct overrides
30+
not missingCallToSuperclassInit(base, _, base.getScope()) and
31+
msg =
32+
"This initialization method does not call $@, which follows it in the MRO of $@, leaving it partially initialized."
33+
)
34+
select base, msg, shouldCall, shouldCall.getQualifiedName(), mroStart, mroStart.getName()

0 commit comments

Comments
 (0)