Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby: More splat flow (alternative) #14090

Merged
merged 11 commits into from
Sep 27, 2023
Merged

Ruby: More splat flow (alternative) #14090

merged 11 commits into from
Sep 27, 2023

Conversation

hmac
Copy link
Contributor

@hmac hmac commented Aug 29, 2023

No description provided.

Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This aligns with how I envisioned it in my comment #13974 (review). A few comments.

exists(c.asCallable()) and // exclude library callables
isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
exists(c.asCallable()) // exclude library callables
// and isParameterNode(_, c, any(ParameterPosition p | p.isSplat(_)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

exists(int n, int splatPos, CfgNodes::ExprNodes::CallCfgNode call |
// Don't propagate taint on the `self` element of the splat
// since that won't (probably) won't reach the parameters of the callable.
// This saves a node per call.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a misunderstanding on my part that content at index -1 represented the value of the container itself. That's why I added the n >= 0 constraint. I've now realised this isn't true and that positional content is only defined for n in [0..10] so I'll remove the constraint and this comment.

// Don't propagate taint on the `self` element of the splat
// since that won't (probably) won't reach the parameters of the callable.
// This saves a node per call.
n >= 0 and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can n ever be < 0?

}

class TSourceParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode;
TSynthHashSplatParameterNode or TSynthSplatParameterNode or TSynthSplatArgParameterNode or
TSynthSplatArgumentElementNode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) or
TSynthSplatArgumentElementNode(CfgNodes::ExprNodes::CallCfgNode c, int n) {
n in [0 .. 10] and
exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) and arg.isArgumentOf(c, pos))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be limited to pos.isSplat(any(int splatPos | splatPos > 0))?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do that, then I think it breaks cases like

def f(x, y); end
f(*[taint, 2])

because we no longer match the explicit splat arg to the synth splat parameter. If we re-add ppos.isSynthSplat() and apos.isSplat(0) to parameterMatch, then we still have a failure in cases like

def g(x, *y); end

g(*[1, taint])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking we would still have ppos.isSynthSplat() and apos.isSplat(0), since there should be no need to go through a TSynthSplatArgumentElementNode node in that case, as the indices don't need to be adjusted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the missing flow in this case

def g(x, *y); end

g(*[1, taint])

by adding a read step from element N of SynthSplatParameterNode to SynthSplatParameterElementNode(N). This means that taint flows to SynthSplatParameterElementNode(1) and then we have an existing store step that will store it from there into the splat parameter at element 0.

I've pushed a change which does this (plus the other changes in this comment thread). Hopefully that does the trick. I am a bit concerned that we're re-using these nodes for multiple things and that can be confusing, but I guess it's necessary to keep performance reasonable.

@hmac hmac force-pushed the splat-flow-4 branch 2 times, most recently from 0ccd5b7 to 93cf226 Compare September 5, 2023 14:48
@hmac hmac requested a review from hvitved September 7, 2023 09:48
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])
hmac and others added 6 commits September 14, 2023 09:26
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.
- 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.
@hmac hmac marked this pull request as ready for review September 14, 2023 08:27
@hmac hmac requested a review from a team as a code owner September 14, 2023 08:27
@hmac hmac added the no-change-note-required This PR does not need a change note label Sep 14, 2023
hvitved
hvitved previously approved these changes Sep 14, 2023
Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

not exists(Argument arg, ArgumentPosition pos | pos.isSplat(_) | arg.isArgumentOf(c, pos))
TSynthSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) or
TSynthSplatArgumentElementNode(CfgNodes::ExprNodes::CallCfgNode c, int n) {
n in [-1 .. 10] and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment saying that we use -1 to represent unknown index.

@hmac hmac merged commit dc2acf5 into github:main Sep 27, 2023
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-change-note-required This PR does not need a change note Ruby
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants