Skip to content

Commit 1b8154c

Browse files
authored
Merge pull request github#2925 from BekaValentine/python-objectapi-to-valueapi-callargs
Python: ObjectAPI to ValueAPI: CallArgs
2 parents be09c17 + 047c328 commit 1b8154c

File tree

2 files changed

+151
-2
lines changed

2 files changed

+151
-2
lines changed

python/ql/src/Expressions/CallArgs.qll

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,38 @@ private int varargs_length_objectapi(Call call) {
1313
result = count(call.getStarargs().(List).getAnElt())
1414
}
1515

16+
private int varargs_length(Call call) {
17+
not exists(call.getStarargs()) and result = 0
18+
or
19+
exists(TupleValue t |
20+
call.getStarargs().pointsTo(t) |
21+
result = t.length()
22+
)
23+
or
24+
result = count(call.getStarargs().(List).getAnElt())
25+
}
26+
1627
/** Gets a keyword argument that is not a keyword-only parameter. */
1728
private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
1829
func.getACall().getNode() = call and
1930
result = call.getAKeyword() and
2031
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
2132
}
2233

34+
/** Gets a keyword argument that is not a keyword-only parameter. */
35+
private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
36+
func.getACall().getNode() = call and
37+
result = call.getAKeyword() and
38+
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
39+
}
40+
2341
/** Gets the count of arguments that are passed as positional parameters even if they
2442
* are named in the call.
2543
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
2644
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
2745
*/
2846

29-
private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object callable) {
47+
private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
3048
call = get_a_call_objectapi(callable).getNode() and
3149
exists(int positional_keywords |
3250
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
@@ -40,24 +58,62 @@ private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object
4058
)
4159
}
4260

61+
/** Gets the count of arguments that are passed as positional parameters even if they
62+
* are named in the call.
63+
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
64+
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
65+
*/
66+
67+
private int positional_arg_count_for_call(Call call, Value callable) {
68+
call = get_a_call(callable).getNode() and
69+
exists(int positional_keywords |
70+
exists(FunctionValue func | func = get_function_or_initializer(callable) |
71+
not func.getScope().hasKwArg() and
72+
positional_keywords = count(not_keyword_only_arg(call, func))
73+
or
74+
func.getScope().hasKwArg() and positional_keywords = 0
75+
)
76+
|
77+
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
78+
)
79+
}
80+
4381
int arg_count_objectapi(Call call) {
4482
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
4583
}
4684

85+
int arg_count(Call call) {
86+
result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword())
87+
}
88+
4789
/* Gets a call corresponding to the given class or function*/
4890
private ControlFlowNode get_a_call_objectapi(Object callable) {
4991
result = callable.(ClassObject).getACall()
5092
or
5193
result = callable.(FunctionObject).getACall()
5294
}
5395

96+
/* Gets a call corresponding to the given class or function*/
97+
private ControlFlowNode get_a_call(Value callable) {
98+
result = callable.(ClassValue).getACall()
99+
or
100+
result = callable.(FunctionValue).getACall()
101+
}
102+
54103
/* Gets the function object corresponding to the given class or function*/
55104
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
56105
result = func_or_cls.(FunctionObject)
57106
or
58107
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
59108
}
60109

110+
/* Gets the function object corresponding to the given class or function*/
111+
FunctionValue get_function_or_initializer(Value func_or_cls) {
112+
result = func_or_cls.(FunctionValue)
113+
or
114+
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
115+
}
116+
61117

62118
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
63119
predicate illegally_named_parameter_objectapi(Call call, Object func, string name) {
@@ -67,6 +123,14 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam
67123
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
68124
}
69125

126+
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
127+
predicate illegally_named_parameter(Call call, Value func, string name) {
128+
not func.isBuiltin() and
129+
name = call.getANamedArgumentName() and
130+
call.getAFlowNode() = get_a_call(func) and
131+
not get_function_or_initializer(func).isLegalArgumentName(name)
132+
}
133+
70134
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
71135
predicate too_few_args_objectapi(Call call, Object callable, int limit) {
72136
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
@@ -88,6 +152,27 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) {
88152
)
89153
}
90154

155+
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
156+
predicate too_few_args(Call call, Value callable, int limit) {
157+
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
158+
not illegally_named_parameter(call, callable, _) and
159+
not exists(call.getStarargs()) and not exists(call.getKwargs()) and
160+
arg_count(call) < limit and
161+
exists(FunctionValue func | func = get_function_or_initializer(callable) |
162+
call = func.getACall().getNode() and limit = func.minParameters() and
163+
/* The combination of misuse of `mox.Mox().StubOutWithMock()`
164+
* and a bug in mox's implementation of methods results in having to
165+
* pass 1 too few arguments to the mocked function.
166+
*/
167+
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
168+
or
169+
call = func.getACall().getNode() and limit = func.minParameters() - 1
170+
or
171+
callable instanceof ClassValue and
172+
call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1
173+
)
174+
}
175+
91176
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
92177
predicate too_many_args_objectapi(Call call, Object callable, int limit) {
93178
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
@@ -103,7 +188,25 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) {
103188
callable instanceof ClassObject and
104189
call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1
105190
) and
106-
positional_arg_count_objectapi_for_call_objectapi(call, callable) > limit
191+
positional_arg_count_for_call_objectapi(call, callable) > limit
192+
}
193+
194+
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
195+
predicate too_many_args(Call call, Value callable, int limit) {
196+
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
197+
not illegally_named_parameter(call, callable, _) and
198+
exists(FunctionValue func |
199+
func = get_function_or_initializer(callable) and
200+
not func.getScope().hasVarArg() and limit >= 0
201+
|
202+
call = func.getACall().getNode() and limit = func.maxParameters()
203+
or
204+
call = func.getACall().getNode() and limit = func.maxParameters() - 1
205+
or
206+
callable instanceof ClassValue and
207+
call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1
208+
) and
209+
positional_arg_count_for_call(call, callable) > limit
107210
}
108211

109212
/** Holds if `call` has too many or too few arguments for `func` */
@@ -113,6 +216,13 @@ predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string
113216
too_many_args_objectapi(call, func, limit) and too = "too many"
114217
}
115218

219+
/** Holds if `call` has too many or too few arguments for `func` */
220+
predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
221+
too_few_args(call, func, limit) and too = "too few"
222+
or
223+
too_many_args(call, func, limit) and too = "too many"
224+
}
225+
116226
/** Holds if `call` has correct number of arguments for `func`.
117227
* Implies nothing about whether `call` could call `func`.
118228
*/
@@ -123,8 +233,25 @@ predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject f
123233
arg_count_objectapi(call) < func.maxParameters()
124234
}
125235

236+
/** Holds if `call` has correct number of arguments for `func`.
237+
* Implies nothing about whether `call` could call `func`.
238+
*/
239+
bindingset[call, func]
240+
predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
241+
arg_count(call)+1 >= func.minParameters()
242+
and
243+
arg_count(call) < func.maxParameters()
244+
}
245+
126246
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
127247
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
128248
overriding.overrides(func) and
129249
overriding.getACall().getNode() = call
130250
}
251+
252+
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
253+
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
254+
overriding.overrides(func) and
255+
overriding.getACall().getNode() = call
256+
}
257+

python/ql/src/semmle/python/objects/ObjectAPI.qll

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,28 @@ abstract class FunctionValue extends CallableValue {
514514
predicate isOverriddenMethod() {
515515
exists(Value f | f.overrides(this))
516516
}
517+
518+
/** Whether `name` is a legal argument name for this function */
519+
bindingset[name]
520+
predicate isLegalArgumentName(string name) {
521+
this.getScope().getAnArg().asName().getId() = name
522+
or
523+
this.getScope().getAKeywordOnlyArg().getId() = name
524+
or
525+
this.getScope().hasKwArg()
526+
}
527+
528+
/** Whether this is a "normal" method, that is, it is exists as a class attribute
529+
* which is not a lambda and not the __new__ method. */
530+
predicate isNormalMethod() {
531+
exists(ClassValue cls, string name |
532+
cls.declaredAttribute(name) = this and
533+
name != "__new__" and
534+
exists(Expr expr, AstNode origin | expr.pointsTo(this, origin) |
535+
not origin instanceof Lambda
536+
)
537+
)
538+
}
517539
}
518540

519541
/** Class representing Python functions */

0 commit comments

Comments
 (0)