mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Change implenetation of missing calls to use getASuperCallTarget, and change alerts to alert on the class and provide clearer information, using optional location links.
This commit is contained in:
@@ -3,32 +3,38 @@
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import codeql.util.Option
|
||||
|
||||
predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, string name) {
|
||||
exists(DataFlow::MethodCallNode call1, DataFlow::MethodCallNode call2, Class cls |
|
||||
meth.getName() = name and
|
||||
meth.getScope() = cls and
|
||||
call1.asExpr() != call2.asExpr() and
|
||||
calledMulti = getASuperCallTarget(cls, meth, call1) and
|
||||
calledMulti = getASuperCallTarget(cls, meth, call2) and
|
||||
calledMulti = getASuperCallTargetFromCall(cls, meth, call1, name) and
|
||||
calledMulti = getASuperCallTargetFromCall(cls, meth, call2, name) and
|
||||
nonTrivial(calledMulti)
|
||||
)
|
||||
}
|
||||
|
||||
Function getASuperCallTarget(Class mroBase, Function meth, DataFlow::MethodCallNode call) {
|
||||
Function getASuperCallTargetFromCall(
|
||||
Class mroBase, Function meth, DataFlow::MethodCallNode call, string name
|
||||
) {
|
||||
meth = call.getScope() and
|
||||
getADirectSuperclass*(mroBase) = meth.getScope() and
|
||||
call.calls(_, meth.getName()) and
|
||||
exists(Function target | (result = target or result = getASuperCallTarget(mroBase, target, _)) |
|
||||
meth.getName() = name and
|
||||
call.calls(_, name) and
|
||||
exists(Class targetCls | result = getASuperCallTargetFromClass(mroBase, targetCls, name) |
|
||||
superCall(call, _) and
|
||||
target =
|
||||
findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(meth.getScope(),
|
||||
mroBase), mroBase, meth.getName())
|
||||
targetCls = getNextClassInMroKnownStartingClass(meth.getScope(), mroBase)
|
||||
or
|
||||
exists(Class called |
|
||||
callsMethodOnClassWithSelf(meth, call, called, _) and
|
||||
target = findFunctionAccordingToMroKnownStartingClass(called, mroBase, meth.getName())
|
||||
)
|
||||
callsMethodOnClassWithSelf(meth, call, targetCls, _)
|
||||
)
|
||||
}
|
||||
|
||||
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))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,31 +84,83 @@ predicate callsMethodOnUnknownClassWithSelf(Function meth, string name) {
|
||||
)
|
||||
}
|
||||
|
||||
predicate mayProceedInMro(Class a, Class b, Class mroStart) {
|
||||
b = getNextClassInMroKnownStartingClass(a, mroStart)
|
||||
or
|
||||
exists(Class mid |
|
||||
mid = getNextClassInMroKnownStartingClass(a, mroStart) and
|
||||
mayProceedInMro(mid, b, mroStart)
|
||||
predicate missingCallToSuperclassMethod(Class base, Function shouldCall, string name) {
|
||||
base.getName() = name and
|
||||
shouldCall.getName() = name and
|
||||
base = getADirectSuperclass*(base.getScope()) 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)
|
||||
}
|
||||
|
||||
predicate missingCallToSuperclassMethodRestricted(Class base, Function shouldCall, string name) {
|
||||
missingCallToSuperclassMethod(base, shouldCall, name) and
|
||||
not exists(Class subBase |
|
||||
subBase = getADirectSubclass+(base) and
|
||||
missingCallToSuperclassMethod(subBase, shouldCall, name)
|
||||
) and
|
||||
not exists(Function superShouldCall |
|
||||
superShouldCall.getScope() = getADirectSuperclass+(shouldCall.getScope()) and
|
||||
missingCallToSuperclassMethod(base, superShouldCall, name)
|
||||
)
|
||||
}
|
||||
|
||||
predicate missingCallToSuperclassMethod(
|
||||
Function base, Function shouldCall, Class mroStart, string name
|
||||
) {
|
||||
base.getName() = name and
|
||||
shouldCall.getName() = name and
|
||||
not callsSuper(base) and
|
||||
not callsMethodOnUnknownClassWithSelf(base, name) and
|
||||
nonTrivial(shouldCall) and
|
||||
base.getScope() = getADirectSuperclass*(mroStart) and
|
||||
mayProceedInMro(base.getScope(), shouldCall.getScope(), mroStart) and
|
||||
not exists(Class called |
|
||||
(
|
||||
callsMethodOnClassWithSelf(base, _, called, name)
|
||||
or
|
||||
callsMethodOnClassWithSelf(findFunctionAccordingToMro(mroStart, name), _, called, name)
|
||||
) and
|
||||
shouldCall.getScope() = getADirectSuperclass*(called)
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
private module FunctionOption = Option<Function>;
|
||||
|
||||
class FunctionOption extends FunctionOption::Option {
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.asSome()
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
or
|
||||
this.isNone() and
|
||||
filepath = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
|
||||
string getQualifiedName() {
|
||||
result = this.asSome().getQualifiedName()
|
||||
or
|
||||
this.isNone() and
|
||||
result = ""
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -15,21 +15,35 @@
|
||||
import python
|
||||
import MethodCallOrder
|
||||
|
||||
predicate missingCallToSuperclassDel(Function base, Function shouldCall, Class mroStart) {
|
||||
missingCallToSuperclassMethod(base, shouldCall, mroStart, "__del__")
|
||||
Function getDelMethod(Class c) {
|
||||
result = c.getAMethod() and
|
||||
result.getName() = "__del__"
|
||||
}
|
||||
|
||||
from Function base, Function shouldCall, Class mroStart, string msg
|
||||
from Class base, Function shouldCall, FunctionOption possibleIssue, string msg
|
||||
where
|
||||
missingCallToSuperclassDel(base, shouldCall, mroStart) and
|
||||
(
|
||||
// Simple case: the method that should be called is directly overridden
|
||||
mroStart = base.getScope() and
|
||||
msg = "This delete method does not call $@, which may leave $@ not properly cleaned up."
|
||||
or
|
||||
// Only alert for a different mro base if there are no alerts for direct overrides
|
||||
not missingCallToSuperclassDel(base, _, base.getScope()) and
|
||||
msg =
|
||||
"This delete method does not call super().__del__, which may cause $@ to be missed during the cleanup of $@."
|
||||
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 destruction. ($@ may be missing a call to super().__del__)"
|
||||
or
|
||||
possiblyMissingSuper.isNone() and
|
||||
(
|
||||
possibleIssue.asSome() = getDelMethod(base) and
|
||||
msg =
|
||||
"This class does not call $@ during destruction. ($@ may be missing a call to a base class __del__)"
|
||||
or
|
||||
not getDelMethod(base) and
|
||||
possibleIssue.isNone() and
|
||||
msg =
|
||||
"This class does not call $@ during destruction. (The class lacks an __del__ method to ensure every base class __del__ is called.)"
|
||||
)
|
||||
)
|
||||
)
|
||||
select base, msg, shouldCall, shouldCall.getQualifiedName(), mroStart, mroStart.getName()
|
||||
select base, msg, shouldCall, shouldCall.getQualifiedName(), possibleIssue,
|
||||
possibleIssue.getQualifiedName()
|
||||
|
||||
@@ -14,21 +14,30 @@
|
||||
import python
|
||||
import MethodCallOrder
|
||||
|
||||
predicate missingCallToSuperclassInit(Function base, Function shouldCall, Class mroStart) {
|
||||
missingCallToSuperclassMethod(base, shouldCall, mroStart, "__init__")
|
||||
}
|
||||
|
||||
from Function base, Function shouldCall, Class mroStart, string msg
|
||||
from Class base, Function shouldCall, FunctionOption possibleIssue, string msg
|
||||
where
|
||||
missingCallToSuperclassInit(base, shouldCall, mroStart) and
|
||||
(
|
||||
// Simple case: the method that should be called is directly overridden
|
||||
mroStart = base.getScope() and
|
||||
msg = "This initialization method does not call $@, which may leave $@ partially initialized."
|
||||
or
|
||||
// Only alert for a different mro base if there are no alerts for direct overrides
|
||||
not missingCallToSuperclassInit(base, _, base.getScope()) and
|
||||
msg =
|
||||
"This initialization method does not call super().__init__, which may cause $@ to be missed during the initialization of $@."
|
||||
not exists(Function newMethod | newMethod = base.getAMethod() and newMethod.getName() = "__new__") and
|
||||
exists(FunctionOption possiblyMissingSuper |
|
||||
missingCallToSuperclassMethodRestricted(base, shouldCall, "__init__") and
|
||||
possiblyMissingSuper = getPossibleMissingSuperOption(base, shouldCall, "__init__") and
|
||||
(
|
||||
not possiblyMissingSuper.isNone() and
|
||||
possibleIssue = possiblyMissingSuper 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(), mroStart, mroStart.getName()
|
||||
select base, msg, shouldCall, shouldCall.getQualifiedName(), possibleIssue,
|
||||
possibleIssue.getQualifiedName()
|
||||
|
||||
Reference in New Issue
Block a user