Skip to content

Commit c5b79fa

Browse files
Update multiple calls queries to include call targets in alert message
1 parent 2e6f35b commit c5b79fa

File tree

3 files changed

+81
-23
lines changed

3 files changed

+81
-23
lines changed

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import semmle.python.ApiGraphs
55
import semmle.python.dataflow.new.internal.DataFlowDispatch
66
import codeql.util.Option
77

8-
predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, string name) {
9-
exists(DataFlow::MethodCallNode call1, DataFlow::MethodCallNode call2, Class cls |
8+
predicate multipleCallsToSuperclassMethod(
9+
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
10+
DataFlow::MethodCallNode call2, string name
11+
) {
12+
exists(Class cls |
1013
meth.getName() = name and
1114
meth.getScope() = cls and
12-
call1.asExpr() != call2.asExpr() and
15+
call1.getLocation().toString() < call2.getLocation().toString() and
1316
calledMulti = getASuperCallTargetFromCall(cls, meth, call1, name) and
1417
calledMulti = getASuperCallTargetFromCall(cls, meth, call2, name) and
1518
nonTrivial(calledMulti)
@@ -18,23 +21,44 @@ predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, s
1821

1922
Function getASuperCallTargetFromCall(
2023
Class mroBase, Function meth, DataFlow::MethodCallNode call, string name
24+
) {
25+
exists(Function target | target = getDirectSuperCallTargetFromCall(mroBase, meth, call, name) |
26+
result = target
27+
or
28+
result = getASuperCallTargetFromCall(mroBase, target, _, name)
29+
)
30+
}
31+
32+
Function getDirectSuperCallTargetFromCall(
33+
Class mroBase, Function meth, DataFlow::MethodCallNode call, string name
2134
) {
2235
meth = call.getScope() and
2336
getADirectSuperclass*(mroBase) = meth.getScope() and
2437
meth.getName() = name and
2538
call.calls(_, name) and
26-
exists(Class targetCls | result = getASuperCallTargetFromClass(mroBase, targetCls, name) |
39+
mroBase = getADirectSubclass*(meth.getScope()) and
40+
exists(Class targetCls |
41+
// the differences between 0-arg and 2-arg super is not considered; we assume each super uses the mro of the instance `self`
2742
superCall(call, _) and
28-
targetCls = getNextClassInMroKnownStartingClass(meth.getScope(), mroBase)
43+
targetCls = getNextClassInMroKnownStartingClass(meth.getScope(), mroBase) and
44+
result = findFunctionAccordingToMroKnownStartingClass(targetCls, mroBase, name)
2945
or
30-
callsMethodOnClassWithSelf(meth, call, targetCls, _)
46+
// targetCls is the mro base for this lookup.
47+
// note however that if the call we find uses super(), that still uses the mro of the instance `self` will sill be used
48+
// assuming it's 0-arg or is 2-arg with `self` as second arg.
49+
callsMethodOnClassWithSelf(meth, call, targetCls, _) and
50+
result = findFunctionAccordingToMroKnownStartingClass(targetCls, targetCls, name)
3151
)
3252
}
3353

3454
Function getASuperCallTargetFromClass(Class mroBase, Class cls, string name) {
3555
exists(Function target |
3656
target = findFunctionAccordingToMroKnownStartingClass(cls, mroBase, name) and
37-
(result = target or result = getASuperCallTargetFromCall(mroBase, target, _, name))
57+
(
58+
result = target
59+
or
60+
result = getASuperCallTargetFromCall(mroBase, target, _, name)
61+
)
3862
)
3963
}
4064

python/ql/src/Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.ql

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,34 @@
1414
import python
1515
import MethodCallOrder
1616

17-
predicate multipleCallsToSuperclassDel(Function meth, Function calledMulti) {
18-
multipleCallsToSuperclassMethod(meth, calledMulti, "__del__")
17+
predicate multipleCallsToSuperclassDel(
18+
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
19+
DataFlow::MethodCallNode call2
20+
) {
21+
multipleCallsToSuperclassMethod(meth, calledMulti, call1, call2, "__del__")
1922
}
2023

21-
from Function meth, Function calledMulti
24+
from
25+
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
26+
DataFlow::MethodCallNode call2, Function target1, Function target2, string msg
2227
where
23-
multipleCallsToSuperclassDel(meth, calledMulti) and
24-
// Don't alert for multiple calls to a superclass del when a subclass will do.
28+
multipleCallsToSuperclassDel(meth, calledMulti, call1, call2) and
29+
// Only alert for the lowest method in the hierarchy that both calls will call.
2530
not exists(Function subMulti |
26-
multipleCallsToSuperclassDel(meth, subMulti) and
31+
multipleCallsToSuperclassDel(meth, subMulti, _, _) and
2732
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
33+
) and
34+
target1 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call1, _) and
35+
target2 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call2, _) and
36+
(
37+
target1 != target2 and
38+
msg =
39+
"This deletion method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
40+
or
41+
target1 = target2 and
42+
// The targets themselves are called multiple times (either is calledMulti, or something earlier in the MRO)
43+
// Mentioning them again would be redundant.
44+
msg = "This deletion method calls $@ multiple times, via $@ and $@."
2845
)
29-
select meth, "This delete method calls $@ multiple times.", calledMulti,
30-
calledMulti.getQualifiedName()
46+
select meth, msg, calledMulti, calledMulti.getQualifiedName(), call1, "this call", call2,
47+
"this call", target1, target1.getQualifiedName(), target2, target2.getQualifiedName()

python/ql/src/Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.ql

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,34 @@
1414
import python
1515
import MethodCallOrder
1616

17-
predicate multipleCallsToSuperclassInit(Function meth, Function calledMulti) {
18-
multipleCallsToSuperclassMethod(meth, calledMulti, "__init__")
17+
predicate multipleCallsToSuperclassInit(
18+
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
19+
DataFlow::MethodCallNode call2
20+
) {
21+
multipleCallsToSuperclassMethod(meth, calledMulti, call1, call2, "__init__")
1922
}
2023

21-
from Function meth, Function calledMulti
24+
from
25+
Function meth, Function calledMulti, DataFlow::MethodCallNode call1,
26+
DataFlow::MethodCallNode call2, Function target1, Function target2, string msg
2227
where
23-
multipleCallsToSuperclassInit(meth, calledMulti) and
24-
// Don't alert for multiple calls to a superclass init when a subclass will do.
28+
multipleCallsToSuperclassInit(meth, calledMulti, call1, call2) and
29+
// Only alert for the lowest method in the hierarchy that both calls will call.
2530
not exists(Function subMulti |
26-
multipleCallsToSuperclassInit(meth, subMulti) and
31+
multipleCallsToSuperclassInit(meth, subMulti, _, _) and
2732
calledMulti.getScope() = getADirectSuperclass+(subMulti.getScope())
33+
) and
34+
target1 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call1, _) and
35+
target2 = getDirectSuperCallTargetFromCall(meth.getScope(), meth, call2, _) and
36+
(
37+
target1 != target2 and
38+
msg =
39+
"This initialization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
40+
or
41+
target1 = target2 and
42+
// The targets themselves are called multiple times (either is calledMulti, or something earlier in the MRO)
43+
// Mentioning them again would be redundant.
44+
msg = "This initialization method calls $@ multiple times, via $@ and $@."
2845
)
29-
select meth, "This initialization method calls $@ multiple times.", calledMulti,
30-
calledMulti.getQualifiedName()
46+
select meth, msg, calledMulti, calledMulti.getQualifiedName(), call1, "this call", call2,
47+
"this call", target1, target1.getQualifiedName(), target2, target2.getQualifiedName()

0 commit comments

Comments
 (0)