1313 */
1414
1515import python
16+ import semmle.python.dataflow.new.DataFlow
1617import semmle.python.dataflow.new.internal.DataFlowDispatch
1718
1819predicate overrides ( Function base , Function sub ) {
1920 base .getName ( ) = sub .getName ( ) and
20- base .getScope ( ) = getADirectSuperclass * ( sub .getScope ( ) )
21+ base .getScope ( ) = getADirectSuperclass + ( sub .getScope ( ) )
22+ }
23+
24+ bindingset [ num, str]
25+ string plural ( int num , string str ) {
26+ num = 1 and result = "1 " + str
27+ or
28+ num != 1 and result = num .toString ( ) + " " + str + "s"
2129}
2230
2331/** Holds if no way to call `base` would be valid for `sub`. The `msg` applies to the `sub method. */
2432predicate strongSignatureMismatch ( Function base , Function sub , string msg ) {
2533 overrides ( base , sub ) and
2634 (
2735 sub .getMinPositionalArguments ( ) > base .getMaxPositionalArguments ( ) and
28- msg = "requires more positional arguments than overridden $@ allows."
36+ msg =
37+ "requires " +
38+ plural ( sub .getMinPositionalArguments ( ) - base .getMaxPositionalArguments ( ) ,
39+ "more positional argument" ) + " than overridden $@ allows."
2940 or
3041 sub .getMaxPositionalArguments ( ) < base .getMinPositionalArguments ( ) and
31- msg = "requires fewer positional arguments than overridden $@ allows."
42+ msg =
43+ "requires " +
44+ plural ( base .getMinPositionalArguments ( ) - sub .getMaxPositionalArguments ( ) ,
45+ "fewer positional argument" ) + " than overridden $@ allows."
3246 )
3347}
3448
@@ -37,18 +51,26 @@ predicate weakSignatureMismatch(Function base, Function sub, string msg) {
3751 overrides ( base , sub ) and
3852 (
3953 sub .getMinPositionalArguments ( ) > base .getMinPositionalArguments ( ) and
40- msg = "requires more positional arguments than overridden $@ may accept."
54+ msg =
55+ "requires " +
56+ plural ( sub .getMinPositionalArguments ( ) - base .getMinPositionalArguments ( ) ,
57+ "more positional argument" ) + "than some possible calls to overridden $@."
4158 or
4259 sub .getMaxPositionalArguments ( ) < base .getMaxPositionalArguments ( ) and
43- msg = "requires fewer positional arguments than overridden $@ may accept."
60+ msg =
61+ "requires " +
62+ plural ( base .getMaxPositionalArguments ( ) - sub .getMaxPositionalArguments ( ) ,
63+ "fewer positional argument" ) + " than some possible calls to overridden $@."
4464 or
65+ sub .getMinPositionalArguments ( ) <= base .getMinPositionalArguments ( ) and
66+ sub .getMaxPositionalArguments ( ) >= base .getMaxPositionalArguments ( ) and
4567 exists ( string arg |
4668 // TODO: positional-only args not considered
4769 // e.g. `def foo(x, y, /, z):` has x,y as positional only args, should not be considered as possible kw args
4870 arg = base .getAnArg ( ) .getName ( ) and
4971 not arg = sub .getAnArg ( ) .getName ( ) and
5072 not exists ( sub .getKwarg ( ) ) and
51- msg = "does not accept keyword argument " + arg + ", which overridden $@ does."
73+ msg = "does not accept keyword argument ` " + arg + "` , which overridden $@ does."
5274 )
5375 or
5476 exists ( base .getKwarg ( ) ) and
@@ -67,16 +89,100 @@ predicate ignore(Function f) {
6789 )
6890}
6991
70- from Function base , Function sub , string msg
92+ Function resolveCall ( Call call ) {
93+ exists ( DataFlowCall dfc | call = dfc .getNode ( ) .( CallNode ) .getNode ( ) |
94+ result = viableCallable ( dfc ) .( DataFlowFunction ) .getScope ( )
95+ )
96+ }
97+
98+ predicate callViableForEither ( Function base , Function sub , Call call ) {
99+ overrides ( base , sub ) and
100+ base = resolveCall ( call ) and
101+ sub = resolveCall ( call )
102+ }
103+
104+ predicate matchingStatic ( Function base , Function sub ) {
105+ overrides ( base , sub ) and
106+ (
107+ isStaticmethod ( base ) and
108+ isStaticmethod ( sub )
109+ or
110+ not isStaticmethod ( base ) and
111+ not isStaticmethod ( sub )
112+ )
113+ }
114+
115+ int extraSelfArg ( Function func ) { if isStaticmethod ( func ) then result = 0 else result = 1 }
116+
117+ predicate callMatchesSignature ( Function func , Call call ) {
118+ (
119+ call .getPositionalArgumentCount ( ) + extraSelfArg ( func ) >= func .getMinPositionalArguments ( )
120+ or
121+ exists ( call .getStarArg ( ) )
122+ or
123+ exists ( call .getKwargs ( ) )
124+ ) and
125+ call .getPositionalArgumentCount ( ) + extraSelfArg ( func ) <= func .getMaxPositionalArguments ( ) and
126+ (
127+ exists ( func .getKwarg ( ) )
128+ or
129+ forall ( string name | name = call .getANamedArgumentName ( ) | exists ( func .getArgByName ( name ) ) )
130+ )
131+ }
132+
133+ Call getASignatureMismatchWitness ( Function base , Function sub ) {
134+ callViableForEither ( base , sub , result ) and
135+ callMatchesSignature ( base , result ) and
136+ not callMatchesSignature ( sub , result )
137+ }
138+
139+ Call chooseASignatureMismatchWitnessInFile ( Function base , Function sub , File file ) {
140+ result =
141+ min ( Call c |
142+ c = getASignatureMismatchWitness ( base , sub ) and
143+ c .getLocation ( ) .getFile ( ) = file
144+ |
145+ c order by c .getLocation ( ) .getStartLine ( ) , c .getLocation ( ) .getStartColumn ( )
146+ )
147+ }
148+
149+ Call chooseASignatureMismatchWitness ( Function base , Function sub ) {
150+ exists ( getASignatureMismatchWitness ( base , sub ) ) and
151+ (
152+ result = chooseASignatureMismatchWitnessInFile ( base , sub , base .getLocation ( ) .getFile ( ) )
153+ or
154+ not exists ( Call c |
155+ c = getASignatureMismatchWitness ( base , sub ) and
156+ c .getLocation ( ) .getFile ( ) = base .getLocation ( ) .getFile ( )
157+ ) and
158+ result = chooseASignatureMismatchWitnessInFile ( base , sub , base .getLocation ( ) .getFile ( ) )
159+ or
160+ not exists ( Call c |
161+ c = getASignatureMismatchWitness ( base , sub ) and
162+ c .getLocation ( ) .getFile ( ) = [ base , sub ] .getLocation ( ) .getFile ( )
163+ ) and
164+ result =
165+ min ( Call c |
166+ c = getASignatureMismatchWitness ( base , sub )
167+ |
168+ c
169+ order by
170+ c .getLocation ( ) .getFile ( ) .getAbsolutePath ( ) , c .getLocation ( ) .getStartLine ( ) ,
171+ c .getLocation ( ) .getStartColumn ( )
172+ )
173+ )
174+ }
175+
176+ from Function base , Function sub , string msg , string extraMsg , Call call
71177where
72- // not exists(base.getACall()) and
73- // not exists(FunctionValue a_derived |
74- // a_derived.overrides(base) and
75- // exists(a_derived.getACall())
76- // ) and
77178 not sub .isSpecialMethod ( ) and
78179 sub .getName ( ) != "__init__" and
79180 not ignore ( sub ) and
80181 not ignore ( base ) and
81- strongSignatureMismatch ( base , sub , msg )
82- select sub , "This method " + msg , base , base .getQualifiedName ( )
182+ matchingStatic ( base , sub ) and
183+ weakSignatureMismatch ( base , sub , msg ) and
184+ //msg = " has a different signature to $@." and
185+ call = chooseASignatureMismatchWitness ( base , sub ) and
186+ extraMsg =
187+ " $@ correctly calls the base method, but does not match the signature of the overriding method."
188+ select sub , "This method " + msg + extraMsg , base , base .getQualifiedName ( ) , call , "This call"
0 commit comments