@@ -844,11 +844,13 @@ module InterProceduralPointsTo {
844844 private predicate normal_parameter_points_to ( ParameterDefinition def , PointsToContext context , ObjectInternal value , ControlFlowNode origin ) {
845845 exists ( PointsToContext caller , ControlFlowNode arg |
846846 PointsToInternal:: pointsTo ( arg , caller , value , origin ) and
847- callsite_argument_transfer ( arg , caller , def , context )
847+ named_argument_transfer ( arg , caller , def , context )
848848 )
849849 or
850850 not def .isSelf ( ) and not def .isVarargs ( ) and not def .isKwargs ( ) and
851851 context .isRuntime ( ) and value = ObjectInternal:: unknown ( ) and origin = def .getDefiningNode ( )
852+ or
853+ positional_parameter_points_to ( def , context , value , origin )
852854 }
853855
854856 pragma [ noinline]
@@ -898,44 +900,107 @@ module InterProceduralPointsTo {
898900 /** Helper for parameter_points_to */
899901 pragma [ noinline]
900902 private predicate special_parameter_points_to ( ParameterDefinition def , PointsToContext context , ObjectInternal value , ControlFlowNode origin ) {
903+ /* Runtime: Just an unknown tuple (or dict for `**` args) */
901904 special_parameter_value ( def , value ) and
902- (
903- context .isRuntime ( )
904- or
905- exists ( PointsToContext caller , CallNode call |
906- context .fromCall ( call , caller ) and
907- context .appliesToScope ( def .getScope ( ) ) and
908- not exists ( call .getArg ( def .getParameter ( ) .getPosition ( ) ) ) and
909- not exists ( call .getArgByName ( def .getParameter ( ) .getName ( ) ) )
910- )
905+ context .isRuntime ( ) and
906+ origin = def .getDefiningNode ( )
907+ or
908+ /* A tuple constructed from positional arguments for a `*` parameter. */
909+ def .isVarargs ( ) and
910+ exists ( CallNode call , Function scope , PointsToContext caller , int offset , int length |
911+ varargs_tuple ( call , caller , scope , context , offset , length ) and
912+ value = TVarargsTuple ( call , caller , offset , length ) and
913+ def .getScope ( ) = scope
914+ ) and
915+ origin = def .getDefiningNode ( )
916+ or
917+ /* A `*` parameter with no surplus positional arguments; an empty tuple */
918+ def .isVarargs ( ) and
919+ exists ( Function scope |
920+ varargs_empty_tuple ( scope , context ) and
921+ value = ObjectInternal:: emptyTuple ( ) and
922+ def .getScope ( ) = scope
911923 ) and
912924 origin = def .getDefiningNode ( )
913925 }
914926
927+ /** Holds if `call` in context `caller` calls into the function scope `scope` in context `callee` and
928+ * that the number of position arguments (including expansion of `*` argument) exceeds the number of positional arguments by
929+ * `length` and that the excess arguments start at `start`.
930+ */
931+ predicate varargs_tuple ( CallNode call , PointsToContext caller , Function scope , PointsToContext callee , int start , int length ) {
932+ exists ( int parameter_offset |
933+ callsite_calls_function ( call , caller , scope , callee , parameter_offset ) and
934+ start = scope .getPositionalParameterCount ( ) - parameter_offset and
935+ length = positional_argument_count ( call , caller ) - start and
936+ length > 0
937+ )
938+ }
939+
940+ /** Holds if for function scope `func` in context `callee` the `*` parameter will hold the empty tuple. */
941+ predicate varargs_empty_tuple ( Function func , PointsToContext callee ) {
942+ exists ( CallNode call , PointsToContext caller , int parameter_offset |
943+ callsite_calls_function ( call , caller , func , callee , parameter_offset ) and
944+ func .getPositionalParameterCount ( ) - parameter_offset >= positional_argument_count ( call , caller )
945+ )
946+ }
947+
915948 /** Helper predicate for special_parameter_points_to */
916949 private predicate special_parameter_value ( ParameterDefinition p , ObjectInternal value ) {
917950 p .isVarargs ( ) and value = TUnknownInstance ( ObjectInternal:: builtin ( "tuple" ) )
918951 or
919952 p .isKwargs ( ) and value = TUnknownInstance ( ObjectInternal:: builtin ( "dict" ) )
920953 }
921954
922- /** Holds if the `(argument, caller)` pair matches up with `(param, callee)` pair across call. */
923- cached predicate callsite_argument_transfer ( ControlFlowNode argument , PointsToContext caller , ParameterDefinition param , PointsToContext callee ) {
955+ /** Holds if the `n`th argument in call `call` with context `caller` points-to `value` from `origin`, including values in tuples
956+ * expanded by a `*` argument. For example, for the call `f('a', *(`x`,`y`))` the arguments are `('a', 'x', y')`
957+ */
958+ predicate positional_argument_points_to ( CallNode call , int n , PointsToContext caller , ObjectInternal value , ControlFlowNode origin ) {
959+ PointsToInternal:: pointsTo ( call .getArg ( n ) , caller , value , origin )
960+ or
961+ exists ( SequenceObjectInternal arg , int pos |
962+ pos = call .getNode ( ) .getPositionalArgumentCount ( ) and
963+ PointsToInternal:: pointsTo ( origin , caller , arg , _) and
964+ value = arg .getItem ( n - pos ) and
965+ origin = call .getStarArg ( )
966+ )
967+ }
968+
969+ /** Gets the number of positional arguments including values in tuples expanded by a `*` argument.*/
970+ private int positional_argument_count ( CallNode call , PointsToContext caller ) {
971+ result = call .getNode ( ) .getPositionalArgumentCount ( ) and not exists ( call .getStarArg ( ) ) and caller .appliesTo ( call )
972+ or
973+ exists ( SequenceObjectInternal arg , int pos |
974+ pos = call .getNode ( ) .getPositionalArgumentCount ( ) and
975+ PointsToInternal:: pointsTo ( call .getStarArg ( ) , caller , arg , _) and
976+ result = pos + arg .length ( )
977+ )
978+ }
979+
980+ /** Holds if the parameter definition `def` points-to `value` from `origin` given the context `context` */
981+ predicate positional_parameter_points_to ( ParameterDefinition def , PointsToContext context , ObjectInternal value , ControlFlowNode origin ) {
982+ exists ( CallNode call , int argument , PointsToContext caller , Function func , int offset |
983+ positional_argument_points_to ( call , argument , caller , value , origin ) and
984+ callsite_calls_function ( call , caller , func , context , offset ) and
985+ def .getParameter ( ) = func .getArg ( argument + offset )
986+ )
987+ }
988+
989+ /** Holds if the named `argument` given the context `caller` is transferred to the parameter `param` with conntext `callee` by a call. */
990+ cached predicate named_argument_transfer ( ControlFlowNode argument , PointsToContext caller , ParameterDefinition param , PointsToContext callee ) {
924991 exists ( CallNode call , Function func , int offset |
925992 callsite_calls_function ( call , caller , func , callee , offset )
926993 |
927- exists ( int n |
928- argument = call .getArg ( n ) and
929- param .getParameter ( ) = func .getArg ( n + offset )
930- )
931- or
932994 exists ( string name |
933995 argument = call .getArgByName ( name ) and
934996 param .getParameter ( ) = func .getArgByName ( name )
935997 )
936998 )
937999 }
9381000
1001+ /** Holds if the `call` with context `caller` calls the function `scope` in context `callee`
1002+ * and the offset from argument to parameter is `parameter_offset`
1003+ */
9391004 cached predicate callsite_calls_function ( CallNode call , PointsToContext caller , Function scope , PointsToContext callee , int parameter_offset ) {
9401005 exists ( ObjectInternal func |
9411006 callWithContext ( call , caller , func , callee ) and
0 commit comments