Merge pull request #19932 from joefarebrother/python-qual-init-del-calls

Python: Modernize 4 queries for missing/multiple calls to init/del methods
This commit is contained in:
Joe Farebrother
2025-09-08 09:29:38 +01:00
committed by GitHub
49 changed files with 956 additions and 608 deletions

View File

@@ -1,3 +1,7 @@
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToDel.ql
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToInit.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrHash.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrNotEquals.ql
ql/python/ql/src/Classes/Comparisons/IncompleteOrdering.ql
@@ -5,12 +9,8 @@ ql/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
ql/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
ql/python/ql/src/Classes/InconsistentMRO.ql
ql/python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql
ql/python/ql/src/Classes/MissingCallToDel.ql
ql/python/ql/src/Classes/MissingCallToInit.ql
ql/python/ql/src/Classes/MutatingDescriptor.ql
ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql
ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql
ql/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql
ql/python/ql/src/Exceptions/CatchingBaseException.ql

View File

@@ -1,3 +1,7 @@
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToDel.ql
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToInit.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrHash.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrNotEquals.ql
ql/python/ql/src/Classes/Comparisons/IncompleteOrdering.ql
@@ -5,12 +9,8 @@ ql/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
ql/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
ql/python/ql/src/Classes/InconsistentMRO.ql
ql/python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql
ql/python/ql/src/Classes/MissingCallToDel.ql
ql/python/ql/src/Classes/MissingCallToInit.ql
ql/python/ql/src/Classes/MutatingDescriptor.ql
ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql
ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql
ql/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql
ql/python/ql/src/Exceptions/CatchingBaseException.ql

View File

@@ -1,3 +1,7 @@
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToDel.ql
ql/python/ql/src/Classes/CallsToInitDel/MissingCallToInit.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrHash.ql
ql/python/ql/src/Classes/Comparisons/EqualsOrNotEquals.ql
ql/python/ql/src/Classes/Comparisons/IncompleteOrdering.ql
@@ -5,16 +9,12 @@ ql/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
ql/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
ql/python/ql/src/Classes/InconsistentMRO.ql
ql/python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql
ql/python/ql/src/Classes/MissingCallToDel.ql
ql/python/ql/src/Classes/MissingCallToInit.ql
ql/python/ql/src/Classes/MutatingDescriptor.ql
ql/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql
ql/python/ql/src/Classes/PropertyInOldStyleClass.ql
ql/python/ql/src/Classes/SlotsInOldStyleClass.ql
ql/python/ql/src/Classes/SubclassShadowing/SubclassShadowing.ql
ql/python/ql/src/Classes/SuperInOldStyleClass.ql
ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql
ql/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql
ql/python/ql/src/Diagnostics/ExtractedFiles.ql

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

@@ -0,0 +1,207 @@
/** Definitions for reasoning about multiple or missing calls to superclass methods. */
import python
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.internal.DataFlowDispatch
import codeql.util.Option
/** Holds if `meth` is a method named `name` that transitively calls `calledMulti` of the same name via the calls `call1` and `call2`. */
predicate multipleCallsToSuperclassMethod(
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
DataFlow::MethodCallNode call2, string name
) {
exists(Class cls |
meth.getName() = name and
meth.getScope() = cls and
locationBefore(call1.getLocation(), call2.getLocation()) and
rankedSuperCallByLocation(1, cls, meth, call1, name, calledMulti) and
rankedSuperCallByLocation(2, cls, meth, call2, name, calledMulti) and
nonTrivial(calledMulti)
)
}
predicate rankedSuperCallByLocation(
int i, Class mroBase, Function meth, DataFlow::MethodCallNode call, string name, Function target
) {
call =
rank[i](DataFlow::MethodCallNode calli |
target = getASuperCallTargetFromCall(mroBase, meth, calli, name)
|
calli order by calli.getLocation().getStartLine(), calli.getLocation().getStartColumn()
)
}
/** Holds if l1 comes before l2, assuming they're in the same file. */
pragma[inline]
private predicate locationBefore(Location l1, Location l2) {
l1.getStartLine() < l2.getStartLine()
or
l1.getStartLine() = l2.getStartLine() and
l1.getStartColumn() < l2.getStartColumn()
}
/** Gets a method transitively called by `meth` named `name` with `call` that it overrides, with `mroBase` as the type determining the MRO to search. */
Function getASuperCallTargetFromCall(
Class mroBase, Function meth, DataFlow::MethodCallNode call, string name
) {
exists(Function target | target = getDirectSuperCallTargetFromCall(mroBase, meth, call, name) |
result = target
or
result = getASuperCallTargetFromCall(mroBase, target, _, name)
)
}
/** Gets the method called by `meth` named `name` with `call`, with `mroBase` as the type determining the MRO to search. */
Function getDirectSuperCallTargetFromCall(
Class mroBase, Function meth, DataFlow::MethodCallNode call, string name
) {
meth = call.getScope() and
meth.getName() = name and
call.calls(_, name) and
mroBase = getADirectSubclass*(meth.getScope()) and
exists(Class targetCls |
// the differences between 0-arg and 2-arg super is not considered; we assume each super uses the mro of the instance `self`
superCall(call, _) and
targetCls = getNextClassInMroKnownStartingClass(meth.getScope(), mroBase) and
result = findFunctionAccordingToMroKnownStartingClass(targetCls, mroBase, name)
or
// targetCls is the mro base for this lookup.
// note however that if the call we find uses super(), that still uses the mro of the instance `self`
// assuming it's 0-arg or is 2-arg with `self` as second arg.
callsMethodOnClassWithSelf(meth, call, targetCls, _) and
result = findFunctionAccordingToMroKnownStartingClass(targetCls, targetCls, name)
)
}
/** Gets a method that is transitively called by a call to `cls.<name>`, with `mroBase` as the type determining the MRO to search. */
Function getASuperCallTargetFromClass(Class mroBase, Class cls, string name) {
exists(Function target |
target = findFunctionAccordingToMroKnownStartingClass(cls, mroBase, name) and
(
result = target
or
result = getASuperCallTargetFromCall(mroBase, target, _, name)
)
)
}
/** Holds if `meth` does something besides calling a superclass method. */
predicate nonTrivial(Function meth) {
exists(Stmt s | s = meth.getAStmt() |
not s instanceof Pass and
not exists(DataFlow::Node call | call.asExpr() = s.(ExprStmt).getValue() |
superCall(call, meth.getName())
or
callsMethodOnClassWithSelf(meth, call, _, meth.getName())
)
) and
exists(meth.getANormalExit()) // doesn't always raise an exception
}
/** Holds if `call` is a call to `super().<name>`. No distinction is made between 0- and 2- arg super calls. */
predicate superCall(DataFlow::MethodCallNode call, string name) {
exists(DataFlow::Node sup |
call.calls(sup, name) and
sup = API::builtin("super").getACall()
)
}
/** Holds if `meth` calls a `super()` method of the same name. */
predicate callsSuper(Function meth) {
exists(DataFlow::MethodCallNode call |
call.getScope() = meth and
superCall(call, meth.getName())
)
}
/** Holds if `meth` calls `target.<name>(self, ...)` with the call `call`. */
predicate callsMethodOnClassWithSelf(
Function meth, DataFlow::MethodCallNode call, Class target, string name
) {
exists(DataFlow::Node callTarget, DataFlow::ParameterNode self |
call.calls(callTarget, name) and
self.getParameter() = meth.getArg(0) and
self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and
callTarget = classTracker(target)
)
}
/** Holds if `meth` calls a method named `name` passing its `self` argument as its first parameter, but the class it refers to is unknown. */
predicate callsMethodOnUnknownClassWithSelf(Function meth, string name) {
exists(DataFlow::MethodCallNode call, DataFlow::Node callTarget, DataFlow::ParameterNode self |
call.calls(callTarget, name) and
self.getParameter() = meth.getArg(0) and
self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and
not callTarget = classTracker(any(Class target))
)
}
/** Holds if `base` does not call a superclass method `shouldCall` named `name` when it appears it should. */
predicate missingCallToSuperclassMethod(Class base, Function shouldCall, string name) {
shouldCall.getName() = name and
shouldCall.getScope() = getADirectSuperclass+(base) and
not shouldCall = getASuperCallTargetFromClass(base, base, name) and
nonTrivial(shouldCall) and
// "Benefit of the doubt" - if somewhere in the chain we call an unknown superclass, assume all the necessary parent methods are called from it
not callsMethodOnUnknownClassWithSelf(getASuperCallTargetFromClass(base, base, name), name)
}
/**
* Holds if `base` does not call a superclass method `shouldCall` named `name` when it appears it should.
* Results are restricted to hold only for the highest `base` class and the lowest `shouldCall` method in the hierarchy for which this applies.
*/
predicate missingCallToSuperclassMethodRestricted(Class base, Function shouldCall, string name) {
missingCallToSuperclassMethod(base, shouldCall, name) and
not exists(Class superBase |
// Alert only on the highest base class that has the issue
superBase = getADirectSuperclass+(base) and
missingCallToSuperclassMethod(superBase, shouldCall, name)
) and
not exists(Function subShouldCall |
// Mention in the alert only the lowest method we're missing the call to
subShouldCall.getScope() = getADirectSubclass+(shouldCall.getScope()) and
missingCallToSuperclassMethod(base, subShouldCall, name)
)
}
/**
* If `base` contains a `super()` call, gets a method in the inheritance hierarchy of `name` in the MRO of `base`
* that does not contain a `super()` call, but would call `shouldCall` if it did, which does not otherwise get called
* during a call to `base.<name>`.
*/
Function getPossibleMissingSuper(Class base, Function shouldCall, string name) {
missingCallToSuperclassMethod(base, shouldCall, name) and
exists(Function baseMethod |
baseMethod.getScope() = base and
baseMethod.getName() = name and
// the base method calls super, so is presumably expecting every method called in the MRO chain to do so
callsSuper(baseMethod) and
// result is something that does get called in the chain
result = getASuperCallTargetFromClass(base, base, name) and
// it doesn't call super
not callsSuper(result) and
// if it did call super, it would resolve to the missing method
shouldCall =
findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(result
.getScope(), base), base, name)
)
}
/** An optional `Function`. */
class FunctionOption extends LocatableOption<Location, Function>::Option {
/** Gets the qualified name of this function, or the empty string if it is None. */
string getQualifiedName() {
this.isNone() and result = ""
or
result = this.asSome().getQualifiedName()
}
}
/** Gets the result of `getPossibleMissingSuper`, or None if none exists. */
bindingset[name]
FunctionOption getPossibleMissingSuperOption(Class base, Function shouldCall, string name) {
result.asSome() = getPossibleMissingSuper(base, shouldCall, name)
or
not exists(getPossibleMissingSuper(base, shouldCall, name)) and
result.isNone()
}

View File

@@ -0,0 +1,52 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in
when and how superclass finalizers are called during object finalization.
However, the developer has responsibility for ensuring that objects are properly cleaned up, and that all superclass <code>__del__</code>
methods are called.
</p>
<p>
Classes with a <code>__del__</code> method (a finalizer) typically hold some resource such as a file handle that needs to be cleaned up.
If the <code>__del__</code> method of a superclass is not called during object finalization, it is likely that
resources may be leaked.
</p>
<p>A call to the <code>__del__</code> method of a superclass during object initialization may be unintentionally skipped:
</p>
<ul>
<li>If a subclass calls the <code>__del__</code> method of the wrong class.</li>
<li>If a call to the <code>__del__</code> method of one its base classes is omitted.</li>
<li>If a call to <code>super().__del__</code> is used, but not all <code>__del__</code> methods in the Method Resolution Order (MRO)
chain themselves call <code>super()</code>. This in particular arises more often in cases of multiple inheritance. </li>
</ul>
</overview>
<recommendation>
<p>Ensure that all superclass <code>__del__</code> methods are properly called.
Either each base class's finalize method should be explicitly called, or <code>super()</code> calls
should be consistently used throughout the inheritance hierarchy.</p>
</recommendation>
<example>
<p>In the following example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__del__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__del__</code>.
</p>
<sample src="examples/MissingCallToDel.py" />
</example>
<references>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__del__">__del__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,49 @@
/**
* @name Missing call to superclass `__del__` during object destruction
* @description An omitted call to a superclass `__del__` method may lead to class instances not being cleaned up properly.
* @kind problem
* @tags quality
* reliability
* correctness
* performance
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/missing-call-to-delete
*/
import python
import MethodCallOrder
Function getDelMethod(Class c) {
result = c.getAMethod() and
result.getName() = "__del__"
}
from Class base, Function shouldCall, FunctionOption possibleIssue, string msg
where
not exists(Function newMethod | newMethod = base.getAMethod() and newMethod.getName() = "__new__") and
exists(FunctionOption possiblyMissingSuper |
missingCallToSuperclassMethodRestricted(base, shouldCall, "__del__") and
possiblyMissingSuper = getPossibleMissingSuperOption(base, shouldCall, "__del__") and
(
not possiblyMissingSuper.isNone() and
possibleIssue = possiblyMissingSuper and
msg =
"This class does not call $@ during finalization. ($@ may be missing a call to super().__del__)"
or
possiblyMissingSuper.isNone() and
(
possibleIssue.asSome() = getDelMethod(base) and
msg =
"This class does not call $@ during finalization. ($@ may be missing a call to a base class __del__)"
or
not exists(getDelMethod(base)) and
possibleIssue.isNone() and
msg =
"This class does not call $@ during finalization. (The class lacks an __del__ method to ensure every base class __del__ is called.)"
)
)
)
select base, msg, shouldCall, shouldCall.getQualifiedName(), possibleIssue,
possibleIssue.getQualifiedName()

View File

@@ -0,0 +1,50 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in
when and how superclass initializers are called during object initialization.
However, the developer has responsibility for ensuring that objects are properly initialized, and that all superclass <code>__init__</code>
methods are called.
</p>
<p>
If the <code>__init__</code> method of a superclass is not called during object initialization, this can lead to errors due to
the object not being fully initialized, such as having missing attributes.
</p>
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be unintentionally skipped:
</p>
<ul>
<li>If a subclass calls the <code>__init__</code> method of the wrong class.</li>
<li>If a call to the <code>__init__</code> method of one its base classes is omitted.</li>
<li>If a call to <code>super().__init__</code> is used, but not all <code>__init__</code> methods in the Method Resolution Order (MRO)
chain themselves call <code>super()</code>. This in particular arises more often in cases of multiple inheritance. </li>
</ul>
</overview>
<recommendation>
<p>Ensure that all superclass <code>__init__</code> methods are properly called.
Either each base class's initialize method should be explicitly called, or <code>super()</code> calls
should be consistently used throughout the inheritance hierarchy.</p>
</recommendation>
<example>
<p>In the following example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__init__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__init__</code>.
</p>
<sample src="examples/MissingCallToInit.py" />
</example>
<references>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">__init__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,41 @@
/**
* @name Missing call to superclass `__init__` during object initialization
* @description An omitted call to a superclass `__init__` method may lead to objects of this class not being fully initialized.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/missing-call-to-init
*/
import python
import MethodCallOrder
from Class base, Function shouldCall, FunctionOption possibleIssue, string msg
where
exists(FunctionOption possiblyMissingSuper |
missingCallToSuperclassMethodRestricted(base, shouldCall, "__init__") and
possiblyMissingSuper = getPossibleMissingSuperOption(base, shouldCall, "__init__") and
(
possibleIssue.asSome() = possiblyMissingSuper.asSome() and
msg =
"This class does not call $@ during initialization. ($@ may be missing a call to super().__init__)"
or
possiblyMissingSuper.isNone() and
(
possibleIssue.asSome() = base.getInitMethod() and
msg =
"This class does not call $@ during initialization. ($@ may be missing a call to a base class __init__)"
or
not exists(base.getInitMethod()) and
possibleIssue.isNone() and
msg =
"This class does not call $@ during initialization. (The class lacks an __init__ method to ensure every base class __init__ is called.)"
)
)
)
select base, msg, shouldCall, shouldCall.getQualifiedName(), possibleIssue,
possibleIssue.getQualifiedName()

View File

@@ -0,0 +1,55 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in
when and how superclass finalizers are called during object finalization.
However, the developer has responsibility for ensuring that objects are properly cleaned up.
</p>
<p>
Objects with a <code>__del__</code> method (a finalizer) often hold resources such as file handles that need to be cleaned up.
If a superclass finalizer is called multiple times, this may lead to errors such as closing an already closed file, and lead to objects not being
cleaned up properly as expected.
</p>
<p>There are a number of ways that a <code>__del__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__del__</code> methods.</li>
<li>In situations involving multiple inheritance, an finalization method may call the finalizers of each of its base types,
which themselves both call the finalizer of a shared base type. (This is an example of the Diamond Inheritance problem)</li>
<li>Another situation involving multiple inheritance arises when a subclass calls the <code>__del__</code> methods of each of its base classes,
one of which calls <code>super().__del__</code>. This super call resolves to the next class in the Method Resolution Order (MRO) of the subclass,
which may be another base class that already has its initializer explicitly called.</li>
</ul>
</overview>
<recommendation>
<p>Ensure that each finalizer method is called exactly once during finalization.
This can be ensured by calling <code>super().__del__</code> for each finalizer method in the inheritance chain.
</p>
</recommendation>
<example>
<p>In the following example, there is a mixture of explicit calls to <code>__del__</code> and calls using <code>super()</code>, resulting in <code>Vehicle.__del__</code>
being called twice.
<code>FixedSportsCar.__del__</code> fixes this by using <code>super()</code> consistently with the other delete methods.
</p>
<sample src="examples/SuperclassDelCalledMultipleTimes.py" />
</example>
<references>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__del__">__del__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">The Diamond Problem</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Multiple calls to `__del__` during object destruction
* @description A duplicated call to a superclass `__del__` method may lead to class instances not be cleaned up properly.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/multiple-calls-to-delete
*/
import python
import MethodCallOrder
predicate multipleCallsToSuperclassDel(
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
DataFlow::MethodCallNode call2
) {
multipleCallsToSuperclassMethod(meth, calledMulti, call1, call2, "__del__")
}
from
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
DataFlow::MethodCallNode call2, Function target1, Function target2, string msg
where
multipleCallsToSuperclassDel(meth, calledMulti, call1, call2) and
// Only alert for the lowest method in the hierarchy that both calls will call.
not exists(Function subMulti |
multipleCallsToSuperclassDel(meth, subMulti, _, _) and
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
) and
target1 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call1, _) and
target2 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call2, _) and
(
target1 != target2 and
msg =
"This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
or
target1 = target2 and
// The targets themselves are called multiple times (either is calledMulti, or something earlier in the MRO)
// Mentioning them again would be redundant.
msg = "This finalization method calls $@ multiple times, via $@ and $@."
)
select meth, msg, calledMulti, calledMulti.getQualifiedName(), call1, "this call", call2,
"this call", target1, target1.getQualifiedName(), target2, target2.getQualifiedName()

View File

@@ -0,0 +1,74 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in
when and how superclass initializers are called during object initialization.
However, the developer has responsibility for ensuring that objects are properly initialized.
</p>
<p>
Calling an <code>__init__</code> method more than once during object initialization risks the object being incorrectly
initialized, as the method and the rest of the inheritance chain may not have been written with the expectation
that it could be called multiple times. For example, it may set attributes to a default value in a way that unexpectedly overwrites
values setting those attributes in a subclass.
</p>
<p>There are a number of ways that an <code>__init__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__init__</code> methods.</li>
<li>In situations involving multiple inheritance, an initialization method may call the initializers of each of its base types,
which themselves both call the initializer of a shared base type. (This is an example of the Diamond Inheritance problem)</li>
<li>Another situation involving multiple inheritance arises when a subclass calls the <code>__init__</code> methods of each of its base classes,
one of which calls <code>super().__init__</code>. This super call resolves to the next class in the Method Resolution Order (MRO) of the subclass,
which may be another base class that already has its initializer explicitly called.</li>
</ul>
</overview>
<recommendation>
<p>
Take care whenever possible not to call an an initializer multiple times. If each <code>__init__</code> method in the hierarchy
calls <code>super().__init__()</code>, then each initializer will be called exactly once according to the MRO of the subclass.
When explicitly calling base class initializers (such as to pass different arguments to different initializers),
ensure this is done consistently throughout, rather than using <code>super()</code> calls in the base classes.
</p>
<p>
In some cases, it may not be possible to avoid calling a base initializer multiple times without significant refactoring.
In this case, carefully check that the initializer does not interfere with subclass initializers
when called multiple times (such as by overwriting attributes), and ensure this behavior is documented.
</p>
</recommendation>
<example>
<p>In the following (BAD) example, the class <code>D</code> calls <code>B.__init__</code> and <code>C.__init__</code>,
which each call <code>A.__init__</code>. This results in <code>self.state</code> being set to <code>None</code> as
<code>A.__init__</code> is called again after <code>B.__init__</code> had finished. This may lead to unexpected results.
</p>
<sample src="examples/SuperclassInitCalledMultipleTimesBad1.py" />
<p>In the following (GOOD) example, a call to <code>super().__init__</code> is made in each class
in the inheritance hierarchy, ensuring each initializer is called exactly once.
</p>
<sample src="examples/SuperclassInitCalledMultipleTimesGood2.py" />
<p>In the following (BAD) example, explicit base class calls are mixed with <code>super()</code> calls, and <code>C.__init__</code> is called twice.</p>
<sample src="examples/SuperclassInitCalledMultipleTimesBad3.py" />
</example>
<references>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">__init__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">The Diamond Problem</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Multiple calls to `__init__` during object initialization
* @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
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/multiple-calls-to-init
*/
import python
import MethodCallOrder
predicate multipleCallsToSuperclassInit(
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
DataFlow::MethodCallNode call2
) {
multipleCallsToSuperclassMethod(meth, calledMulti, call1, call2, "__init__")
}
from
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
DataFlow::MethodCallNode call2, Function target1, Function target2, string msg
where
multipleCallsToSuperclassInit(meth, calledMulti, call1, call2) and
// Only alert for the lowest method in the hierarchy that both calls will call.
not exists(Function subMulti |
multipleCallsToSuperclassInit(meth, subMulti, _, _) and
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
) and
target1 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call1, _) and
target2 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call2, _) and
(
target1 != target2 and
msg =
"This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
or
target1 = target2 and
// The targets themselves are called multiple times (either is calledMulti, or something earlier in the MRO)
// Mentioning them again would be redundant.
msg = "This initialization method calls $@ multiple times, via $@ and $@."
)
select meth, msg, calledMulti, calledMulti.getQualifiedName(), call1, "this call", call2,
"this call", target1, target1.getQualifiedName(), target2, target2.getQualifiedName()

View File

@@ -10,14 +10,14 @@ class Car(Vehicle):
recycle(self.car_parts)
Vehicle.__del__(self)
#Car.__del__ is missed out.
#BAD: Car.__del__ is not called.
class SportsCar(Car, Vehicle):
def __del__(self):
recycle(self.sports_car_parts)
Vehicle.__del__(self)
#Fix SportsCar by calling Car.__del__
#GOOD: Car.__del__ is called correctly.
class FixedSportsCar(Car, Vehicle):
def __del__(self):

View File

@@ -10,14 +10,14 @@ class Car(Vehicle):
Vehicle.__init__(self)
self.car_init()
#Car.__init__ is missed out.
# BAD: Car.__init__ is not called.
class SportsCar(Car, Vehicle):
def __init__(self):
Vehicle.__init__(self)
self.sports_car_init()
#Fix SportsCar by calling Car.__init__
# GOOD: Car.__init__ is called correctly.
class FixedSportsCar(Car, Vehicle):
def __init__(self):

View File

@@ -15,14 +15,14 @@ class Car(Vehicle):
class SportsCar(Car, Vehicle):
# Vehicle.__del__ will get called twice
# BAD: Vehicle.__del__ will get called twice
def __del__(self):
recycle(self.sports_car_parts)
Car.__del__(self)
Vehicle.__del__(self)
#Fix SportsCar by using super()
# GOOD: super() is used ensuring each del method is called once.
class FixedSportsCar(Car, Vehicle):
def __del__(self):

View File

@@ -0,0 +1,20 @@
class A:
def __init__(self):
self.state = None
class B(A):
def __init__(self):
A.__init__(self)
self.state = "B"
self.b = 3
class C(A):
def __init__(self):
A.__init__(self)
self.c = 2
class D(B,C):
def __init__(self):
B.__init__(self)
C.__init__(self) # BAD: This calls A.__init__ a second time, setting self.state to None.

View File

@@ -0,0 +1,22 @@
class A:
def __init__(self):
print("A")
self.state = None
class B(A):
def __init__(self):
print("B")
super().__init__() # When called from D, this calls C.__init__
self.state = "B"
self.b = 3
class C(A):
def __init__(self):
print("C")
super().__init__()
self.c = 2
class D(B,C):
def __init__(self):
B.__init__(self)
C.__init__(self) # BAD: C.__init__ is called a second time

View File

@@ -0,0 +1,22 @@
class A:
def __init__(self):
self.state = None
class B(A):
def __init__(self):
super().__init__()
self.state = "B"
self.b = 3
class C(A):
def __init__(self):
super().__init__()
self.c = 2
class D(B,C):
def __init__(self): # GOOD: Each method calls super, so each init method runs once. self.state will be set to "B".
super().__init__()
self.d = 1

View File

@@ -1,3 +1,5 @@
deprecated module;
import python
// Helper predicates for multiple call to __init__/__del__ queries.

View File

@@ -1,50 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction.
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
Therefore the developer has responsibility for ensuring that objects are properly cleaned up when
there are multiple <code>__del__</code> methods that need to be called.
</p>
<p>
If the <code>__del__</code> method of a superclass is not called during object destruction it is likely that
that resources may be leaked.
</p>
<p>A call to the <code>__del__</code> method of a superclass during object destruction may be omitted:
</p>
<ul>
<li>When a subclass calls the <code>__del__</code> method of the wrong class.</li>
<li>When a call to the <code>__del__</code> method of one its base classes is omitted.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful to explicitly call the <code>__del__</code> of the correct base class, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
</recommendation>
<example>
<p>In this example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__del__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__del__</code>.
</p>
<sample src="MissingCallToDel.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
</references>
</qhelp>

View File

@@ -1,26 +0,0 @@
/**
* @name Missing call to `__del__` during object destruction
* @description An omitted call to a super-class `__del__` method may lead to class instances not being cleaned up properly.
* @kind problem
* @tags quality
* reliability
* correctness
* performance
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/missing-call-to-delete
*/
import python
import MethodCallOrder
from ClassObject self, FunctionObject missing
where
missing_call_to_superclass_method(self, _, missing, "__del__") and
not missing.neverReturns() and
not self.failedInference() and
not missing.isBuiltin()
select self,
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
missing, missing.descriptiveString()

View File

@@ -1,52 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization.
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
Therefore the developer has responsibility for ensuring that objects are properly initialized when
there are multiple <code>__init__</code> methods that need to be called.
</p>
<p>
If the <code>__init__</code> method of a superclass is not called during object initialization it is likely that
that object will end up in an incorrect state.
</p>
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be omitted:
</p>
<ul>
<li>When a subclass calls the <code>__init__</code> method of the wrong class.</li>
<li>When a call to the <code>__init__</code> method of one its base classes is omitted.</li>
<li>When multiple inheritance is used and a class inherits from several base classes,
and at least one of those does not use <code>super()</code> in its own <code>__init__</code> method.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful to explicitly call the <code>__init__</code> of the correct base class, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
</recommendation>
<example>
<p>In this example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__init__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__init__</code>.
</p>
<sample src="MissingCallToInit.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
</references>
</qhelp>

View File

@@ -1,29 +0,0 @@
/**
* @name Missing call to `__init__` during object initialization
* @description An omitted call to a super-class `__init__` method may lead to objects of this class not being fully initialized.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/missing-call-to-init
*/
import python
import MethodCallOrder
from ClassObject self, FunctionObject initializer, FunctionObject missing
where
self.lookupAttribute("__init__") = initializer and
missing_call_to_superclass_method(self, initializer, missing, "__init__") and
// If a superclass is incorrect, don't flag this class as well.
not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and
not missing.neverReturns() and
not self.failedInference() and
not missing.isBuiltin() and
not self.isAbstract()
select self,
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
missing, missing.descriptiveString(), initializer, "__init__ method"

View File

@@ -1,29 +0,0 @@
#Calling a method multiple times by using explicit calls when a base inherits from other base
class Vehicle(object):
def __del__(self):
recycle(self.base_parts)
class Car(Vehicle):
def __del__(self):
recycle(self.car_parts)
Vehicle.__del__(self)
class SportsCar(Car, Vehicle):
# Vehicle.__del__ will get called twice
def __del__(self):
recycle(self.sports_car_parts)
Car.__del__(self)
Vehicle.__del__(self)
#Fix SportsCar by only calling Car.__del__
class FixedSportsCar(Car, Vehicle):
def __del__(self):
recycle(self.sports_car_parts)
Car.__del__(self)

View File

@@ -1,58 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction.
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
Therefore the developer has responsibility for ensuring that objects are properly cleaned up when
there are multiple <code>__del__</code> methods that need to be called.
</p>
<p>
Calling a <code>__del__</code> method more than once during object destruction risks resources being released multiple
times. The relevant <code>__del__</code> method may not be designed to be called more than once.
</p>
<p>There are a number of ways that a <code>__del__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__del__</code> methods.</li>
<li>A class using multiple inheritance directly calls the <code>__del__</code> methods of its base types.
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful not to explicitly call a <code>__del__</code> method more than once, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
</recommendation>
<example>
<p>In the first example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
both <code>Vehicle.__del__</code> and <code>Car.__del__</code>.
This can be fixed by removing the call to <code>Vehicle.__del__</code>, as shown in <code>FixedSportsCar</code>.
</p>
<sample src="SuperclassDelCalledMultipleTimes.py" />
<p>In the second example, there is a mixture of explicit calls to <code>__del__</code> and calls using <code>super()</code>.
To fix this example, <code>super()</code> should be used throughout.
</p>
<sample src="SuperclassInitCalledMultipleTimes2.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
</references>
</qhelp>

View File

@@ -1,29 +0,0 @@
/**
* @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.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/multiple-calls-to-delete
*/
import python
import MethodCallOrder
from ClassObject self, FunctionObject multi
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()

View File

@@ -1,36 +0,0 @@
#Calling a method multiple times by using explicit calls when a base inherits from other base
class Vehicle(object):
def __init__(self):
self.mobile = True
class Car(Vehicle):
def __init__(self):
Vehicle.__init__(self)
self.car_init()
def car_init(self):
pass
class SportsCar(Car, Vehicle):
# Vehicle.__init__ will get called twice
def __init__(self):
Vehicle.__init__(self)
Car.__init__(self)
self.sports_car_init()
def sports_car_init(self):
pass
#Fix SportsCar by only calling Car.__init__
class FixedSportsCar(Car, Vehicle):
def __init__(self):
Car.__init__(self)
self.sports_car_init()
def sports_car_init(self):
pass

View File

@@ -1,58 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization.
However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies.
Therefore the developer has responsibility for ensuring that objects are properly initialized when
there are multiple <code>__init__</code> methods that need to be called.
</p>
<p>
Calling an <code>__init__</code> method more than once during object initialization risks the object being incorrectly initialized.
It is unlikely that the relevant <code>__init__</code> method is designed to be called more than once.
</p>
<p>There are a number of ways that an <code>__init__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__init__</code> methods.</li>
<li>A class using multiple inheritance directly calls the <code>__init__</code> methods of its base types.
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful not to explicitly call an <code>__init__</code> method more than once, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
</recommendation>
<example>
<p>In the first example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
both <code>Vehicle.__init__</code> and <code>Car.__init__</code>.
This can be fixed by removing the call to <code>Vehicle.__init__</code>, as shown in <code>FixedSportsCar</code>.
</p>
<sample src="SuperclassInitCalledMultipleTimes.py" />
<p>In the second example, there is a mixture of explicit calls to <code>__init__</code> and calls using <code>super()</code>.
To fix this example, <code>super()</code> should be used throughout.
</p>
<sample src="SuperclassInitCalledMultipleTimes2.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
</references>
</qhelp>

View File

@@ -1,30 +0,0 @@
/**
* @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.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/multiple-calls-to-init
*/
import python
import MethodCallOrder
from ClassObject self, FunctionObject multi
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()

View File

@@ -1,38 +0,0 @@
#Calling a method multiple times by using explicit calls when a base uses super()
class Vehicle(object):
def __init__(self):
super(Vehicle, self).__init__()
self.mobile = True
class Car(Vehicle):
def __init__(self):
super(Car, self).__init__()
self.car_init()
def car_init(self):
pass
class SportsCar(Car, Vehicle):
# Vehicle.__init__ will get called twice
def __init__(self):
Vehicle.__init__(self)
Car.__init__(self)
self.sports_car_init()
def sports_car_init(self):
pass
#Fix SportsCar by using super()
class FixedSportsCar(Car, Vehicle):
def __init__(self):
super(SportsCar, self).__init__()
self.sports_car_init()
def sports_car_init(self):
pass

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The queries `py/missing-call-to-init`, `py/missing-calls-to-del`, `py/multiple-calls-to-init`, and `py/multiple-calls-to-del` queries have been modernized; no longer relying on outdated libraries, producing more precise results with more descriptive alert messages, and improved documentation.

View File

@@ -1 +1 @@
| missing_del.py:12:1:12:13 | class X3 | Class X3 may not be cleaned up properly as $@ is not called during deletion. | missing_del.py:9:5:9:22 | Function __del__ | method X2.__del__ |
| missing_del.py:13:1:13:13 | Class X3 | This class does not call $@ during finalization. ($@ may be missing a call to a base class __del__) | missing_del.py:9:5:9:22 | Function __del__ | X2.__del__ | missing_del.py:15:5:15:22 | Function __del__ | X3.__del__ |

View File

@@ -1 +1,2 @@
Classes/MissingCallToDel.ql
query: Classes/CallsToInitDel/MissingCallToDel.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,14 +2,19 @@
class X1(object):
def __del__(self):
pass
print("X1 del")
class X2(X1):
def __del__(self):
print("X2 del")
X1.__del__(self)
class X3(X2):
class X3(X2): # $ Alert - skips X2 del
def __del__(self):
print("X3 del")
X1.__del__(self)
a = X3()
del a

View File

@@ -1,3 +1,6 @@
| missing_init.py:12:1:12:13 | class B3 | Class B3 may not be initialized properly as $@ is not called from its $@. | missing_init.py:9:5:9:23 | Function __init__ | method B2.__init__ | missing_init.py:14:5:14:23 | Function __init__ | __init__ method |
| missing_init.py:39:1:39:21 | class IUVT | Class IUVT may not be initialized properly as $@ is not called from its $@. | missing_init.py:30:5:30:23 | Function __init__ | method UT.__init__ | missing_init.py:26:5:26:23 | Function __init__ | __init__ method |
| missing_init.py:72:1:72:13 | class AB | Class AB may not be initialized properly as $@ is not called from its $@. | missing_init.py:69:5:69:23 | Function __init__ | method AA.__init__ | missing_init.py:75:5:75:23 | Function __init__ | __init__ method |
| missing_init.py:13:1:13:13 | Class B3 | This class does not call $@ during initialization. ($@ may be missing a call to a base class __init__) | missing_init.py:9:5:9:23 | Function __init__ | B2.__init__ | missing_init.py:14:5:14:23 | Function __init__ | B3.__init__ |
| missing_init.py:42:1:42:21 | Class IUVT | This class does not call $@ during initialization. (The class lacks an __init__ method to ensure every base class __init__ is called.) | missing_init.py:33:5:33:23 | Function __init__ | UT.__init__ | file://:0:0:0:0 | (none) | |
| missing_init.py:67:1:67:13 | Class AB | This class does not call $@ during initialization. ($@ may be missing a call to a base class __init__) | missing_init.py:64:5:64:23 | Function __init__ | AA.__init__ | missing_init.py:70:5:70:23 | Function __init__ | AB.__init__ |
| missing_init.py:122:5:122:17 | Class DC | This class does not call $@ during initialization. ($@ may be missing a call to a base class __init__) | missing_init.py:117:5:117:23 | Function __init__ | DA.__init__ | missing_init.py:124:9:124:27 | Function __init__ | DB.DC.__init__ |
| missing_init.py:132:1:132:13 | Class DD | This class does not call $@ during initialization. ($@ may be missing a call to a base class __init__) | missing_init.py:117:5:117:23 | Function __init__ | DA.__init__ | missing_init.py:134:5:134:23 | Function __init__ | DD.__init__ |
| missing_init.py:200:1:200:17 | Class H3 | This class does not call $@ during initialization. ($@ may be missing a call to super().__init__) | missing_init.py:197:5:197:23 | Function __init__ | H2.__init__ | missing_init.py:193:5:193:23 | Function __init__ | H1.__init__ |

View File

@@ -1 +1,2 @@
Classes/MissingCallToInit.ql
query: Classes/CallsToInitDel/MissingCallToInit.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,18 +2,21 @@
class B1(object):
def __init__(self):
do_something()
print("B1 init")
class B2(B1):
def __init__(self):
print("B2 init")
B1.__init__(self)
class B3(B2):
def __init__(self):
class B3(B2): # $ Alert
def __init__(self):
print("B3 init")
B1.__init__(self)
B3()
#OK if superclass __init__ is builtin as
#builtin classes tend to rely on __new__
class MyException(Exception):
@@ -23,11 +26,11 @@ class MyException(Exception):
#ODASA-4107
class IUT(object):
def __init__(self):
def __init__(self):
print("IUT init")
class UT(object):
def __init__(self):
def __init__(self):
print("UT init")
class PU(object):
@@ -36,150 +39,167 @@ class PU(object):
class UVT(UT, PU):
pass
class IUVT(IUT, UVT):
class IUVT(IUT, UVT): # $ Alert
pass
#False positive
print("IUVT")
IUVT()
class M1(object):
def __init__(self):
print("A")
print("M1 init")
class M2(object):
pass
class Mult(M2, M1):
def __init__(self):
super(Mult, self).__init__() # Calls M1.__init__
print("Mult init")
super(Mult, self).__init__() # OK - Calls M1.__init__
class X:
def __init__(self):
do_something()
class Y(X):
@decorated
def __init__(self):
X.__init__(self)
class Z(Y):
def __init__(self):
Y.__init__(self)
Mult()
class AA(object):
def __init__(self):
do_something()
print("AA init")
class AB(AA):
class AB(AA): # $ Alert
#Don't call super class init
def __init__(self):
do_something()
# Doesn't call super class init
def __init__(self):
print("AB init")
class AC(AB):
def __init__(self):
#Missing call to AA.__init__ but not AC's fault.
# Doesn't call AA init, but we don't alert here as the issue is with AB.
print("AC init")
super(AC, self).__init__()
AC()
import six
import abc
class BA(object):
def __init__(self):
do_something()
print("BA init")
@six.add_metaclass(abc.ABCMeta)
class BB(BA):
def __init__(self):
print("BB init")
super(BB,self).__init__()
BB()
@six.add_metaclass(abc.ABCMeta)
class CA(object):
def __init__(self):
do_something()
print("CA init")
class CB(BA):
class CB(CA):
def __init__(self):
print("CB init")
super(CB,self).__init__()
CB()
#ODASA-5799
class DA(object):
def __init__(self):
do_something()
print("DA init")
class DB(DA):
class DC(DA):
class DC(DA): # $ SPURIOUS: Alert # We only consider direct super calls, so have an FP here
def __init__(self):
def __init__(self):
print("DC init")
sup = super(DB.DC, self)
sup.__init__()
#Simpler variants
class DD(DA):
DB.DC()
def __init__(self):
#Simpler variants
class DD(DA): # $ SPURIOUS: Alert # We only consider direct super calls, so have an FP here
def __init__(self):
print("DD init")
sup = super(DD, self)
sup.__init__()
DD()
class DE(DA):
class DF(DA):
class DF(DA): # No alert here
def __init__(self):
def __init__(self):
print("DF init")
sup = super(DE.DF, self).__init__()
DE.DF()
class FA(object):
def __init__(self):
pass
pass # does nothing, thus is considered a trivial method and ok to not call
class FB(object):
def __init__(self):
do_something()
print("FB init")
class FC(FA, FB):
def __init__(self):
#OK to skip call to FA.__init__ as that does nothing.
# No alert here - ok to skip call to trivial FA init
FB.__init__(self)
#Potential false positives.
class ConfusingInit(B1):
def __init__(self):
def __init__(self): # We track this correctly and don't alert.
super_call = super(ConfusingInit, self).__init__
super_call()
# Library class
import collections
class G1(collections.Counter):
class G1:
def __init__(self):
collections.Counter.__init__(self)
class G2(G1):
print("G1 init")
class G2:
def __init__(self):
super(G2, self).__init__()
print("G2 init")
class G3(collections.Counter):
class G3(G1, G2):
def __init__(self):
print("G3 init")
for cls in self.__class__.__bases__:
cls.__init__(self) # We dont track which classes this could refer to, but assume it calls all required init methods and don't alert.
G3()
class H1:
def __init__(self):
super(G3, self).__init__()
class G4(G3):
print("H1 init")
class H2:
def __init__(self):
G3.__init__(self)
print("H2 init")
class H3(H1, H2): # $ Alert # The alert should also mention that H1.__init__ may be missing a call to super().__init__
def __init__(self):
print("H3 init")
super().__init__()
H3()

View File

@@ -1,2 +0,0 @@
| multiple_del.py:17:1:17:17 | class Y3 | Class Y3 may not be cleaned up properly as $@ may be called multiple times during destruction. | multiple_del.py:9:5:9:22 | Function __del__ | method Y1.__del__ |
| multiple_del.py:34:1:34:17 | class Z3 | Class Z3 may not be cleaned up properly as $@ may be called multiple times during destruction. | multiple_del.py:26:5:26:22 | Function __del__ | method Z1.__del__ |

View File

@@ -1 +0,0 @@
Classes/SuperclassDelCalledMultipleTimes.ql

View File

@@ -1,2 +0,0 @@
| multiple_init.py:17:1:17:17 | class C3 | Class C3 may not be initialized properly as $@ may be called multiple times during initialization. | multiple_init.py:9:5:9:23 | Function __init__ | method C1.__init__ |
| multiple_init.py:34:1:34:17 | class D3 | Class D3 may not be initialized properly as $@ may be called multiple times during initialization. | multiple_init.py:26:5:26:23 | Function __init__ | method D1.__init__ |

View File

@@ -1 +0,0 @@
Classes/SuperclassInitCalledMultipleTimes.ql

View File

@@ -0,0 +1,2 @@
| multiple_del.py:21:5:21:22 | Function __del__ | This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:23:9:23:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:24:9:24:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:15:5:15:22 | Function __del__ | Y2.__del__ |
| multiple_del.py:43:5:43:22 | Function __del__ | This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:45:9:45:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:46:9:46:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:37:5:37:22 | Function __del__ | Z2.__del__ |

View File

@@ -0,0 +1,2 @@
query: Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,37 +2,48 @@
class Base(object):
def __del__(self):
pass
print("Base del")
class Y1(Base):
def __del__(self):
print("Y1 del")
super(Y1, self).__del__()
class Y2(Base):
def __del__(self):
print("Y2 del")
super(Y2, self).__del__() #When `type(self) == Y3` this calls `Y1.__del__`
class Y3(Y2, Y1):
def __del__(self):
def __del__(self): # $ Alert
print("Y3 del")
Y1.__del__(self)
Y2.__del__(self)
a = Y3()
del a
#Calling a method multiple times by using explicit calls when a base inherits from other base
class Z1(object):
def __del__(self):
pass
print("Z1 del")
class Z2(Z1):
def __del__(self):
print("Z2 del")
Z1.__del__(self)
class Z3(Z2, Z1):
def __del__(self):
def __del__(self): # $ Alert
print("Z3 del")
Z1.__del__(self)
Z2.__del__(self)
b = Z3()
del b

View File

@@ -0,0 +1,4 @@
| multiple_init.py:21:5:21:23 | Function __init__ | This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_init.py:9:5:9:23 | Function __init__ | C1.__init__ | multiple_init.py:23:9:23:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:24:9:24:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:9:5:9:23 | Function __init__ | C1.__init__ | multiple_init.py:15:5:15:23 | Function __init__ | C2.__init__ |
| multiple_init.py:42:5:42:23 | Function __init__ | This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_init.py:31:5:31:23 | Function __init__ | D1.__init__ | multiple_init.py:44:9:44:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:45:9:45:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:31:5:31:23 | Function __init__ | D1.__init__ | multiple_init.py:36:5:36:23 | Function __init__ | D2.__init__ |
| multiple_init.py:84:5:84:23 | Function __init__ | This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_init.py:80:5:80:23 | Function __init__ | F3.__init__ | multiple_init.py:86:9:86:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:87:9:87:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:75:5:75:23 | Function __init__ | F2.__init__ | multiple_init.py:80:5:80:23 | Function __init__ | F3.__init__ |
| multiple_init.py:111:5:111:23 | Function __init__ | This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_init.py:92:5:92:23 | Function __init__ | G1.__init__ | multiple_init.py:113:9:113:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:114:9:114:25 | ControlFlowNode for Attribute() | this call | multiple_init.py:96:5:96:23 | Function __init__ | G2.__init__ | multiple_init.py:101:5:101:23 | Function __init__ | G3.__init__ |

View File

@@ -0,0 +1,2 @@
query: Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,117 @@
#Calling a method multiple times by using explicit calls when a base uses super()
class Base(object):
def __init__(self):
print("Base init")
class C1(Base):
def __init__(self):
print("C1 init")
super(C1, self).__init__()
class C2(Base):
def __init__(self):
print("C2 init")
super(C2, self).__init__() #When `type(self) == C3` this calls `C1.__init__`
class C3(C2, C1):
def __init__(self): # $ Alert
print("C3 init")
C1.__init__(self)
C2.__init__(self)
C3()
#Calling a method multiple times by using explicit calls when a base inherits from other base
class D1(object):
def __init__(self):
print("D1 init")
class D2(D1):
def __init__(self):
print("D2 init")
D1.__init__(self)
class D3(D2, D1):
def __init__(self): # $ Alert
print("D3 init")
D1.__init__(self)
D2.__init__(self)
D3()
#OK to call object.__init__ multiple times
class E1(object):
def __init__(self):
print("E1 init")
super(E1, self).__init__()
class E2(object):
def __init__(self):
print("E2 init")
object.__init__(self)
class E3(E2, E1):
def __init__(self): # OK: builtin init methods are fine.
E1.__init__(self)
E2.__init__(self)
E3()
class F1:
pass
class F2(F1):
def __init__(self):
print("F2 init")
super().__init__()
class F3(F1):
def __init__(self):
print("F3 init")
class F4(F2, F3):
def __init__(self): # $ Alert # F2's super call calls F3
print("F4 init")
F2.__init__(self)
F3.__init__(self)
F4()
class G1:
def __init__(self):
print("G1 init")
class G2(G1):
def __init__(self):
print("G2 init")
G1.__init__(self)
class G3(G1):
def __init__(self):
print("G3 init")
G1.__init__(self)
class G4(G1):
def __init__(self):
print("G4 init")
G1.__init__(self)
class G5(G2,G3,G4):
def __init__(self): # $ Alert # Only one alert is generated, that mentions the first two calls
print("G5 init")
G2.__init__(self)
G3.__init__(self)
G4.__init__(self)
G5()

View File

@@ -1,76 +0,0 @@
#Calling a method multiple times by using explicit calls when a base uses super()
class Base(object):
def __init__(self):
pass
class C1(Base):
def __init__(self):
super(C1, self).__init__()
class C2(Base):
def __init__(self):
super(C2, self).__init__() #When `type(self) == C3` this calls `C1.__init__`
class C3(C2, C1):
def __init__(self):
C1.__init__(self)
C2.__init__(self)
#Calling a method multiple times by using explicit calls when a base inherits from other base
class D1(object):
def __init__(self):
pass
class D2(D1):
def __init__(self):
D1.__init__(self)
class D3(D2, D1):
def __init__(self):
D1.__init__(self)
D2.__init__(self)
#OK to call object.__init__ multiple times
class E1(object):
def __init__(self):
super(E1, self).__init__()
class E2(object):
def __init__(self):
object.__init__(self)
class E3(E2, E1):
def __init__(self):
E1.__init__(self)
E2.__init__(self)
#Two invocations, but can only be called once
class F1(Base):
def __init__(self, cond):
if cond:
Base.__init__(self)
else:
Base.__init__(self)
#Single call, splitting causes what seems to be multiple invocations.
class F2(Base):
def __init__(self, cond):
if cond:
pass
if cond:
pass
Base.__init__(self)