Skip to content

Commit ac1a338

Browse files
authored
Merge pull request #3407 from RasmusWL/python-add-BoundMethodValue-v2
Approved by tausbn
2 parents a230877 + 49d7e12 commit ac1a338

18 files changed

+270
-41
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ abstract class CallableObjectInternal extends ObjectInternal {
2727
none()
2828
}
2929

30+
/** Gets the `n`th parameter node of this callable. */
3031
abstract NameNode getParameter(int n);
3132

33+
/** Gets the `name`d parameter node of this callable. */
3234
abstract NameNode getParameterByName(string name);
3335

3436
abstract predicate neverReturns();
@@ -438,16 +440,30 @@ class BoundMethodObjectInternal extends CallableObjectInternal, TBoundMethod {
438440
PointsTo::pointsTo(result.getFunction(), ctx, this, _)
439441
}
440442

441-
override NameNode getParameter(int n) { result = this.getFunction().getParameter(n + 1) }
443+
/** Gets the parameter node that will be used for `self`. */
444+
NameNode getSelfParameter() { result = this.getFunction().getParameter(0) }
442445

446+
override NameNode getParameter(int n) {
447+
result = this.getFunction().getParameter(n + 1) and
448+
// don't return the parameter for `self` at `n = -1`
449+
n >= 0
450+
}
451+
452+
/**
453+
* Gets the `name`d parameter node of this callable.
454+
* Will not return the parameter node for `self`, instead use `getSelfParameter`.
455+
*/
443456
override NameNode getParameterByName(string name) {
444-
result = this.getFunction().getParameterByName(name)
457+
result = this.getFunction().getParameterByName(name) and
458+
not result = this.getSelfParameter()
445459
}
446460

447461
override predicate neverReturns() { this.getFunction().neverReturns() }
448462

449463
override predicate functionAndOffset(CallableObjectInternal function, int offset) {
450464
function = this.getFunction() and offset = 1
465+
or
466+
function = this and offset = 0
451467
}
452468

453469
override predicate useOriginAsLegacyObject() { any() }

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

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,29 @@ class CallableValue extends Value {
352352
result = this.(CallableObjectInternal).getParameterByName(name)
353353
}
354354

355-
/** Gets the argument corresponding to the `n'th parameter node of this callable. */
355+
/**
356+
* Gets the argument in `call` corresponding to the `n`'th positional parameter of this callable.
357+
*
358+
* Use this method instead of `call.getArg(n)` to handle the fact that this function might be used as
359+
* a bound-method, such that argument `n` of the call corresponds to the `n+1` parameter of the callable.
360+
*
361+
* This method also gives results when the argument is passed as a keyword argument in `call`, as long
362+
* as `this` is not a builtin function or a builtin method.
363+
*
364+
* Examples:
365+
*
366+
* - if `this` represents the `PythonFunctionValue` for `def func(a, b):`, and `call` represents
367+
* `func(10, 20)`, then `getArgumentForCall(call, 0)` will give the `ControlFlowNode` for `10`.
368+
*
369+
* - with `call` representing `func(b=20, a=10)`, `getArgumentForCall(call, 0)` will give
370+
* the `ControlFlowNode` for `10`.
371+
*
372+
* - if `this` represents the `PythonFunctionValue` for `def func(self, a, b):`, and `call`
373+
* represents `foo.func(10, 20)`, then `getArgumentForCall(call, 1)` will give the
374+
* `ControlFlowNode` for `10`.
375+
* Note: There will also exist a `BoundMethodValue bm` where `bm.getArgumentForCall(call, 0)`
376+
* will give the `ControlFlowNode` for `10` (notice the shift in index used).
377+
*/
356378
cached
357379
ControlFlowNode getArgumentForCall(CallNode call, int n) {
358380
exists(ObjectInternal called, int offset |
@@ -363,7 +385,7 @@ class CallableValue extends Value {
363385
or
364386
exists(string name |
365387
call.getArgByName(name) = result and
366-
this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name
388+
this.getParameter(n).getId() = name
367389
)
368390
or
369391
called instanceof BoundMethodObjectInternal and
@@ -373,21 +395,37 @@ class CallableValue extends Value {
373395
)
374396
}
375397

376-
/** Gets the argument corresponding to the `name`d parameter node of this callable. */
398+
/**
399+
* Gets the argument in `call` corresponding to the `name`d keyword parameter of this callable.
400+
*
401+
* This method also gives results when the argument is passed as a positional argument in `call`, as long
402+
* as `this` is not a builtin function or a builtin method.
403+
*
404+
* Examples:
405+
*
406+
* - if `this` represents the `PythonFunctionValue` for `def func(a, b):`, and `call` represents
407+
* `func(10, 20)`, then `getNamedArgumentForCall(call, "a")` will give the `ControlFlowNode` for `10`.
408+
*
409+
* - with `call` representing `func(b=20, a=10)`, `getNamedArgumentForCall(call, "a")` will give
410+
* the `ControlFlowNode` for `10`.
411+
*
412+
* - if `this` represents the `PythonFunctionValue` for `def func(self, a, b):`, and `call`
413+
* represents `foo.func(10, 20)`, then `getNamedArgumentForCall(call, "a")` will give the
414+
* `ControlFlowNode` for `10`.
415+
*/
377416
cached
378417
ControlFlowNode getNamedArgumentForCall(CallNode call, string name) {
379418
exists(CallableObjectInternal called, int offset |
380419
PointsToInternal::pointsTo(call.getFunction(), _, called, _) and
381420
called.functionAndOffset(this, offset)
382421
|
422+
call.getArgByName(name) = result
423+
or
383424
exists(int n |
384425
call.getArg(n) = result and
385-
this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name
426+
this.getParameter(n + offset).getId() = name
386427
)
387428
or
388-
call.getArgByName(name) = result and
389-
exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name))
390-
or
391429
called instanceof BoundMethodObjectInternal and
392430
offset = 1 and
393431
name = "self" and
@@ -396,6 +434,29 @@ class CallableValue extends Value {
396434
}
397435
}
398436

437+
/**
438+
* Class representing bound-methods, such as `o.func`, where `o` is an instance
439+
* of a class that has a callable attribute `func`.
440+
*/
441+
class BoundMethodValue extends CallableValue {
442+
BoundMethodValue() { this instanceof BoundMethodObjectInternal }
443+
444+
/**
445+
* Gets the callable that will be used when `this` is called.
446+
* The actual callable for `func` in `o.func`.
447+
*/
448+
CallableValue getFunction() { result = this.(BoundMethodObjectInternal).getFunction() }
449+
450+
/**
451+
* Gets the value that will be used for the `self` parameter when `this` is called.
452+
* The value for `o` in `o.func`.
453+
*/
454+
Value getSelf() { result = this.(BoundMethodObjectInternal).getSelf() }
455+
456+
/** Gets the parameter node that will be used for `self`. */
457+
NameNode getSelfParameter() { result = this.(BoundMethodObjectInternal).getSelfParameter() }
458+
}
459+
399460
/**
400461
* Class representing classes in the Python program, both Python and built-in.
401462
*/
@@ -663,11 +724,13 @@ class PythonFunctionValue extends FunctionValue {
663724
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
664725

665726
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
666-
727+
667728
override ClassValue getAnInferredReturnType() {
668-
/* We have to do a special version of this because builtin functions have no
729+
/*
730+
* We have to do a special version of this because builtin functions have no
669731
* explicit return nodes that we can query and get the class of.
670732
*/
733+
671734
result = this.getAReturnedNode().pointsTo().getClass()
672735
}
673736
}
@@ -690,9 +753,11 @@ class BuiltinFunctionValue extends FunctionValue {
690753
}
691754

692755
override ClassValue getAnInferredReturnType() {
693-
/* We have to do a special version of this because builtin functions have no
756+
/*
757+
* We have to do a special version of this because builtin functions have no
694758
* explicit return nodes that we can query and get the class of.
695759
*/
760+
696761
result = TBuiltinClassObject(this.(BuiltinFunctionObjectInternal).getReturnType())
697762
}
698763
}
@@ -719,7 +784,7 @@ class BuiltinMethodValue extends FunctionValue {
719784
/* Information is unavailable for C code in general */
720785
none()
721786
}
722-
787+
723788
override ClassValue getAnInferredReturnType() {
724789
result = TBuiltinClassObject(this.(BuiltinMethodObjectInternal).getReturnType())
725790
}

python/ql/test/library-tests/PointsTo/calls/Argument.expected

Lines changed: 0 additions & 15 deletions
This file was deleted.

python/ql/test/library-tests/PointsTo/calls/Argument.ql

Lines changed: 0 additions & 5 deletions
This file was deleted.

python/ql/test/library-tests/PointsTo/calls/Call.expected

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
| 19 | ControlFlowNode for f() | Function f |
2+
| 21 | ControlFlowNode for f() | Function f |
3+
| 22 | ControlFlowNode for C() | class C |
4+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) |
5+
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
6+
| 25 | ControlFlowNode for Attribute() | Function C.n |
7+
| 29 | ControlFlowNode for staticmethod() | builtin-class staticmethod |
8+
| 33 | ControlFlowNode for Attribute() | Function D.foo |
9+
| 34 | ControlFlowNode for Attribute() | Function D.foo |
10+
| 34 | ControlFlowNode for D() | class D |
11+
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) |
12+
| 38 | ControlFlowNode for len() | Builtin-function len |
13+
| 40 | ControlFlowNode for f() | Function f |
14+
| 41 | ControlFlowNode for C() | class C |
15+
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
16+
| 45 | ControlFlowNode for open() | Builtin-function open |
17+
| 46 | ControlFlowNode for open() | Builtin-function open |
18+
| 51 | ControlFlowNode for foo() | Function foo |
19+
| 55 | ControlFlowNode for bar() | Function bar |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import python
2+
3+
from CallNode call, Value func
4+
where call.getFunction().pointsTo(func)
5+
select call.getLocation().getStartLine(), call.toString(), func.toString()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
| 19 | ControlFlowNode for f() | Function f |
2+
| 21 | ControlFlowNode for f() | Function f |
3+
| 22 | ControlFlowNode for C() | class C |
4+
| 23 | ControlFlowNode for Attribute() | Function f |
5+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) |
6+
| 24 | ControlFlowNode for Attribute() | Function C.n |
7+
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
8+
| 25 | ControlFlowNode for Attribute() | Function C.n |
9+
| 29 | ControlFlowNode for staticmethod() | builtin-class staticmethod |
10+
| 33 | ControlFlowNode for Attribute() | Function D.foo |
11+
| 34 | ControlFlowNode for Attribute() | Function D.foo |
12+
| 34 | ControlFlowNode for D() | class D |
13+
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) |
14+
| 37 | ControlFlowNode for Attribute() | builtin method append |
15+
| 38 | ControlFlowNode for len() | Builtin-function len |
16+
| 40 | ControlFlowNode for f() | Function f |
17+
| 41 | ControlFlowNode for C() | class C |
18+
| 42 | ControlFlowNode for Attribute() | Function C.n |
19+
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
20+
| 45 | ControlFlowNode for open() | Builtin-function open |
21+
| 46 | ControlFlowNode for open() | Builtin-function open |
22+
| 51 | ControlFlowNode for foo() | Function foo |
23+
| 55 | ControlFlowNode for bar() | Function bar |
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import python
22

3-
from ControlFlowNode call, FunctionObject func
3+
from ControlFlowNode call, Value func
44
where call = func.getACall()
55
select call.getLocation().getStartLine(), call.toString(), func.toString()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
| 19 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for w |
2+
| 19 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for x |
3+
| 19 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for y |
4+
| 21 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for y |
5+
| 21 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for w |
6+
| 21 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for z |
7+
| 23 | ControlFlowNode for Attribute() | Function f | 0 | ControlFlowNode for c |
8+
| 23 | ControlFlowNode for Attribute() | Function f | 1 | ControlFlowNode for w |
9+
| 23 | ControlFlowNode for Attribute() | Function f | 2 | ControlFlowNode for z |
10+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | 0 | ControlFlowNode for w |
11+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | 1 | ControlFlowNode for z |
12+
| 24 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for c |
13+
| 24 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for x |
14+
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | 0 | ControlFlowNode for x |
15+
| 25 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for y |
16+
| 25 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for z |
17+
| 33 | ControlFlowNode for Attribute() | Function D.foo | 0 | ControlFlowNode for IntegerLiteral |
18+
| 34 | ControlFlowNode for Attribute() | Function D.foo | 0 | ControlFlowNode for IntegerLiteral |
19+
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) | 0 | ControlFlowNode for IntegerLiteral |
20+
| 37 | ControlFlowNode for Attribute() | builtin method append | 0 | ControlFlowNode for l |
21+
| 37 | ControlFlowNode for Attribute() | builtin method append | 1 | ControlFlowNode for IntegerLiteral |
22+
| 38 | ControlFlowNode for len() | Builtin-function len | 0 | ControlFlowNode for l |
23+
| 40 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for IntegerLiteral |
24+
| 40 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for IntegerLiteral |
25+
| 40 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for IntegerLiteral |
26+
| 42 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for c |
27+
| 42 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for IntegerLiteral |
28+
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | 0 | ControlFlowNode for IntegerLiteral |
29+
| 45 | ControlFlowNode for open() | Builtin-function open | 0 | ControlFlowNode for Str |
30+
| 45 | ControlFlowNode for open() | Builtin-function open | 1 | ControlFlowNode for Str |
31+
| 51 | ControlFlowNode for foo() | Function foo | 0 | ControlFlowNode for IntegerLiteral |
32+
| 51 | ControlFlowNode for foo() | Function foo | 1 | ControlFlowNode for IntegerLiteral |
33+
| 51 | ControlFlowNode for foo() | Function foo | 2 | ControlFlowNode for IntegerLiteral |
34+
| 55 | ControlFlowNode for bar() | Function bar | 0 | ControlFlowNode for IntegerLiteral |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import python
2+
3+
from CallNode call, CallableValue callable, int i
4+
select call.getLocation().getStartLine(), call.toString(), callable.toString(), i,
5+
callable.getArgumentForCall(call, i).toString()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
| 19 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for w |
2+
| 19 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for x |
3+
| 19 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for y |
4+
| 21 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for y |
5+
| 21 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for w |
6+
| 21 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for z |
7+
| 23 | ControlFlowNode for Attribute() | Function f | arg1 | ControlFlowNode for w |
8+
| 23 | ControlFlowNode for Attribute() | Function f | arg2 | ControlFlowNode for z |
9+
| 23 | ControlFlowNode for Attribute() | Function f | self | ControlFlowNode for c |
10+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | arg1 | ControlFlowNode for w |
11+
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | arg2 | ControlFlowNode for z |
12+
| 24 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for x |
13+
| 24 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for c |
14+
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | arg1 | ControlFlowNode for x |
15+
| 25 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for z |
16+
| 25 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for y |
17+
| 33 | ControlFlowNode for Attribute() | Function D.foo | arg | ControlFlowNode for IntegerLiteral |
18+
| 34 | ControlFlowNode for Attribute() | Function D.foo | arg | ControlFlowNode for IntegerLiteral |
19+
| 37 | ControlFlowNode for Attribute() | builtin method append | self | ControlFlowNode for l |
20+
| 40 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for IntegerLiteral |
21+
| 40 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for IntegerLiteral |
22+
| 40 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for IntegerLiteral |
23+
| 42 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for IntegerLiteral |
24+
| 42 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for c |
25+
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | arg1 | ControlFlowNode for IntegerLiteral |
26+
| 46 | ControlFlowNode for open() | Builtin-function open | file | ControlFlowNode for Str |
27+
| 46 | ControlFlowNode for open() | Builtin-function open | mode | ControlFlowNode for Str |
28+
| 51 | ControlFlowNode for foo() | Function foo | a | ControlFlowNode for IntegerLiteral |
29+
| 55 | ControlFlowNode for bar() | Function bar | a | ControlFlowNode for IntegerLiteral |
30+
| 55 | ControlFlowNode for bar() | Function bar | b | ControlFlowNode for IntegerLiteral |
31+
| 55 | ControlFlowNode for bar() | Function bar | c | ControlFlowNode for IntegerLiteral |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import python
2+
3+
from CallNode call, CallableValue callable, string name
4+
select call.getLocation().getStartLine(), call.toString(), callable.toString(), name,
5+
callable.getNamedArgumentForCall(call, name).toString()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
| Function C.n | 0 | ControlFlowNode for self |
2+
| Function C.n | 1 | ControlFlowNode for arg1 |
3+
| Function D.foo | 0 | ControlFlowNode for arg |
4+
| Function bar | 0 | ControlFlowNode for a |
5+
| Function f | 0 | ControlFlowNode for arg0 |
6+
| Function f | 1 | ControlFlowNode for arg1 |
7+
| Function f | 2 | ControlFlowNode for arg2 |
8+
| Function foo | 0 | ControlFlowNode for a |
9+
| Method(Function C.n, C()) | 0 | ControlFlowNode for arg1 |
10+
| Method(Function C.n, class C) | 0 | ControlFlowNode for arg1 |
11+
| Method(Function f, C()) | 0 | ControlFlowNode for arg1 |
12+
| Method(Function f, C()) | 1 | ControlFlowNode for arg2 |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import python
2+
3+
from CallableValue callable, int i
4+
select callable.toString(), i, callable.getParameter(i).toString()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
| Function C.n | arg1 | ControlFlowNode for arg1 |
2+
| Function C.n | self | ControlFlowNode for self |
3+
| Function D.foo | arg | ControlFlowNode for arg |
4+
| Function bar | a | ControlFlowNode for a |
5+
| Function f | arg0 | ControlFlowNode for arg0 |
6+
| Function f | arg1 | ControlFlowNode for arg1 |
7+
| Function f | arg2 | ControlFlowNode for arg2 |
8+
| Function foo | a | ControlFlowNode for a |
9+
| Method(Function C.n, C()) | arg1 | ControlFlowNode for arg1 |
10+
| Method(Function C.n, class C) | arg1 | ControlFlowNode for arg1 |
11+
| Method(Function f, C()) | arg1 | ControlFlowNode for arg1 |
12+
| Method(Function f, C()) | arg2 | ControlFlowNode for arg2 |

0 commit comments

Comments
 (0)