|
13 | 13 | */ |
14 | 14 |
|
15 | 15 | import python |
16 | | -import Expressions.CallArgs |
| 16 | +import semmle.python.dataflow.new.internal.DataFlowDispatch |
17 | 17 |
|
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 |
29 | 38 | ( |
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." |
31 | 41 | 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 |
33 | 67 | ) |
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