Skip to content

Commit 53ddfce

Browse files
committed
Python: Clarify and document points-to and object model for calls involving starargs.
1 parent 5b06524 commit 53ddfce

File tree

5 files changed

+37
-13
lines changed

5 files changed

+37
-13
lines changed

python/ql/src/semmle/python/Flow.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ class CallNode extends ControlFlowNode {
481481
)
482482
}
483483

484+
/** Gets the tuple (*) argument of this call, provided there is exactly one. */
484485
ControlFlowNode getStarArg() {
485486
result.getNode() = this.getNode().getStarArg() and
486487
result.getBasicBlock().dominates(this.getBasicBlock())

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ module ObjectInternal {
481481
result = TBuiltinClassObject(Builtin::special("ClassType"))
482482
}
483483

484+
ObjectInternal emptyTuple() {
485+
result.(BuiltinTupleObjectInternal).length() = 0
486+
}
487+
484488
}
485489

486490
/** Helper for boolean predicates returning both `true` and `false` */

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ abstract class TupleObjectInternal extends SequenceObjectInternal {
9090

9191
}
9292

93+
/** A tuple built-in to the interpreter, including the empty tuple. */
9394
class BuiltinTupleObjectInternal extends TBuiltinTuple, TupleObjectInternal {
9495

9596
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
@@ -116,7 +117,7 @@ class BuiltinTupleObjectInternal extends TBuiltinTuple, TupleObjectInternal {
116117
}
117118
}
118119

119-
120+
/** A tuple declared by a tuple expression in the Python source code */
120121
class PythonTupleObjectInternal extends TPythonTuple, TupleObjectInternal {
121122

122123
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
@@ -147,6 +148,7 @@ class PythonTupleObjectInternal extends TPythonTuple, TupleObjectInternal {
147148

148149
}
149150

151+
/** A tuple created by a `*` parameter */
150152
class VarargsTupleObjectInternal extends TVarargsTuple, TupleObjectInternal {
151153

152154
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ cached newtype TObject =
181181
or
182182
/* Varargs tuple */
183183
TVarargsTuple(CallNode call, PointsToContext context, int offset, int length) {
184-
InterProceduralPointsTo::varargs_tuple(call, _, context, _, offset, length)
184+
InterProceduralPointsTo::varargs_tuple(call, context, _, _, offset, length)
185185
}
186186
or
187187
/* `type` */

python/ql/src/semmle/python/pointsto/PointsTo.qll

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -900,38 +900,46 @@ module InterProceduralPointsTo {
900900
/** Helper for parameter_points_to */
901901
pragma [noinline]
902902
private predicate special_parameter_points_to(ParameterDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
903+
/* Runtime: Just an unknown tuple (or dict for `**` args) */
903904
special_parameter_value(def, value) and
904905
context.isRuntime() and
905906
origin = def.getDefiningNode()
906907
or
908+
/* A tuple constructed from positional arguments for a `*` parameter. */
907909
exists(CallNode call, Function scope, PointsToContext caller, int offset, int length |
908-
varargs_tuple(call, scope, caller, context, offset, length) and
910+
varargs_tuple(call, caller, scope, context, offset, length) and
909911
value = TVarargsTuple(call, caller, offset, length) and
910912
def.getScope() = scope
911913
) and
912914
origin = def.getDefiningNode()
913915
or
916+
/* A `*` parameter with no surplus positional arguments; an empty tuple */
914917
exists(Function scope |
915918
varargs_empty_tuple(scope, context) and
916-
value.(BuiltinTupleObjectInternal).length() = 0 and
919+
value = ObjectInternal::emptyTuple() and
917920
def.getScope() = scope
918921
) and
919922
origin = def.getDefiningNode()
920923
}
921924

922-
predicate varargs_tuple(CallNode call, Function scope, PointsToContext caller, PointsToContext callee, int startOffset, int length) {
925+
/** Holds if `call` in context `caller` calls into the function scope `func` in context `callee` and
926+
* that the number of position arguments (including expansion of `*` argument) exceeds the number of positional arguments by
927+
* `length` and that the excess arguments start at `start`.
928+
*/
929+
predicate varargs_tuple(CallNode call, PointsToContext caller, Function scope, PointsToContext callee, int start, int length) {
923930
exists(int parameter_offset |
924931
callsite_calls_function(call, caller, scope, callee, parameter_offset) and
925-
startOffset = scope.getPositionalParameterCount() - parameter_offset and
926-
length = positional_argument_count(call, caller) - startOffset and
932+
start = scope.getPositionalParameterCount() - parameter_offset and
933+
length = positional_argument_count(call, caller) - start and
927934
length > 0
928935
)
929936
}
930937

931-
predicate varargs_empty_tuple(Function scope, PointsToContext callee) {
938+
/** Holds if for function scope `func` in context `callee` the `*` parameter will hold the empty tuple. */
939+
predicate varargs_empty_tuple(Function func, PointsToContext callee) {
932940
exists(CallNode call, PointsToContext caller, int parameter_offset |
933-
callsite_calls_function(call, caller, scope, callee, parameter_offset) and
934-
scope.getPositionalParameterCount() - parameter_offset >= positional_argument_count(call, caller)
941+
callsite_calls_function(call, caller, func, callee, parameter_offset) and
942+
func.getPositionalParameterCount() - parameter_offset >= positional_argument_count(call, caller)
935943
)
936944
}
937945

@@ -942,17 +950,21 @@ module InterProceduralPointsTo {
942950
p.isKwargs() and value = TUnknownInstance(ObjectInternal::builtin("dict"))
943951
}
944952

945-
predicate positional_argument_points_to(CallNode call, int argument, PointsToContext caller, ObjectInternal value, ControlFlowNode origin) {
946-
PointsToInternal::pointsTo(call.getArg(argument), caller, value, origin)
953+
/** Holds if the `n`th argument in call `call` with context `caller` points-to `value` from `origin`, including values in tuples
954+
* expanded by a `*` argument. For example, for the call `f('a', *(`x`,`y`))` the arguments are `('a', 'x', y')`
955+
*/
956+
predicate positional_argument_points_to(CallNode call, int n, PointsToContext caller, ObjectInternal value, ControlFlowNode origin) {
957+
PointsToInternal::pointsTo(call.getArg(n), caller, value, origin)
947958
or
948959
exists(SequenceObjectInternal arg, int pos |
949960
pos = call.getNode().getPositionalArgumentCount() and
950961
PointsToInternal::pointsTo(origin, caller, arg, _) and
951-
value = arg.getItem(argument-pos) and
962+
value = arg.getItem(n-pos) and
952963
origin = call.getStarArg()
953964
)
954965
}
955966

967+
/** Gets the number of positional arguments including values in tuples expanded by a `*` argument.*/
956968
private int positional_argument_count(CallNode call, PointsToContext caller) {
957969
result = call.getNode().getPositionalArgumentCount() and not exists(call.getStarArg()) and caller.appliesTo(call)
958970
or
@@ -963,6 +975,7 @@ module InterProceduralPointsTo {
963975
)
964976
}
965977

978+
/** Holds if the parameter definition `def` points-to `value` from `origin` given the context `context` */
966979
predicate positional_parameter_points_to(ParameterDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
967980
exists(CallNode call, int argument, PointsToContext caller, Function func, int offset |
968981
positional_argument_points_to(call, argument, caller, value, origin) and
@@ -971,6 +984,7 @@ module InterProceduralPointsTo {
971984
)
972985
}
973986

987+
/** Holds if the named `argument` given the context `caller` is transferred to the parameter `param` with conntext `callee` by a call. */
974988
cached predicate named_argument_transfer(ControlFlowNode argument, PointsToContext caller, ParameterDefinition param, PointsToContext callee) {
975989
exists(CallNode call, Function func, int offset |
976990
callsite_calls_function(call, caller, func, callee, offset)
@@ -982,6 +996,9 @@ module InterProceduralPointsTo {
982996
)
983997
}
984998

999+
/** Holds if the `call` with context `caller` calls the function `scope` in context `callee`
1000+
* and the offset from argument to parameter is `parameter_offset`
1001+
*/
9851002
cached predicate callsite_calls_function(CallNode call, PointsToContext caller, Function scope, PointsToContext callee, int parameter_offset) {
9861003
exists(ObjectInternal func |
9871004
callWithContext(call, caller, func, callee) and

0 commit comments

Comments
 (0)