Modernize multple calls to init/del

This commit is contained in:
Joe Farebrother
2025-07-01 11:25:06 +01:00
parent a02016a95f
commit d0daacd17e
4 changed files with 59 additions and 58 deletions

View File

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

View File

@@ -4,37 +4,32 @@ import python
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.internal.DataFlowDispatch
// Helper predicates for multiple call to __init__/__del__ queries.
pragma[noinline]
private predicate multiple_invocation_paths_helper(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
) {
i1 != i2 and
i1 = top.getACallee+() and
i2 = top.getACallee+() and
i1.getFunction() = multi
predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, string name) {
exists(DataFlow::MethodCallNode call1, DataFlow::MethodCallNode call2, Class cls |
meth.getName() = name and
meth.getScope() = cls and
not call1 = call2 and
calledMulti = getASuperCallTarget(cls, meth, call1) and
calledMulti = getASuperCallTarget(cls, meth, call2) and
nonTrivial(calledMulti)
)
}
pragma[noinline]
private predicate multiple_invocation_paths(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
) {
multiple_invocation_paths_helper(top, i1, i2, multi) and
i2.getFunction() = multi
}
/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */
predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) {
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
multiple_invocation_paths(top, i1, i2, multi) and
top.runtime(self.declaredAttribute(name)) and
self.getASuperType().declaredAttribute(name) = multi
Function getASuperCallTarget(Class mroBase, Function meth, DataFlow::MethodCallNode call) {
meth = call.getScope() and
getADirectSuperclass*(mroBase) = meth.getScope() and
call.calls(_, meth.getName()) and
exists(Function target, Class nextMroBase |
(result = target or result = getASuperCallTarget(nextMroBase, target, _))
|
// Only called twice if called from different functions,
// or if one call-site can reach the other.
i1.getCall().getScope() != i2.getCall().getScope()
superCall(call, _) and
nextMroBase = mroBase and
target =
findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(meth.getScope(),
mroBase), mroBase, meth.getName())
or
i1.getCall().strictlyReaches(i2.getCall())
callsMethodOnClassWithSelf(meth, call, nextMroBase, _) and
target = findFunctionAccordingToMro(nextMroBase, meth.getName())
)
}

View File

@@ -1,6 +1,6 @@
/**
* @name Multiple calls to `__del__` during object destruction
* @description A duplicated call to a super-class `__del__` method may lead to class instances not be cleaned up properly.
* @description A duplicated call to a superclass `__del__` method may lead to class instances not be cleaned up properly.
* @kind problem
* @tags quality
* reliability
@@ -14,16 +14,17 @@
import python
import MethodCallOrder
from ClassObject self, FunctionObject multi
predicate multipleCallsToSuperclassDel(Function meth, Function calledMulti) {
multipleCallsToSuperclassMethod(meth, calledMulti, "__sel__")
}
from Function meth, Function calledMulti
where
multiple_calls_to_superclass_method(self, multi, "__del__") and
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
not exists(FunctionObject better |
multiple_calls_to_superclass_method(self, better, "__del__") and
better.overrides(multi)
) and
not self.failedInference()
select self,
"Class " + self.getName() +
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
multi.descriptiveString()
multipleCallsToSuperclassDel(meth, calledMulti) and
// Don't alert for multiple calls to a superclass del when a subclass will do.
not exists(Function subMulti |
multipleCallsToSuperclassDel(meth, subMulti) and
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
)
select meth, "This delete method calls $@ multiple times.", calledMulti,
calledMulti.getQualifiedName()

View File

@@ -1,6 +1,6 @@
/**
* @name Multiple calls to `__init__` during object initialization
* @description A duplicated call to a super-class `__init__` method may lead to objects of this class not being properly initialized.
* @description A duplicated call to a superclass `__init__` method may lead to objects of this class not being properly initialized.
* @kind problem
* @tags quality
* reliability
@@ -14,17 +14,17 @@
import python
import MethodCallOrder
from ClassObject self, FunctionObject multi
predicate multipleCallsToSuperclassInit(Function meth, Function calledMulti) {
multipleCallsToSuperclassMethod(meth, calledMulti, "__init__")
}
from Function meth, Function calledMulti
where
multi != theObjectType().lookupAttribute("__init__") and
multiple_calls_to_superclass_method(self, multi, "__init__") and
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
not exists(FunctionObject better |
multiple_calls_to_superclass_method(self, better, "__init__") and
better.overrides(multi)
) and
not self.failedInference()
select self,
"Class " + self.getName() +
" may not be initialized properly as $@ may be called multiple times during initialization.",
multi, multi.descriptiveString()
multipleCallsToSuperclassInit(meth, calledMulti) and
// Don't alert for multiple calls to a superclass init when a subclass will do.
not exists(Function subMulti |
multipleCallsToSuperclassInit(meth, subMulti) and
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
)
select meth, "This initializer method calls $@ multiple times.", calledMulti,
calledMulti.getQualifiedName()