|
3 | 3 | import python |
4 | 4 | import semmle.python.ApiGraphs |
5 | 5 | import semmle.python.dataflow.new.internal.DataFlowDispatch |
| 6 | +import codeql.util.Option |
6 | 7 |
|
7 | 8 | predicate multipleCallsToSuperclassMethod(Function meth, Function calledMulti, string name) { |
8 | 9 | exists(DataFlow::MethodCallNode call1, DataFlow::MethodCallNode call2, Class cls | |
9 | 10 | meth.getName() = name and |
10 | 11 | meth.getScope() = cls and |
11 | 12 | call1.asExpr() != call2.asExpr() and |
12 | | - calledMulti = getASuperCallTarget(cls, meth, call1) and |
13 | | - calledMulti = getASuperCallTarget(cls, meth, call2) and |
| 13 | + calledMulti = getASuperCallTargetFromCall(cls, meth, call1, name) and |
| 14 | + calledMulti = getASuperCallTargetFromCall(cls, meth, call2, name) and |
14 | 15 | nonTrivial(calledMulti) |
15 | 16 | ) |
16 | 17 | } |
17 | 18 |
|
18 | | -Function getASuperCallTarget(Class mroBase, Function meth, DataFlow::MethodCallNode call) { |
| 19 | +Function getASuperCallTargetFromCall( |
| 20 | + Class mroBase, Function meth, DataFlow::MethodCallNode call, string name |
| 21 | +) { |
19 | 22 | meth = call.getScope() and |
20 | 23 | getADirectSuperclass*(mroBase) = meth.getScope() and |
21 | | - call.calls(_, meth.getName()) and |
22 | | - exists(Function target | (result = target or result = getASuperCallTarget(mroBase, target, _)) | |
| 24 | + meth.getName() = name and |
| 25 | + call.calls(_, name) and |
| 26 | + exists(Class targetCls | result = getASuperCallTargetFromClass(mroBase, targetCls, name) | |
23 | 27 | superCall(call, _) and |
24 | | - target = |
25 | | - findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(meth.getScope(), |
26 | | - mroBase), mroBase, meth.getName()) |
| 28 | + targetCls = getNextClassInMroKnownStartingClass(meth.getScope(), mroBase) |
27 | 29 | or |
28 | | - exists(Class called | |
29 | | - callsMethodOnClassWithSelf(meth, call, called, _) and |
30 | | - target = findFunctionAccordingToMroKnownStartingClass(called, mroBase, meth.getName()) |
31 | | - ) |
| 30 | + callsMethodOnClassWithSelf(meth, call, targetCls, _) |
| 31 | + ) |
| 32 | +} |
| 33 | + |
| 34 | +Function getASuperCallTargetFromClass(Class mroBase, Class cls, string name) { |
| 35 | + exists(Function target | |
| 36 | + target = findFunctionAccordingToMroKnownStartingClass(cls, mroBase, name) and |
| 37 | + (result = target or result = getASuperCallTargetFromCall(mroBase, target, _, name)) |
32 | 38 | ) |
33 | 39 | } |
34 | 40 |
|
@@ -78,31 +84,83 @@ predicate callsMethodOnUnknownClassWithSelf(Function meth, string name) { |
78 | 84 | ) |
79 | 85 | } |
80 | 86 |
|
81 | | -predicate mayProceedInMro(Class a, Class b, Class mroStart) { |
82 | | - b = getNextClassInMroKnownStartingClass(a, mroStart) |
83 | | - or |
84 | | - exists(Class mid | |
85 | | - mid = getNextClassInMroKnownStartingClass(a, mroStart) and |
86 | | - mayProceedInMro(mid, b, mroStart) |
87 | | - ) |
88 | | -} |
89 | | - |
90 | | -predicate missingCallToSuperclassMethod( |
91 | | - Function base, Function shouldCall, Class mroStart, string name |
92 | | -) { |
| 87 | +predicate missingCallToSuperclassMethod(Class base, Function shouldCall, string name) { |
93 | 88 | base.getName() = name and |
94 | 89 | shouldCall.getName() = name and |
95 | | - not callsSuper(base) and |
96 | | - not callsMethodOnUnknownClassWithSelf(base, name) and |
| 90 | + base = getADirectSuperclass*(base.getScope()) and |
| 91 | + not shouldCall = getASuperCallTargetFromClass(base, base, name) and |
97 | 92 | nonTrivial(shouldCall) and |
98 | | - base.getScope() = getADirectSuperclass*(mroStart) and |
99 | | - mayProceedInMro(base.getScope(), shouldCall.getScope(), mroStart) and |
100 | | - not exists(Class called | |
101 | | - ( |
102 | | - callsMethodOnClassWithSelf(base, _, called, name) |
103 | | - or |
104 | | - callsMethodOnClassWithSelf(findFunctionAccordingToMro(mroStart, name), _, called, name) |
105 | | - ) and |
106 | | - shouldCall.getScope() = getADirectSuperclass*(called) |
| 93 | + // "Benefit of the doubt" - if somewhere in the chain we call an unknown superclass, assume all the necessary parent methods are called from it |
| 94 | + not callsMethodOnUnknownClassWithSelf(getASuperCallTargetFromClass(base, base, name), name) |
| 95 | +} |
| 96 | + |
| 97 | +predicate missingCallToSuperclassMethodRestricted(Class base, Function shouldCall, string name) { |
| 98 | + missingCallToSuperclassMethod(base, shouldCall, name) and |
| 99 | + not exists(Class subBase | |
| 100 | + subBase = getADirectSubclass+(base) and |
| 101 | + missingCallToSuperclassMethod(subBase, shouldCall, name) |
| 102 | + ) and |
| 103 | + not exists(Function superShouldCall | |
| 104 | + superShouldCall.getScope() = getADirectSuperclass+(shouldCall.getScope()) and |
| 105 | + missingCallToSuperclassMethod(base, superShouldCall, name) |
| 106 | + ) |
| 107 | +} |
| 108 | + |
| 109 | +Function getPossibleMissingSuper(Class base, Function shouldCall, string name) { |
| 110 | + missingCallToSuperclassMethod(base, shouldCall, name) and |
| 111 | + exists(Function baseMethod | |
| 112 | + baseMethod.getScope() = base and |
| 113 | + baseMethod.getName() = name and |
| 114 | + // the base method calls super, so is presumably expecting every method called in the MRO chain to do so |
| 115 | + callsSuper(baseMethod) and |
| 116 | + // result is something that does get called in the chain |
| 117 | + result = getASuperCallTargetFromClass(base, base, name) and |
| 118 | + // it doesn't call super |
| 119 | + not callsSuper(result) and |
| 120 | + // if it did call super, it would resolve to the missing method |
| 121 | + shouldCall = |
| 122 | + findFunctionAccordingToMroKnownStartingClass(getNextClassInMroKnownStartingClass(result |
| 123 | + .getScope(), base), base, name) |
107 | 124 | ) |
108 | 125 | } |
| 126 | + |
| 127 | +private module FunctionOption = Option<Function>; |
| 128 | + |
| 129 | +class FunctionOption extends FunctionOption::Option { |
| 130 | + /** |
| 131 | + * Holds if this element is at the specified location. |
| 132 | + * The location spans column `startcolumn` of line `startline` to |
| 133 | + * column `endcolumn` of line `endline` in file `filepath`. |
| 134 | + * For more information, see |
| 135 | + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). |
| 136 | + */ |
| 137 | + predicate hasLocationInfo( |
| 138 | + string filepath, int startline, int startcolumn, int endline, int endcolumn |
| 139 | + ) { |
| 140 | + this.asSome() |
| 141 | + .getLocation() |
| 142 | + .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) |
| 143 | + or |
| 144 | + this.isNone() and |
| 145 | + filepath = "" and |
| 146 | + startline = 0 and |
| 147 | + startcolumn = 0 and |
| 148 | + endline = 0 and |
| 149 | + endcolumn = 0 |
| 150 | + } |
| 151 | + |
| 152 | + string getQualifiedName() { |
| 153 | + result = this.asSome().getQualifiedName() |
| 154 | + or |
| 155 | + this.isNone() and |
| 156 | + result = "" |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +bindingset[name] |
| 161 | +FunctionOption getPossibleMissingSuperOption(Class base, Function shouldCall, string name) { |
| 162 | + result.asSome() = getPossibleMissingSuper(base, shouldCall, name) |
| 163 | + or |
| 164 | + not exists(getPossibleMissingSuper(base, shouldCall, name)) and |
| 165 | + result.isNone() |
| 166 | +} |
0 commit comments