Skip to content

Commit 3e61fb8

Browse files
Modernize signature mismatch
1 parent 1fab97b commit 3e61fb8

File tree

1 file changed

+63
-16
lines changed

1 file changed

+63
-16
lines changed

python/ql/src/Functions/SignatureOverriddenMethod.ql

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,70 @@
1313
*/
1414

1515
import python
16-
import Expressions.CallArgs
16+
import semmle.python.dataflow.new.internal.DataFlowDispatch
1717

18-
from FunctionValue base, PythonFunctionValue derived
19-
where
20-
not exists(base.getACall()) and
21-
not exists(FunctionValue a_derived |
22-
a_derived.overrides(base) and
23-
exists(a_derived.getACall())
24-
) and
25-
not derived.getScope().isSpecialMethod() and
26-
derived.getName() != "__init__" and
27-
derived.isNormalMethod() and
28-
// call to overrides distributed for efficiency
18+
predicate overrides(Function base, Function sub) {
19+
base.getName() = sub.getName() and
20+
base.getScope() = getADirectSuperclass*(sub.getScope())
21+
}
22+
23+
/** Holds if no way to call `base` would be valid for `sub`. The `msg` applies to the `sub method. */
24+
predicate strongSignatureMismatch(Function base, Function sub, string msg) {
25+
overrides(base, sub) and
26+
(
27+
sub.getMinPositionalArguments() > base.getMaxPositionalArguments() and
28+
msg = "requires more positional arguments than overridden $@ allows."
29+
or
30+
sub.getMaxPositionalArguments() < base.getMinPositionalArguments() and
31+
msg = "requires fewer positional arguments than overridden $@ allows."
32+
)
33+
}
34+
35+
/** Holds if there may be some ways to call `base` that would not be valid for `sub`. The `msg` applies to the `sub` method. */
36+
predicate weakSignatureMismatch(Function base, Function sub, string msg) {
37+
overrides(base, sub) and
2938
(
30-
derived.overrides(base) and derived.minParameters() > base.maxParameters()
39+
sub.getMinPositionalArguments() > base.getMinPositionalArguments() and
40+
msg = "requires more positional arguments than overridden $@ may accept."
3141
or
32-
derived.overrides(base) and derived.maxParameters() < base.minParameters()
42+
sub.getMaxPositionalArguments() < base.getMaxPositionalArguments() and
43+
msg = "requires fewer positional arguments than overridden $@ may accept."
44+
or
45+
exists(string arg |
46+
// TODO: positional-only args not considered
47+
// e.g. `def foo(x, y, /, z):` has x,y as positional only args, should not be considered as possible kw args
48+
arg = base.getAnArg().getName() and
49+
not arg = sub.getAnArg().getName() and
50+
not exists(sub.getKwarg()) and
51+
msg = "does not accept keyword argument " + arg + ", which overridden $@ does."
52+
)
53+
or
54+
exists(base.getKwarg()) and
55+
not exists(sub.getKwarg()) and
56+
msg = "does not accept arbitrary keyword arguments, which overridden $@ does."
57+
)
58+
}
59+
60+
predicate ignore(Function f) {
61+
isClassmethod(f)
62+
or
63+
exists(Function g |
64+
g.getScope() = f.getScope() and
65+
g.getName() = f.getName() and
66+
g != f
3367
)
34-
select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.",
35-
base, "overridden method"
68+
}
69+
70+
from Function base, Function sub, string msg
71+
where
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
77+
not sub.isSpecialMethod() and
78+
sub.getName() != "__init__" and
79+
not ignore(sub) and
80+
not ignore(base) and
81+
strongSignatureMismatch(base, sub, msg)
82+
select sub, "This method " + msg, base, base.getQualifiedName()

0 commit comments

Comments
 (0)