Skip to content

Commit 08cfedd

Browse files
committed
Python: Port WrongNumberArgumentsInClassInstantiation.ql
Included test changes are trivial `toString` changes.
1 parent 849da28 commit 08cfedd

2 files changed

Lines changed: 71 additions & 17 deletions

File tree

python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,64 @@
1414
*/
1515

1616
import python
17-
import Expressions.CallArgs
18-
private import LegacyPointsTo
17+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1918

20-
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init
19+
/**
20+
* Gets the number of positional arguments in `call`, including elements of any
21+
* literal list passed as `*args`, plus keyword arguments that don't match
22+
* keyword-only parameters (when the function doesn't accept `**kwargs`).
23+
*/
24+
int positional_arg_count(Call call, Function init) {
25+
exists(Class cls |
26+
resolveClassCall(call.getAFlowNode(), cls) and
27+
init = DuckTyping::getInit(cls)
28+
) and
29+
exists(int positional_keywords |
30+
if init.hasKwArg()
31+
then positional_keywords = 0
32+
else
33+
positional_keywords =
34+
count(Keyword kw |
35+
kw = call.getAKeyword() and
36+
not init.getAKeywordOnlyArg().getId() = kw.getArg()
37+
)
38+
|
39+
result =
40+
count(call.getAnArg()) + count(call.getStarargs().(List).getAnElt()) + positional_keywords
41+
)
42+
}
43+
44+
/**
45+
* Holds if `call` constructs `cls` with too many arguments, where `limit` is the maximum.
46+
*/
47+
predicate too_many_args(Call call, Class cls, int limit) {
48+
exists(Function init |
49+
resolveClassCall(call.getAFlowNode(), cls) and
50+
init = DuckTyping::getInit(cls) and
51+
not init.hasVarArg() and
52+
// Subtract 1 from max to account for `self` parameter
53+
limit = init.getMaxPositionalArguments() - 1 and
54+
limit >= 0 and
55+
positional_arg_count(call, init) > limit
56+
)
57+
}
58+
59+
/**
60+
* Holds if `call` constructs `cls` with too few arguments, where `limit` is the minimum.
61+
*/
62+
predicate too_few_args(Call call, Class cls, int limit) {
63+
exists(Function init |
64+
resolveClassCall(call.getAFlowNode(), cls) and
65+
init = DuckTyping::getInit(cls) and
66+
not exists(call.getStarargs()) and
67+
not exists(call.getKwargs()) and
68+
// Subtract 1 from min to account for `self` parameter
69+
limit = init.getMinPositionalArguments() - 1 and
70+
count(call.getAnArg()) + count(call.getAKeyword()) < limit
71+
)
72+
}
73+
74+
from Call call, Class cls, string too, string should, int limit, Function init
2175
where
2276
(
2377
too_many_args(call, cls, limit) and
@@ -28,6 +82,6 @@ where
2882
too = "too few arguments" and
2983
should = "no fewer than "
3084
) and
31-
init = get_function_or_initializer(cls)
85+
init = DuckTyping::getInit(cls)
3286
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
3387
init.getQualifiedName()
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
2-
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
3-
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function F2.__init__ | F2.__init__ |
4-
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function F3.__init__ | F3.__init__ |
5-
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function F4.__init__ | F4.__init__ |
6-
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
7-
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
8-
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function F7.__init__ | F7.__init__ |
9-
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function F0.__init__ | F0.__init__ |
10-
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function F1.__init__ | F1.__init__ |
11-
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function F5.__init__ | F5.__init__ |
12-
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
13-
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
141
| wrong_arguments.py:85:1:85:12 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
152
| wrong_arguments.py:86:1:86:7 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function F6.__init__ | F6.__init__ |
3+
| wrong_arguments.py:37:1:37:4 | F0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
4+
| wrong_arguments.py:38:1:38:4 | F1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
5+
| wrong_arguments.py:39:1:39:4 | F2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:12:5:12:30 | Function __init__ | F2.__init__ |
6+
| wrong_arguments.py:40:1:40:4 | F3() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:16:5:16:40 | Function __init__ | F3.__init__ |
7+
| wrong_arguments.py:41:1:41:4 | F4() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:20:5:20:31 | Function __init__ | F4.__init__ |
8+
| wrong_arguments.py:42:1:42:4 | F5() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
9+
| wrong_arguments.py:43:1:43:5 | F6() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
10+
| wrong_arguments.py:44:1:44:7 | F7() | Call to $@ with too few arguments; should be no fewer than 3. | wrong_arguments.py:32:5:32:33 | Function __init__ | F7.__init__ |
11+
| wrong_arguments.py:48:1:48:7 | F0() | Call to $@ with too many arguments; should be no more than 1. | wrong_arguments.py:4:5:4:26 | Function __init__ | F0.__init__ |
12+
| wrong_arguments.py:49:1:49:9 | F1() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:8:5:8:36 | Function __init__ | F1.__init__ |
13+
| wrong_arguments.py:50:1:50:9 | F5() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:24:5:24:42 | Function __init__ | F5.__init__ |
14+
| wrong_arguments.py:51:1:51:9 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |
15+
| wrong_arguments.py:52:1:52:11 | F6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:28:5:28:30 | Function __init__ | F6.__init__ |

0 commit comments

Comments
 (0)