Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7b8b4af
Python: Add test for call.getFunction().refersTo
RasmusWL May 4, 2020
a5289bd
Python: Use Object in CallRefersTo test
RasmusWL May 4, 2020
06b67e0
Python: Modernise test/library-tests/PointsTo/calls/*
RasmusWL May 4, 2020
f624754
Python: Use Value in GetACAll test
RasmusWL May 4, 2020
fc0b022
Python: Add test-cases for BuiltinFunction and BuiltinMethod
RasmusWL May 4, 2020
9ec32ee
Python: Add test-cases using keyword arguments
RasmusWL May 4, 2020
acb506d
Python: Add test for getNamedArgumentForCall
RasmusWL May 4, 2020
e9859ad
Python: Fix getArgumentForCall when using keyword arguments
RasmusWL May 4, 2020
96fdb7a
Python: Add tests for getParameter[byName]
RasmusWL May 4, 2020
bc92c26
Python: Add BoundMethodValue
RasmusWL May 4, 2020
838106d
Python: Refactor get[Named]ArgumentForCall
RasmusWL May 4, 2020
061bbb8
Python: Restructure getNamedArgumentForCall
RasmusWL May 5, 2020
87d7738
Python: Expand QLDoc for get[Named]ArgumentForCall
RasmusWL May 5, 2020
dfe7c82
Python: Clean up trailing whitespace
RasmusWL May 5, 2020
affca1a
Python: Add test-cases using keyword arguments for builtin function
RasmusWL May 5, 2020
07ae402
Python: Don't allow getParameter(-1) for BoundMethodValue
RasmusWL May 5, 2020
6488714
Python: Autoformat
RasmusWL May 5, 2020
9e0d57c
Python: Fix grammar in QLDoc
RasmusWL May 25, 2020
87ee6ae
Python: Add a bit of docs to CallableObjectInternal
RasmusWL May 25, 2020
4fc3cae
Python: Add test for how arguments to *args and **kwargs are handled
RasmusWL May 25, 2020
49d7e12
Python: Remove unnecessary restriction from getNamedArgumentForCall
RasmusWL May 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions python/ql/src/semmle/python/objects/Callables.qll
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ abstract class CallableObjectInternal extends ObjectInternal {
none()
}

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

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

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

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

override NameNode getParameter(int n) {
result = this.getFunction().getParameter(n + 1) and
// don't return the parameter for `self` at `n = -1`
n >= 0
}

/**
* Gets the `name`d parameter node of this callable.
* Will not return the parameter node for `self`, instead use `getSelfParameter`.
*/
override NameNode getParameterByName(string name) {
result = this.getFunction().getParameterByName(name)
result = this.getFunction().getParameterByName(name) and
not result = this.getSelfParameter()
}

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

override predicate functionAndOffset(CallableObjectInternal function, int offset) {
function = this.getFunction() and offset = 1
or
function = this and offset = 0
}

override predicate useOriginAsLegacyObject() { any() }
Expand Down
87 changes: 76 additions & 11 deletions python/ql/src/semmle/python/objects/ObjectAPI.qll
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,29 @@ class CallableValue extends Value {
result = this.(CallableObjectInternal).getParameterByName(name)
}

/** Gets the argument corresponding to the `n'th parameter node of this callable. */
/**
* Gets the argument in `call` corresponding to the `n`'th positional parameter of this callable.
*
* Use this method instead of `call.getArg(n)` to handle the fact that this function might be used as
* a bound-method, such that argument `n` of the call corresponds to the `n+1` parameter of the callable.
*
* This method also gives results when the argument is passed as a keyword argument in `call`, as long
* as `this` is not a builtin function or a builtin method.
*
* Examples:
*
* - if `this` represents the `PythonFunctionValue` for `def func(a, b):`, and `call` represents
* `func(10, 20)`, then `getArgumentForCall(call, 0)` will give the `ControlFlowNode` for `10`.
*
* - with `call` representing `func(b=20, a=10)`, `getArgumentForCall(call, 0)` will give
* the `ControlFlowNode` for `10`.
*
* - if `this` represents the `PythonFunctionValue` for `def func(self, a, b):`, and `call`
* represents `foo.func(10, 20)`, then `getArgumentForCall(call, 1)` will give the
* `ControlFlowNode` for `10`.
* Note: There will also exist a `BoundMethodValue bm` where `bm.getArgumentForCall(call, 0)`
* will give the `ControlFlowNode` for `10` (notice the shift in index used).
*/
cached
ControlFlowNode getArgumentForCall(CallNode call, int n) {
exists(ObjectInternal called, int offset |
Expand All @@ -363,7 +385,7 @@ class CallableValue extends Value {
or
exists(string name |
call.getArgByName(name) = result and
this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name
this.getParameter(n).getId() = name
)
or
called instanceof BoundMethodObjectInternal and
Expand All @@ -373,21 +395,37 @@ class CallableValue extends Value {
)
}

/** Gets the argument corresponding to the `name`d parameter node of this callable. */
/**
* Gets the argument in `call` corresponding to the `name`d keyword parameter of this callable.
*
* This method also gives results when the argument is passed as a positional argument in `call`, as long
* as `this` is not a builtin function or a builtin method.
*
* Examples:
*
* - if `this` represents the `PythonFunctionValue` for `def func(a, b):`, and `call` represents
* `func(10, 20)`, then `getNamedArgumentForCall(call, "a")` will give the `ControlFlowNode` for `10`.
*
* - with `call` representing `func(b=20, a=10)`, `getNamedArgumentForCall(call, "a")` will give
* the `ControlFlowNode` for `10`.
*
* - if `this` represents the `PythonFunctionValue` for `def func(self, a, b):`, and `call`
* represents `foo.func(10, 20)`, then `getNamedArgumentForCall(call, "a")` will give the
* `ControlFlowNode` for `10`.
*/
cached
ControlFlowNode getNamedArgumentForCall(CallNode call, string name) {
exists(CallableObjectInternal called, int offset |
PointsToInternal::pointsTo(call.getFunction(), _, called, _) and
called.functionAndOffset(this, offset)
|
call.getArgByName(name) = result
or
exists(int n |
call.getArg(n) = result and
this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name
this.getParameter(n + offset).getId() = name
)
or
call.getArgByName(name) = result and
exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name))
or
called instanceof BoundMethodObjectInternal and
offset = 1 and
name = "self" and
Expand All @@ -396,6 +434,29 @@ class CallableValue extends Value {
}
}

/**
* Class representing bound-methods, such as `o.func`, where `o` is an instance
* of a class that has a callable attribute `func`.
*/
class BoundMethodValue extends CallableValue {
BoundMethodValue() { this instanceof BoundMethodObjectInternal }

/**
* Gets the callable that will be used when `this` is called.
* The actual callable for `func` in `o.func`.
*/
CallableValue getFunction() { result = this.(BoundMethodObjectInternal).getFunction() }

/**
* Gets the value that will be used for the `self` parameter when `this` is called.
* The value for `o` in `o.func`.
*/
Value getSelf() { result = this.(BoundMethodObjectInternal).getSelf() }

/** Gets the parameter node that will be used for `self`. */
NameNode getSelfParameter() { result = this.(BoundMethodObjectInternal).getSelfParameter() }
}

/**
* Class representing classes in the Python program, both Python and built-in.
*/
Expand Down Expand Up @@ -663,11 +724,13 @@ class PythonFunctionValue extends FunctionValue {
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }

override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }

override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
/*
* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
*/

result = this.getAReturnedNode().pointsTo().getClass()
}
}
Expand All @@ -690,9 +753,11 @@ class BuiltinFunctionValue extends FunctionValue {
}

override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
/*
* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
*/

result = TBuiltinClassObject(this.(BuiltinFunctionObjectInternal).getReturnType())
}
}
Expand All @@ -719,7 +784,7 @@ class BuiltinMethodValue extends FunctionValue {
/* Information is unavailable for C code in general */
none()
}

override ClassValue getAnInferredReturnType() {
result = TBuiltinClassObject(this.(BuiltinMethodObjectInternal).getReturnType())
}
Expand Down
15 changes: 0 additions & 15 deletions python/ql/test/library-tests/PointsTo/calls/Argument.expected

This file was deleted.

5 changes: 0 additions & 5 deletions python/ql/test/library-tests/PointsTo/calls/Argument.ql

This file was deleted.

7 changes: 0 additions & 7 deletions python/ql/test/library-tests/PointsTo/calls/Call.expected

This file was deleted.

19 changes: 19 additions & 0 deletions python/ql/test/library-tests/PointsTo/calls/CallPointsTo.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
| 19 | ControlFlowNode for f() | Function f |
| 21 | ControlFlowNode for f() | Function f |
| 22 | ControlFlowNode for C() | class C |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) |
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
| 25 | ControlFlowNode for Attribute() | Function C.n |
| 29 | ControlFlowNode for staticmethod() | builtin-class staticmethod |
| 33 | ControlFlowNode for Attribute() | Function D.foo |
| 34 | ControlFlowNode for Attribute() | Function D.foo |
| 34 | ControlFlowNode for D() | class D |
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) |
| 38 | ControlFlowNode for len() | Builtin-function len |
| 40 | ControlFlowNode for f() | Function f |
| 41 | ControlFlowNode for C() | class C |
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
| 45 | ControlFlowNode for open() | Builtin-function open |
| 46 | ControlFlowNode for open() | Builtin-function open |
| 51 | ControlFlowNode for foo() | Function foo |
| 55 | ControlFlowNode for bar() | Function bar |
5 changes: 5 additions & 0 deletions python/ql/test/library-tests/PointsTo/calls/CallPointsTo.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import python

from CallNode call, Value func
where call.getFunction().pointsTo(func)
select call.getLocation().getStartLine(), call.toString(), func.toString()
23 changes: 23 additions & 0 deletions python/ql/test/library-tests/PointsTo/calls/GetACall.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
| 19 | ControlFlowNode for f() | Function f |
| 21 | ControlFlowNode for f() | Function f |
| 22 | ControlFlowNode for C() | class C |
| 23 | ControlFlowNode for Attribute() | Function f |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) |
| 24 | ControlFlowNode for Attribute() | Function C.n |
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
| 25 | ControlFlowNode for Attribute() | Function C.n |
| 29 | ControlFlowNode for staticmethod() | builtin-class staticmethod |
| 33 | ControlFlowNode for Attribute() | Function D.foo |
| 34 | ControlFlowNode for Attribute() | Function D.foo |
| 34 | ControlFlowNode for D() | class D |
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) |
| 37 | ControlFlowNode for Attribute() | builtin method append |
| 38 | ControlFlowNode for len() | Builtin-function len |
| 40 | ControlFlowNode for f() | Function f |
| 41 | ControlFlowNode for C() | class C |
| 42 | ControlFlowNode for Attribute() | Function C.n |
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) |
| 45 | ControlFlowNode for open() | Builtin-function open |
| 46 | ControlFlowNode for open() | Builtin-function open |
| 51 | ControlFlowNode for foo() | Function foo |
| 55 | ControlFlowNode for bar() | Function bar |
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import python

from ControlFlowNode call, FunctionObject func
from ControlFlowNode call, Value func
where call = func.getACall()
select call.getLocation().getStartLine(), call.toString(), func.toString()
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
| 19 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for w |
| 19 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for x |
| 19 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for y |
| 21 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for y |
| 21 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for w |
| 21 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for z |
| 23 | ControlFlowNode for Attribute() | Function f | 0 | ControlFlowNode for c |
| 23 | ControlFlowNode for Attribute() | Function f | 1 | ControlFlowNode for w |
| 23 | ControlFlowNode for Attribute() | Function f | 2 | ControlFlowNode for z |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | 0 | ControlFlowNode for w |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | 1 | ControlFlowNode for z |
| 24 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for c |
| 24 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for x |
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | 0 | ControlFlowNode for x |
| 25 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for y |
| 25 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for z |
| 33 | ControlFlowNode for Attribute() | Function D.foo | 0 | ControlFlowNode for IntegerLiteral |
| 34 | ControlFlowNode for Attribute() | Function D.foo | 0 | ControlFlowNode for IntegerLiteral |
| 37 | ControlFlowNode for Attribute() | Method(builtin method append, List) | 0 | ControlFlowNode for IntegerLiteral |
| 37 | ControlFlowNode for Attribute() | builtin method append | 0 | ControlFlowNode for l |
| 37 | ControlFlowNode for Attribute() | builtin method append | 1 | ControlFlowNode for IntegerLiteral |
| 38 | ControlFlowNode for len() | Builtin-function len | 0 | ControlFlowNode for l |
| 40 | ControlFlowNode for f() | Function f | 0 | ControlFlowNode for IntegerLiteral |
| 40 | ControlFlowNode for f() | Function f | 1 | ControlFlowNode for IntegerLiteral |
| 40 | ControlFlowNode for f() | Function f | 2 | ControlFlowNode for IntegerLiteral |
| 42 | ControlFlowNode for Attribute() | Function C.n | 0 | ControlFlowNode for c |
| 42 | ControlFlowNode for Attribute() | Function C.n | 1 | ControlFlowNode for IntegerLiteral |
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | 0 | ControlFlowNode for IntegerLiteral |
| 45 | ControlFlowNode for open() | Builtin-function open | 0 | ControlFlowNode for Str |
| 45 | ControlFlowNode for open() | Builtin-function open | 1 | ControlFlowNode for Str |
| 51 | ControlFlowNode for foo() | Function foo | 0 | ControlFlowNode for IntegerLiteral |
| 51 | ControlFlowNode for foo() | Function foo | 1 | ControlFlowNode for IntegerLiteral |
| 51 | ControlFlowNode for foo() | Function foo | 2 | ControlFlowNode for IntegerLiteral |
| 55 | ControlFlowNode for bar() | Function bar | 0 | ControlFlowNode for IntegerLiteral |
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import python

from CallNode call, CallableValue callable, int i
select call.getLocation().getStartLine(), call.toString(), callable.toString(), i,
callable.getArgumentForCall(call, i).toString()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
| 19 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for w |
| 19 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for x |
| 19 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for y |
| 21 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for y |
| 21 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for w |
| 21 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for z |
| 23 | ControlFlowNode for Attribute() | Function f | arg1 | ControlFlowNode for w |
| 23 | ControlFlowNode for Attribute() | Function f | arg2 | ControlFlowNode for z |
| 23 | ControlFlowNode for Attribute() | Function f | self | ControlFlowNode for c |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | arg1 | ControlFlowNode for w |
| 23 | ControlFlowNode for Attribute() | Method(Function f, C()) | arg2 | ControlFlowNode for z |
| 24 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for x |
| 24 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for c |
| 24 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | arg1 | ControlFlowNode for x |
| 25 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for z |
| 25 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for y |
| 33 | ControlFlowNode for Attribute() | Function D.foo | arg | ControlFlowNode for IntegerLiteral |
| 34 | ControlFlowNode for Attribute() | Function D.foo | arg | ControlFlowNode for IntegerLiteral |
| 37 | ControlFlowNode for Attribute() | builtin method append | self | ControlFlowNode for l |
| 40 | ControlFlowNode for f() | Function f | arg0 | ControlFlowNode for IntegerLiteral |
| 40 | ControlFlowNode for f() | Function f | arg1 | ControlFlowNode for IntegerLiteral |
| 40 | ControlFlowNode for f() | Function f | arg2 | ControlFlowNode for IntegerLiteral |
| 42 | ControlFlowNode for Attribute() | Function C.n | arg1 | ControlFlowNode for IntegerLiteral |
| 42 | ControlFlowNode for Attribute() | Function C.n | self | ControlFlowNode for c |
| 42 | ControlFlowNode for Attribute() | Method(Function C.n, C()) | arg1 | ControlFlowNode for IntegerLiteral |
| 46 | ControlFlowNode for open() | Builtin-function open | file | ControlFlowNode for Str |
| 46 | ControlFlowNode for open() | Builtin-function open | mode | ControlFlowNode for Str |
| 51 | ControlFlowNode for foo() | Function foo | a | ControlFlowNode for IntegerLiteral |
| 55 | ControlFlowNode for bar() | Function bar | a | ControlFlowNode for IntegerLiteral |
| 55 | ControlFlowNode for bar() | Function bar | b | ControlFlowNode for IntegerLiteral |
| 55 | ControlFlowNode for bar() | Function bar | c | ControlFlowNode for IntegerLiteral |
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import python

from CallNode call, CallableValue callable, string name
select call.getLocation().getStartLine(), call.toString(), callable.toString(), name,
callable.getNamedArgumentForCall(call, name).toString()
12 changes: 12 additions & 0 deletions python/ql/test/library-tests/PointsTo/calls/getParameter.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
| Function C.n | 0 | ControlFlowNode for self |
| Function C.n | 1 | ControlFlowNode for arg1 |
| Function D.foo | 0 | ControlFlowNode for arg |
| Function bar | 0 | ControlFlowNode for a |
| Function f | 0 | ControlFlowNode for arg0 |
| Function f | 1 | ControlFlowNode for arg1 |
| Function f | 2 | ControlFlowNode for arg2 |
| Function foo | 0 | ControlFlowNode for a |
| Method(Function C.n, C()) | 0 | ControlFlowNode for arg1 |
| Method(Function C.n, class C) | 0 | ControlFlowNode for arg1 |
| Method(Function f, C()) | 0 | ControlFlowNode for arg1 |
| Method(Function f, C()) | 1 | ControlFlowNode for arg2 |
4 changes: 4 additions & 0 deletions python/ql/test/library-tests/PointsTo/calls/getParameter.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import python

from CallableValue callable, int i
select callable.toString(), i, callable.getParameter(i).toString()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
| Function C.n | arg1 | ControlFlowNode for arg1 |
| Function C.n | self | ControlFlowNode for self |
| Function D.foo | arg | ControlFlowNode for arg |
| Function bar | a | ControlFlowNode for a |
| Function f | arg0 | ControlFlowNode for arg0 |
| Function f | arg1 | ControlFlowNode for arg1 |
| Function f | arg2 | ControlFlowNode for arg2 |
| Function foo | a | ControlFlowNode for a |
| Method(Function C.n, C()) | arg1 | ControlFlowNode for arg1 |
| Method(Function C.n, class C) | arg1 | ControlFlowNode for arg1 |
| Method(Function f, C()) | arg1 | ControlFlowNode for arg1 |
| Method(Function f, C()) | arg2 | ControlFlowNode for arg2 |
Loading