Merge pull request #3407 from RasmusWL/python-add-BoundMethodValue-v2

Approved by tausbn
This commit is contained in:
semmle-qlci
2020-05-25 12:00:45 +01:00
committed by GitHub
18 changed files with 270 additions and 41 deletions

View File

@@ -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();
@@ -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() }

View File

@@ -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 |
@@ -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
@@ -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
@@ -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.
*/
@@ -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()
}
}
@@ -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())
}
}
@@ -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())
}

View File

@@ -1,15 +0,0 @@
| 19 | 0 | ControlFlowNode for w | Function f |
| 19 | 1 | ControlFlowNode for x | Function f |
| 19 | 2 | ControlFlowNode for y | Function f |
| 21 | 0 | ControlFlowNode for y | Function f |
| 21 | 1 | ControlFlowNode for w | Function f |
| 21 | 2 | ControlFlowNode for z | Function f |
| 23 | 0 | ControlFlowNode for c | Function f |
| 23 | 1 | ControlFlowNode for w | Function f |
| 23 | 2 | ControlFlowNode for z | Function f |
| 24 | 0 | ControlFlowNode for c | Function n |
| 24 | 1 | ControlFlowNode for x | Function n |
| 25 | 0 | ControlFlowNode for y | Function n |
| 25 | 1 | ControlFlowNode for z | Function n |
| 33 | 0 | ControlFlowNode for IntegerLiteral | Function foo |
| 34 | 0 | ControlFlowNode for IntegerLiteral | Function foo |

View File

@@ -1,5 +0,0 @@
import python
from ControlFlowNode arg, FunctionObject func, int i
where arg = func.getArgumentForCall(_, i)
select arg.getLocation().getStartLine(), i, arg.toString(), func.toString()

View File

@@ -1,7 +0,0 @@
| 19 | ControlFlowNode for f() | Function f |
| 21 | ControlFlowNode for f() | Function f |
| 23 | ControlFlowNode for Attribute() | Function f |
| 24 | ControlFlowNode for Attribute() | Function n |
| 25 | ControlFlowNode for Attribute() | Function n |
| 33 | ControlFlowNode for Attribute() | Function foo |
| 34 | ControlFlowNode for Attribute() | Function foo |

View File

@@ -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 |

View File

@@ -0,0 +1,5 @@
import python
from CallNode call, Value func
where call.getFunction().pointsTo(func)
select call.getLocation().getStartLine(), call.toString(), func.toString()

View File

@@ -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 |

View File

@@ -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()

View File

@@ -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 |

View File

@@ -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()

View File

@@ -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 |

View File

@@ -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()

View File

@@ -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 |

View File

@@ -0,0 +1,4 @@
import python
from CallableValue callable, int i
select callable.toString(), i, callable.getParameter(i).toString()

View File

@@ -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 |

View File

@@ -0,0 +1,4 @@
import python
from CallableValue callable, string name
select callable.toString(), name, callable.getParameterByName(name).toString()

View File

@@ -32,3 +32,24 @@ class D(object):
D.foo(1)
D().foo(2)
l = [1,2,3]
l.append(4)
len(l)
f(arg0=0, arg1=1, arg2=2)
c = C()
c.n(arg1=1)
# positional/keyword arguments for a builtin function
open("foo.txt", "rb") # TODO: Not handled by getNamedArgumentForCall
open(file="foo.txt", mode="rb")
# Testing how arguments to *args and **kwargs are handled
def foo(a, *args):
pass
foo(1, 2, 3)
def bar(a, **kwargs):
pass
bar(a=1, b=2, c=3)