mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
207
python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll
Normal file
207
python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll
Normal 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()
|
||||
}
|
||||
52
python/ql/src/Classes/CallsToInitDel/MissingCallToDel.qhelp
Normal file
52
python/ql/src/Classes/CallsToInitDel/MissingCallToDel.qhelp
Normal 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>
|
||||
49
python/ql/src/Classes/CallsToInitDel/MissingCallToDel.ql
Normal file
49
python/ql/src/Classes/CallsToInitDel/MissingCallToDel.ql
Normal 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()
|
||||
50
python/ql/src/Classes/CallsToInitDel/MissingCallToInit.qhelp
Normal file
50
python/ql/src/Classes/CallsToInitDel/MissingCallToInit.qhelp
Normal 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>
|
||||
41
python/ql/src/Classes/CallsToInitDel/MissingCallToInit.ql
Normal file
41
python/ql/src/Classes/CallsToInitDel/MissingCallToInit.ql
Normal 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()
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
@@ -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):
|
||||
@@ -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):
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
deprecated module;
|
||||
|
||||
import python
|
||||
|
||||
// Helper predicates for multiple call to __init__/__del__ queries.
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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__ |
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Classes/MissingCallToDel.ql
|
||||
query: Classes/CallsToInitDel/MissingCallToDel.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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
|
||||
|
||||
@@ -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__ |
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Classes/MissingCallToInit.ql
|
||||
query: Classes/CallsToInitDel/MissingCallToInit.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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()
|
||||
@@ -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__ |
|
||||
@@ -1 +0,0 @@
|
||||
Classes/SuperclassDelCalledMultipleTimes.ql
|
||||
@@ -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__ |
|
||||
@@ -1 +0,0 @@
|
||||
Classes/SuperclassInitCalledMultipleTimes.ql
|
||||
@@ -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__ |
|
||||
@@ -0,0 +1,2 @@
|
||||
query: Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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
|
||||
@@ -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__ |
|
||||
@@ -0,0 +1,2 @@
|
||||
query: Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user