Skip to content

Commit b973ae8

Browse files
Add additional test cases
1 parent 2ebe9e7 commit b973ae8

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

python/ql/src/Functions/SignatureOverriddenMethod.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ predicate weakSignatureMismatch(Function base, Function sub, string msg) {
108108
exists(string arg |
109109
// TODO: positional-only args not considered
110110
// e.g. `def foo(x, y, /, z):` has x,y as positional only args, should not be considered as possible kw args
111+
// However, this likely does not create FPs, as we require a 'witness' call to generate an alert.
111112
arg = base.getAnArg().getName() and
112113
not arg = sub.getAnArg().getName() and
113114
not exists(sub.getKwarg()) and
@@ -159,6 +160,9 @@ int extraSelfArg(Function func) { if isStaticmethod(func) then result = 0 else r
159160

160161
predicate callMatchesSignature(Function func, Call call) {
161162
(
163+
// TODO: This is not fully precise.
164+
// For example, it does not detect that a method `def foo(self,x,y)` is matched by a call `obj.foo(1,y=2)`
165+
// since y is passed in the call as a keyword argument, but still counts toward a positional argument of the method.
162166
call.getPositionalArgumentCount() + extraSelfArg(func) >= func.getMinPositionalArguments()
163167
or
164168
exists(call.getStarArg())
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
| test.py:24:5:24:26 | Function meth1 | This method requires 2 positional arguments, whereas overridden $@ requires 1. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:5:5:5:20 | Function meth1 | Base.meth1 | test.py:15:9:15:20 | Attribute() | This call |
2-
| test.py:27:5:27:20 | Function meth2 | This method requires 1 positional argument, whereas overridden $@ requires 2 positional arguments. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:8:5:8:26 | Function meth2 | Base.meth2 | test.py:18:9:18:21 | Attribute() | This call |
2+
| test.py:27:5:27:20 | Function meth2 | This method requires 1 positional argument, whereas overridden $@ requires 2. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:8:5:8:26 | Function meth2 | Base.meth2 | test.py:18:9:18:21 | Attribute() | This call |
33
| test.py:30:5:30:26 | Function meth3 | This method requires 2 positional arguments, whereas overridden $@ requires 1. | test.py:11:5:11:20 | Function meth3 | Base.meth3 | file://:0:0:0:0 | (none) | This call |
44
| test.py:69:5:69:24 | Function meth | This method requires 2 positional arguments, whereas overridden $@ requires 1. | test.py:64:5:64:19 | Function meth | BlameBase.meth | file://:0:0:0:0 | (none) | This call |
55
| test.py:74:5:74:24 | Function meth | This method requires 2 positional arguments, whereas overridden $@ requires 1. | test.py:64:5:64:19 | Function meth | BlameBase.meth | file://:0:0:0:0 | (none) | This call |
6+
| test.py:125:5:125:20 | Function meth1 | This method requires 1 positional argument, whereas overridden $@ may be called with 2. $@ correctly calls the base method, but does not match the signature of the overriding method. | test.py:82:5:82:25 | Function meth1 | Base2.meth1 | test.py:110:9:110:23 | Attribute() | This call |
7+
| test.py:131:5:131:31 | Function meth4 | This method requires at least 3 positional arguments, whereas overridden $@ requires at most 2. | test.py:88:5:88:25 | Function meth4 | Base2.meth4 | file://:0:0:0:0 | (none) | This call |
8+
| 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 |
9+
| 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 |
10+
| 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 |
11+
| 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 |

python/ql/test/query-tests/Functions/overriding/test.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def meth(self):
6666

6767
class Correct1(BlameBase):
6868

69-
def meth(self, arg): # $Alert[py/inheritance/signature-mismatch] # Has 1 more arg. The incorrect-overriden-method query would alert for the base method in this case.
69+
def meth(self, arg): # $Alert[py/inheritance/signature-mismatch] # Has 1 more arg. The incorrect-overridden-method query would alert for the base method in this case.
7070
pass
7171

7272
class Correct2(BlameBase):
@@ -76,3 +76,74 @@ def meth(self, arg): # $Alert[py/inheritance/signature-mismatch] # Has 1 more ar
7676

7777
c = Correct2()
7878
c.meth("hi")
79+
80+
class Base2:
81+
82+
def meth1(self, x=1): pass
83+
84+
def meth2(self, x=1): pass
85+
86+
def meth3(self): pass
87+
88+
def meth4(self, x=1): pass
89+
90+
def meth5(self, x, y, z, w=1): pass
91+
92+
def meth6(self, x, *ys): pass
93+
94+
def meth7(self, *ys): pass
95+
96+
def meth8(self, x, y): pass
97+
98+
def meth9(self, x, y): pass
99+
100+
def meth10(self, x, *, y=3): pass
101+
102+
def meth11(self, x, y): pass
103+
104+
def meth12(self, **kwargs): pass
105+
106+
def meth13(self, /, x): pass
107+
108+
def call_some(self):
109+
self.meth1()
110+
self.meth1(x=2)
111+
self.meth3()
112+
self.meth3(x=2)
113+
self.meth6(2, 3, 4)
114+
self.meth7()
115+
self.meth8(1,y=3)
116+
self.meth9(1,2)
117+
self.meth10(1,y=3)
118+
self.meth11(1,y=3)
119+
self.meth12(x=2)
120+
self.meth13(x=2)
121+
122+
123+
class Derrived2(Base2):
124+
125+
def meth1(self): pass # $Alert[py/inheritance/signature-mismatch] # Weak mismatch (base may be called with 2 args. only alert if mismatching call exists)
126+
127+
def meth2(self): pass # No alert (weak mismatch, but not called)
128+
129+
def meth3(self, x=1): pass # No alert (no mismatch - all base calls are valid for sub)
130+
131+
def meth4(self, x, y, z=1): pass # $Alert[py/inheritance/signature-mismatch] # sub min > base max (strong mismatch)
132+
133+
def meth5(self, x, y=1): pass # $Alert[py/inheritance/signature-mismatch]
134+
135+
def meth6(self, x): pass # $Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with 3+ args)
136+
137+
def meth7(self, x, *ys): pass # $Alert[py/inheritance/signature-mismatch] # weak mismatch (base may be called with 1 arg only)
138+
139+
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.
140+
141+
def meth9(self, x, z): pass # No alert (never called with wrong keyword arg)
142+
143+
def meth10(self, x, **kwargs): pass # No alert (y is kw-only arg in base, calls that use it are valid for sub)
144+
145+
def meth11(self, x, z, **kwargs): pass # $MISSING:Alert[py/inheritance/signature-mismatch] # call using y kw-arg is invalid due to not specifying z, but this is not detected. Likely a fairly niche situation.
146+
147+
def meth12(self): pass # $Alert[py/inheritance/signature-mismatch] # call including extra kwarg invalid
148+
149+
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.

0 commit comments

Comments
 (0)