- Only step through a `SynthSplatParameterElementNode` when there is a splat parameter
at index > 0.
- Model read+stores via `SynthSplatArgumentElementNode` as a single read-store
step in type tracking.
In cases such as
def f(x, *y); end
f(*[1, 2])
we don't need any `SynthSplatArgumentElementNodes`. We get flow from the
splat argument to a `SynthSplatParameterNode` via `parameterMatch`, then
from element 0 of the synth splat to the positional param `x` via a
read step.
We add a read step from element 1 to `SynthSplatParameterElementNode(1)`.
From there we get flow to element 0 of `*y` via an existing store step.
Allow flow from a splat argument to a positional parameter in cases
where there are positional arguments left of the splat. For example:
def foo(x, y, z); end
foo(1, *[2, 3])
We now precisely track flow from positional arguments to splat
parameters, provided that splat arguments are not used and there are no
positional parameters after the splat parameter. For example, in this
case:
def f(x, y, *z); end
f(a, b, c, d)
we get flow from `c` to `z[0]` and `d` to `z[1]`.
We get false flow if there are positional parameters after the splat
parameter. For example in this case:
def g(x, y, *z, w); end
g(a, b, c, d)
we get flow from `d` to `z[0]` instead of `w`.
We also track flow in this case
def f(a, *b)
sink b[0]
end
f(1, *[taint, 2])
In cases where there are positional parameters after a splat parameter,
don't attempt to match the splat parameter to a splat argument. We need
more sophisticated modelling to handle these cases, which is future
work.
This models flow in the following case:
def foo(x, y)
sink x # 1
sink y # 2
end
args = [source 1, source 2]
foo(*args)
We do this by introducing a SynthSplatParameterNode which accepts
content from the splat argument, if one is given at the callsite.
From this node we add read steps to each positional parameter.