diff --git a/python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll b/python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll index bd98f3fb0fb..c0f35658788 100644 --- a/python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll +++ b/python/ql/src/Classes/CallsToInitDel/MethodCallOrder.qll @@ -14,12 +14,23 @@ predicate multipleCallsToSuperclassMethod( meth.getName() = name and meth.getScope() = cls and locationBefore(call1.getLocation(), call2.getLocation()) and - calledMulti = getASuperCallTargetFromCall(cls, meth, call1, name) and - calledMulti = getASuperCallTargetFromCall(cls, meth, call2, name) 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) { @@ -176,40 +187,7 @@ Function getPossibleMissingSuper(Class base, Function shouldCall, string name) { ) } -private module FunctionOption = Option; - -/** An optional `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 - } - - /** Gets the qualified name of this function. */ - string getQualifiedName() { - result = this.asSome().getQualifiedName() - or - this.isNone() and - result = "" - } -} +private class FunctionOption = LocatableOption::Option; /** Gets the result of `getPossibleMissingSuper`, or None if none exists. */ bindingset[name] diff --git a/python/ql/test/query-tests/Classes/multiple/multiple-init/multiple_init.py b/python/ql/test/query-tests/Classes/multiple/multiple-init/multiple_init.py index 59efb28e691..faa0a71c6db 100644 --- a/python/ql/test/query-tests/Classes/multiple/multiple-init/multiple_init.py +++ b/python/ql/test/query-tests/Classes/multiple/multiple-init/multiple_init.py @@ -86,4 +86,32 @@ class F4(F2, F3): F2.__init__(self) F3.__init__(self) -F4() \ No newline at end of file +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() \ No newline at end of file