Increase precision in detecting call matches signature

This commit is contained in:
Joe Farebrother
2025-09-02 12:02:08 +01:00
parent 125c6534b7
commit 318d1cd392
3 changed files with 20 additions and 6 deletions

View File

@@ -160,15 +160,27 @@ int extraSelfArg(Function func) { if isStaticmethod(func) then result = 0 else r
predicate callMatchesSignature(Function func, Call call) {
(
// TODO: This is not fully precise.
// For example, it does not detect that a method `def foo(self,x,y)` is matched by a call `obj.foo(1,y=2)`
// since y is passed in the call as a keyword argument, but still counts toward a positional argument of the method.
call.getPositionalArgumentCount() + extraSelfArg(func) >= func.getMinPositionalArguments()
// Each parameter of the function is accounted for in the call
forall(Parameter param, int i | param = func.getArg(i) |
// self arg
i = 0 and not isStaticmethod(func)
or
// positional arg
i - extraSelfArg(func) < call.getPositionalArgumentCount()
or
// has default
exists(param.getDefault())
or
// keyword arg
call.getANamedArgumentName() = param.getName()
)
or
// arbitrary varargs or kwargs
exists(call.getStarArg())
or
exists(call.getKwargs())
) and
// No excess parameters
call.getPositionalArgumentCount() + extraSelfArg(func) <= func.getMaxPositionalArguments() and
(
exists(func.getKwarg())

View File

@@ -8,4 +8,6 @@
| test.py:133:5:133:28 | Function meth5 | This method requires at most 3 positional arguments, whereas overridden $@ requires at least 4. | test.py:90:5:90:34 | Function meth5 | Base2.meth5 | file://:0:0:0:0 | (none) | This call |
| test.py:135:5:135:23 | Function meth6 | This method requires 2 positional arguments, whereas overridden $@ may be called with arbitrarily many. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:92:5:92:28 | Function meth6 | Base2.meth6 | test.py:113:9:113:27 | Attribute() | This call |
| test.py:137:5:137:28 | Function meth7 | This method requires at least 2 positional arguments, whereas overridden $@ may be called with 1. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:94:5:94:25 | Function meth7 | Base2.meth7 | test.py:114:9:114:20 | Attribute() | This call |
| test.py:139:5:139:26 | Function meth8 | This method does not accept keyword argument `y`, which overridden $@ does. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:96:5:96:26 | Function meth8 | Base2.meth8 | test.py:115:9:115:25 | Attribute() | This call |
| test.py:147:5:147:21 | Function meth12 | This method does not accept arbitrary keyword arguments, which overridden $@ does. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:104:5:104:31 | Function meth12 | Base2.meth12 | test.py:119:9:119:24 | Attribute() | This call |
| test.py:149:5:149:27 | Function meth13 | This method does not accept keyword argument `x`, which overridden $@ does. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:106:5:106:27 | Function meth13 | Base2.meth13 | test.py:120:9:120:24 | Attribute() | This call |

View File

@@ -136,7 +136,7 @@ class Derrived2(Base2):
def meth7(self, x, *ys): pass # $Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with 1 arg only)
def meth8(self, x, z): pass # $MISSING:Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with arg named y), however the call to meth8 that witnesses this is not detected as a valid call to Base2.meth8.
def meth8(self, x, z): pass # $Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with arg named y)
def meth9(self, x, z): pass # No alert (never called with wrong keyword arg)
@@ -146,4 +146,4 @@ class Derrived2(Base2):
def meth12(self): pass # $Alert[py/inheritance/signature-mismatch] # call including extra kwarg invalid
def meth13(self, /, y): pass # $MISSING:Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with arg named x), however meth13 is incorrectly detected as having 2 minimum positional arguments, whereas x is kw-only; resulting in the witness call not being detected as a valid call to Base2.meth13.
def meth13(self, /, y): pass # $Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with arg named x), however meth13 is incorrectly detected as having 2 minimum positional arguments, whereas x is kw-only; resulting in the witness call not being detected as a valid call to Base2.meth13.