Skip to content

Commit adcfdf1

Browse files
Modernize multple calls to init/del
1 parent 6f9983a commit adcfdf1

File tree

4 files changed

+59
-58
lines changed

4 files changed

+59
-58
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -851,9 +851,14 @@ Class getNextClassInMroKnownStartingClass(Class cls, Class startingClass) {
851851
)
852852
}
853853

854-
private Function findFunctionAccordingToMroKnownStartingClass(
855-
Class cls, Class startingClass, string name
856-
) {
854+
/**
855+
* Gets a potential definition of the function `name` of the class `cls` according to our approximation of
856+
* MRO for the class `startingCls` (see `getNextClassInMroKnownStartingClass` for more information).
857+
*
858+
* Note: this is almost the same as `findFunctionAccordingToMro`, except we know the
859+
* `startingClass`, which can give slightly more precise results.
860+
*/
861+
Function findFunctionAccordingToMroKnownStartingClass(Class cls, Class startingClass, string name) {
857862
result = cls.getAMethod() and
858863
result.getName() = name and
859864
cls = getADirectSuperclass*(startingClass)
@@ -866,7 +871,7 @@ private Function findFunctionAccordingToMroKnownStartingClass(
866871

867872
/**
868873
* Gets a potential definition of the function `name` according to our approximation of
869-
* MRO for the class `cls` (see `getNextClassInMroKnownStartingClass` for more information).
874+
* MRO for the class `startingCls` (see `getNextClassInMroKnownStartingClass` for more information).
870875
*
871876
* Note: this is almost the same as `findFunctionAccordingToMro`, except we know the
872877
* `startingClass`, which can give slightly more precise results.

python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,32 @@ import python
44
import semmle.python.ApiGraphs
55
import semmle.python.dataflow.new.internal.DataFlowDispatch
66

7-
// Helper predicates for multiple call to __init__/__del__ queries.
8-
pragma[noinline]
9-
private predicate multiple_invocation_paths_helper(
10-
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
11-
) {
12-
i1 != i2 and
13-
i1 = top.getACallee+() and
14-
i2 = top.getACallee+() and
15-
i1.getFunction() = multi
16-
}
17-
18-
pragma[noinline]
19-
private predicate multiple_invocation_paths(
20-
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
21-
) {
22-
multiple_invocation_paths_helper(top, i1, i2, multi) and
23-
i2.getFunction() = multi
7+
predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, string name) {
8+
exists(DataFlow::MethodCallNode call1, DataFlow::MethodCallNode call2, Class cls |
9+
meth.getName() = name and
10+
meth.getScope() = cls and
11+
not call1 = call2 and
12+
calledMulti = getASuperCallTarget(cls, meth, call1) and
13+
calledMulti = getASuperCallTarget(cls, meth, call2) and
14+
nonTrivial(calledMulti)
15+
)
2416
}
2517

26-
/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */
27-
predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) {
28-
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
29-
multiple_invocation_paths(top, i1, i2, multi) and
30-
top.runtime(self.declaredAttribute(name)) and
31-
self.getASuperType().declaredAttribute(name) = multi
18+
Function getASuperCallTarget(Class mroBase, Function meth, DataFlow::MethodCallNode call) {
19+
meth = call.getScope() and
20+
getADirectSuperclass*(mroBase) = meth.getScope() and
21+
call.calls(_, meth.getName()) and
22+
exists(Function target, Class nextMroBase |
23+
(result = target or result = getASuperCallTarget(nextMroBase, target, _))
3224
|
33-
// Only called twice if called from different functions,
34-
// or if one call-site can reach the other.
35-
i1.getCall().getScope() != i2.getCall().getScope()
25+
superCall(call, _) and
26+
nextMroBase = mroBase and
27+
target =
28+
findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(meth.getScope(),
29+
mroBase), mroBase, meth.getName())
3630
or
37-
i1.getCall().strictlyReaches(i2.getCall())
31+
callsMethodOnClassWithSelf(meth, call, nextMroBase, _) and
32+
target = findFunctionAccordingToMro(nextMroBase, meth.getName())
3833
)
3934
}
4035

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Multiple calls to `__del__` during object destruction
3-
* @description A duplicated call to a super-class `__del__` method may lead to class instances not be cleaned up properly.
3+
* @description A duplicated call to a superclass `__del__` method may lead to class instances not be cleaned up properly.
44
* @kind problem
55
* @tags quality
66
* reliability
@@ -14,16 +14,17 @@
1414
import python
1515
import MethodCallOrder
1616

17-
from ClassObject self, FunctionObject multi
17+
predicate multipleCallsToSuperclassDel(Function meth, Function calledMulti) {
18+
multipleCallsToSuperclassMethod(meth, calledMulti, "__sel__")
19+
}
20+
21+
from Function meth, Function calledMulti
1822
where
19-
multiple_calls_to_superclass_method(self, multi, "__del__") and
20-
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
21-
not exists(FunctionObject better |
22-
multiple_calls_to_superclass_method(self, better, "__del__") and
23-
better.overrides(multi)
24-
) and
25-
not self.failedInference()
26-
select self,
27-
"Class " + self.getName() +
28-
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
29-
multi.descriptiveString()
23+
multipleCallsToSuperclassDel(meth, calledMulti) and
24+
// Don't alert for multiple calls to a superclass del when a subclass will do.
25+
not exists(Function subMulti |
26+
multipleCallsToSuperclassDel(meth, subMulti) and
27+
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
28+
)
29+
select meth, "This delete method calls $@ multiple times.", calledMulti,
30+
calledMulti.getQualifiedName()
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Multiple calls to `__init__` during object initialization
3-
* @description A duplicated call to a super-class `__init__` method may lead to objects of this class not being properly initialized.
3+
* @description A duplicated call to a superclass `__init__` method may lead to objects of this class not being properly initialized.
44
* @kind problem
55
* @tags quality
66
* reliability
@@ -14,17 +14,17 @@
1414
import python
1515
import MethodCallOrder
1616

17-
from ClassObject self, FunctionObject multi
17+
predicate multipleCallsToSuperclassInit(Function meth, Function calledMulti) {
18+
multipleCallsToSuperclassMethod(meth, calledMulti, "__init__")
19+
}
20+
21+
from Function meth, Function calledMulti
1822
where
19-
multi != theObjectType().lookupAttribute("__init__") and
20-
multiple_calls_to_superclass_method(self, multi, "__init__") and
21-
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
22-
not exists(FunctionObject better |
23-
multiple_calls_to_superclass_method(self, better, "__init__") and
24-
better.overrides(multi)
25-
) and
26-
not self.failedInference()
27-
select self,
28-
"Class " + self.getName() +
29-
" may not be initialized properly as $@ may be called multiple times during initialization.",
30-
multi, multi.descriptiveString()
23+
multipleCallsToSuperclassInit(meth, calledMulti) and
24+
// Don't alert for multiple calls to a superclass init when a subclass will do.
25+
not exists(Function subMulti |
26+
multipleCallsToSuperclassInit(meth, subMulti) and
27+
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
28+
)
29+
select meth, "This initializer method calls $@ multiple times.", calledMulti,
30+
calledMulti.getQualifiedName()

0 commit comments

Comments
 (0)