mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
Merge pull request #1391 from markshannon/python-points-to-varargs
Python points to varargs
This commit is contained in:
@@ -168,12 +168,12 @@ class Call extends Call_ {
|
||||
|
||||
override CallNode getAFlowNode() { result = super.getAFlowNode() }
|
||||
|
||||
/** Gets a tuple (*) argument of this class definition. */
|
||||
/** Gets a tuple (*) argument of this call. */
|
||||
Expr getStarargs() {
|
||||
result = this.getAPositionalArg().(Starred).getValue()
|
||||
}
|
||||
|
||||
/** Gets a dictionary (**) argument of this class definition. */
|
||||
/** Gets a dictionary (**) argument of this call. */
|
||||
Expr getKwargs() {
|
||||
result = this.getANamedArg().(DictUnpacking).getValue()
|
||||
}
|
||||
@@ -227,6 +227,18 @@ class Call extends Call_ {
|
||||
result = this.getKwargs().(Dict).getAKey().(StrConst).getText()
|
||||
}
|
||||
|
||||
/** Gets the positional argument count of this call, provided there is no more than one tuple (*) argument. */
|
||||
int getPositionalArgumentCount() {
|
||||
count(this.getStarargs()) < 2 and
|
||||
result = count(Expr arg | arg = this.getAPositionalArg() and not arg instanceof Starred)
|
||||
}
|
||||
|
||||
/** Gets the tuple (*) argument of this call, provided there is exactly one. */
|
||||
Expr getStarArg() {
|
||||
count(this.getStarargs()) < 2 and
|
||||
result = getStarargs()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A conditional expression such as, `body if test else orelse` */
|
||||
|
||||
@@ -481,6 +481,12 @@ class CallNode extends ControlFlowNode {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the tuple (*) argument of this call, provided there is exactly one. */
|
||||
ControlFlowNode getStarArg() {
|
||||
result.getNode() = this.getNode().getStarArg() and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A control flow corresponding to an attribute expression, such as `value.attr` */
|
||||
|
||||
@@ -481,6 +481,10 @@ module ObjectInternal {
|
||||
result = TBuiltinClassObject(Builtin::special("ClassType"))
|
||||
}
|
||||
|
||||
ObjectInternal emptyTuple() {
|
||||
result.(BuiltinTupleObjectInternal).length() = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Helper for boolean predicates returning both `true` and `false` */
|
||||
|
||||
@@ -39,7 +39,9 @@ abstract class TupleObjectInternal extends SequenceObjectInternal {
|
||||
}
|
||||
|
||||
private string contents(int n) {
|
||||
n = this.length() and result = ""
|
||||
n < 4 and n = this.length() and result = ""
|
||||
or
|
||||
n = 3 and this.length() > 3 and result = (this.length()-3).toString() + " more..."
|
||||
or
|
||||
result = this.getItem(n).toString() + ", " + this.contents(n+1)
|
||||
}
|
||||
@@ -88,6 +90,7 @@ abstract class TupleObjectInternal extends SequenceObjectInternal {
|
||||
|
||||
}
|
||||
|
||||
/** A tuple built-in to the interpreter, including the empty tuple. */
|
||||
class BuiltinTupleObjectInternal extends TBuiltinTuple, TupleObjectInternal {
|
||||
|
||||
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
|
||||
@@ -114,7 +117,7 @@ class BuiltinTupleObjectInternal extends TBuiltinTuple, TupleObjectInternal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A tuple declared by a tuple expression in the Python source code */
|
||||
class PythonTupleObjectInternal extends TPythonTuple, TupleObjectInternal {
|
||||
|
||||
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
|
||||
@@ -145,6 +148,35 @@ class PythonTupleObjectInternal extends TPythonTuple, TupleObjectInternal {
|
||||
|
||||
}
|
||||
|
||||
/** A tuple created by a `*` parameter */
|
||||
class VarargsTupleObjectInternal extends TVarargsTuple, TupleObjectInternal {
|
||||
|
||||
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
|
||||
none()
|
||||
}
|
||||
|
||||
override Builtin getBuiltin() {
|
||||
none()
|
||||
}
|
||||
|
||||
override ControlFlowNode getOrigin() {
|
||||
none()
|
||||
}
|
||||
|
||||
override ObjectInternal getItem(int n) {
|
||||
exists(CallNode call, PointsToContext context, int offset, int length |
|
||||
this = TVarargsTuple(call, context, offset, length) and
|
||||
n < length and
|
||||
InterProceduralPointsTo::positional_argument_points_to(call, offset+n, context, result, _)
|
||||
)
|
||||
}
|
||||
|
||||
override int length() {
|
||||
this = TVarargsTuple(_, _, _, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** The `sys.version_info` object. We treat this specially to prevent premature pruning and
|
||||
* false positives when we are unsure of the actual version of Python that the code is expecting.
|
||||
*/
|
||||
|
||||
@@ -179,6 +179,11 @@ cached newtype TObject =
|
||||
context.appliesTo(origin)
|
||||
}
|
||||
or
|
||||
/* Varargs tuple */
|
||||
TVarargsTuple(CallNode call, PointsToContext context, int offset, int length) {
|
||||
InterProceduralPointsTo::varargs_tuple(call, context, _, _, offset, length)
|
||||
}
|
||||
or
|
||||
/* `type` */
|
||||
TType()
|
||||
or
|
||||
|
||||
@@ -844,11 +844,13 @@ module InterProceduralPointsTo {
|
||||
private predicate normal_parameter_points_to(ParameterDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
|
||||
exists(PointsToContext caller, ControlFlowNode arg |
|
||||
PointsToInternal::pointsTo(arg, caller, value, origin) and
|
||||
callsite_argument_transfer(arg, caller, def, context)
|
||||
named_argument_transfer(arg, caller, def, context)
|
||||
)
|
||||
or
|
||||
not def.isSelf() and not def.isVarargs() and not def.isKwargs() and
|
||||
context.isRuntime() and value = ObjectInternal::unknown() and origin = def.getDefiningNode()
|
||||
or
|
||||
positional_parameter_points_to(def, context, value, origin)
|
||||
}
|
||||
|
||||
pragma [noinline]
|
||||
@@ -898,18 +900,49 @@ module InterProceduralPointsTo {
|
||||
/** Helper for parameter_points_to */
|
||||
pragma [noinline]
|
||||
private predicate special_parameter_points_to(ParameterDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
|
||||
/* Runtime: Just an unknown tuple (or dict for `**` args) */
|
||||
special_parameter_value(def, value) and
|
||||
(
|
||||
context.isRuntime()
|
||||
or
|
||||
exists(PointsToContext caller, CallNode call |
|
||||
context.fromCall(call, caller) and
|
||||
context.appliesToScope(def.getScope()) and
|
||||
not exists(call.getArg(def.getParameter().getPosition())) and
|
||||
not exists(call.getArgByName(def.getParameter().getName()))
|
||||
)
|
||||
context.isRuntime() and
|
||||
origin = def.getDefiningNode()
|
||||
or
|
||||
/* A tuple constructed from positional arguments for a `*` parameter. */
|
||||
def.isVarargs() and
|
||||
exists(CallNode call, Function scope, PointsToContext caller, int offset, int length |
|
||||
varargs_tuple(call, caller, scope, context, offset, length) and
|
||||
value = TVarargsTuple(call, caller, offset, length) and
|
||||
def.getScope() = scope
|
||||
) and
|
||||
origin = def.getDefiningNode()
|
||||
or
|
||||
/* A `*` parameter with no surplus positional arguments; an empty tuple */
|
||||
def.isVarargs() and
|
||||
exists(Function scope |
|
||||
varargs_empty_tuple(scope, context) and
|
||||
value = ObjectInternal::emptyTuple() and
|
||||
def.getScope() = scope
|
||||
) and
|
||||
origin = def.getDefiningNode()
|
||||
}
|
||||
|
||||
/** Holds if `call` in context `caller` calls into the function scope `scope` in context `callee` and
|
||||
* that the number of position arguments (including expansion of `*` argument) exceeds the number of positional arguments by
|
||||
* `length` and that the excess arguments start at `start`.
|
||||
*/
|
||||
predicate varargs_tuple(CallNode call, PointsToContext caller, Function scope, PointsToContext callee, int start, int length) {
|
||||
exists(int parameter_offset |
|
||||
callsite_calls_function(call, caller, scope, callee, parameter_offset) and
|
||||
start = scope.getPositionalParameterCount() - parameter_offset and
|
||||
length = positional_argument_count(call, caller) - start and
|
||||
length > 0
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if for function scope `func` in context `callee` the `*` parameter will hold the empty tuple. */
|
||||
predicate varargs_empty_tuple(Function func, PointsToContext callee) {
|
||||
exists(CallNode call, PointsToContext caller, int parameter_offset |
|
||||
callsite_calls_function(call, caller, func, callee, parameter_offset) and
|
||||
func.getPositionalParameterCount() - parameter_offset >= positional_argument_count(call, caller)
|
||||
)
|
||||
}
|
||||
|
||||
/** Helper predicate for special_parameter_points_to */
|
||||
@@ -919,16 +952,45 @@ module InterProceduralPointsTo {
|
||||
p.isKwargs() and value = TUnknownInstance(ObjectInternal::builtin("dict"))
|
||||
}
|
||||
|
||||
/** Holds if the `(argument, caller)` pair matches up with `(param, callee)` pair across call. */
|
||||
cached predicate callsite_argument_transfer(ControlFlowNode argument, PointsToContext caller, ParameterDefinition param, PointsToContext callee) {
|
||||
/** Holds if the `n`th argument in call `call` with context `caller` points-to `value` from `origin`, including values in tuples
|
||||
* expanded by a `*` argument. For example, for the call `f('a', *(`x`,`y`))` the arguments are `('a', 'x', y')`
|
||||
*/
|
||||
predicate positional_argument_points_to(CallNode call, int n, PointsToContext caller, ObjectInternal value, ControlFlowNode origin) {
|
||||
PointsToInternal::pointsTo(call.getArg(n), caller, value, origin)
|
||||
or
|
||||
exists(SequenceObjectInternal arg, int pos |
|
||||
pos = call.getNode().getPositionalArgumentCount() and
|
||||
PointsToInternal::pointsTo(origin, caller, arg, _) and
|
||||
value = arg.getItem(n-pos) and
|
||||
origin = call.getStarArg()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of positional arguments including values in tuples expanded by a `*` argument.*/
|
||||
private int positional_argument_count(CallNode call, PointsToContext caller) {
|
||||
result = call.getNode().getPositionalArgumentCount() and not exists(call.getStarArg()) and caller.appliesTo(call)
|
||||
or
|
||||
exists(SequenceObjectInternal arg, int pos |
|
||||
pos = call.getNode().getPositionalArgumentCount() and
|
||||
PointsToInternal::pointsTo(call.getStarArg(), caller, arg, _) and
|
||||
result = pos + arg.length()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the parameter definition `def` points-to `value` from `origin` given the context `context` */
|
||||
predicate positional_parameter_points_to(ParameterDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
|
||||
exists(CallNode call, int argument, PointsToContext caller, Function func, int offset |
|
||||
positional_argument_points_to(call, argument, caller, value, origin) and
|
||||
callsite_calls_function(call, caller, func, context, offset) and
|
||||
def.getParameter() = func.getArg(argument+offset)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the named `argument` given the context `caller` is transferred to the parameter `param` with conntext `callee` by a call. */
|
||||
cached predicate named_argument_transfer(ControlFlowNode argument, PointsToContext caller, ParameterDefinition param, PointsToContext callee) {
|
||||
exists(CallNode call, Function func, int offset |
|
||||
callsite_calls_function(call, caller, func, callee, offset)
|
||||
|
|
||||
exists(int n |
|
||||
argument = call.getArg(n) and
|
||||
param.getParameter() = func.getArg(n+offset)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
argument = call.getArgByName(name) and
|
||||
param.getParameter() = func.getArgByName(name)
|
||||
@@ -936,6 +998,9 @@ module InterProceduralPointsTo {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the `call` with context `caller` calls the function `scope` in context `callee`
|
||||
* and the offset from argument to parameter is `parameter_offset`
|
||||
*/
|
||||
cached predicate callsite_calls_function(CallNode call, PointsToContext caller, Function scope, PointsToContext callee, int parameter_offset) {
|
||||
exists(ObjectInternal func |
|
||||
callWithContext(call, caller, func, callee) and
|
||||
|
||||
Reference in New Issue
Block a user