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

Swift: Add variable-capture flow #14577

Merged
merged 32 commits into from
Oct 27, 2023
Merged

Conversation

MathiasVP
Copy link
Contributor

@MathiasVP MathiasVP commented Oct 24, 2023

Commit-by-commit review recommended. I've kept most of Nora's commits, but note that many of the things added in those commits are actually changed by my own commits afterwards 😂.

DCA looks fine 🎉.

The QLDoc check fails because the shared consistency queries don't have any QLDoc. This will only fail this one time because this PR is introducing those consistency queries.

@github-actions github-actions bot added the Swift label Oct 24, 2023
@MathiasVP MathiasVP marked this pull request as ready for review October 25, 2023 08:18
@MathiasVP MathiasVP requested a review from a team as a code owner October 25, 2023 08:18
@MathiasVP MathiasVP added the no-change-note-required This PR does not need a change note label Oct 25, 2023
def.assigns(any(CfgNode cfg | cfg.getNode().asAstNode() = e1))
)
or
e2.(Pattern).getImmediateMatchingExpr() = e1
Copy link
Contributor

Choose a reason for hiding this comment

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

With the declared type of e2 being Expr, does this even work? IIRC this was a problem when we debugged this together and this disjunct should perhaps just be folded into the case above so we can step from Expr to Expr.

Copy link
Contributor Author

@MathiasVP MathiasVP Oct 25, 2023

Choose a reason for hiding this comment

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

Note that e2 is of type CaptureInput::Expr, and CaptureInput::Expr is actually AstNode (which includes Pattern).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's why compilation doesn't fail. I'm asking whether Expr and Pattern might be sufficiently disjoint (even though both are subtypes of AstNode) that this will block flow?

Copy link
Contributor Author

@MathiasVP MathiasVP Oct 26, 2023

Choose a reason for hiding this comment

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

I think I'll leave this to another PR. I've discovered that the WriteDefinition::assigns predicate doesn't really do the right thing at all. For a variable declaration such as:

var x = 0

the WriteDefinition::assigns(value) predicate holds where value is the left-hand side of the declaration (i.e., the lvalue of x) instead of the right-hand side (i.e., 0). Once that's fixed (which I'll do in a later PR) we can remove this disjunct and have CaptureInput::Expr be Expr.

Copy link
Contributor

@geoffw0 geoffw0 left a comment

Choose a reason for hiding this comment

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

Thank you for your review @aschackmull . I've added a few comments but I think you have more insight here than I do.

I'm pleased to see these changes fix multiple [NOT DETECTED] and MISSING results from existing tests, which suggests things are working well enough to make a difference.

I didn't bother investigating captures through constructors in this PR

Is there an issue for / test showing this?

@@ -129,7 +129,7 @@ func forwarder() {
(i: Int) -> Int in
return 0
})
sink(arg: clean)
sink(arg: clean) // $ SPURIOUS: flow=128
Copy link
Contributor

Choose a reason for hiding this comment

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

This result surprises me. Do you have any idea what's gone wrong here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. We're missing call contexts for the call above:

forward(arg: source(), lambda: {
  (i: Int) -> Int in
    return 0
})

so we actually get flow like:

source() (i.e., the argument) -> arg (i.e., the parameter of `forward`) -> arg (i.e., the argument of lambda(arg)) -> i (the parameter of on line 123) -> i (the return on line 124) -> clean

There can be two explanations of this:

  • Swift doesn't implement dispatch through call contexts (see mayBenefitFromCallContext and viableImplInCallContext in DataFlowDispatch).
  • The call contexts exists, but is being thrown away because there's an issue with an enclosing callable. I actually believe this is the case based on @aschackmull's feedback here.

I'll look into this as well.

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 fixed in 2465cc2 (the answer was indeed a version of case 2).

Copy link
Contributor

Choose a reason for hiding this comment

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

We've lost two good results in that change - and they look like fairly straightforward ones, unless I'm missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're losing this one:

func setEscape() {
  let x = source("setEscape", 0)
  escape = {
    sink(x) // $ MISSING: hasValueFlow=setEscape
    return x + 1
  }
}

because we don't model global flow in Swift. So we don't manage to flow from the definition of escape to the place where it's actually called:

func callEscape() {
  setEscape()
  sink(escape?()) // $ MISSING: hasTaintFlow=setEscape
}

In an earlier commit we accidentially got this flow because we flowed from let x = source("setEscape", 0) and straight into the closure (which violates all kinds of dataflow invariants), but when I fixed that in 2465cc2 the result disappears again, and we're only gonna get it back if we implement global flow in Swift.

For this one:

func captureList() {
  let y: Int = source("captureList", 123);
  { [x = hello()] () in
     sink(x) // $ MISSING: hasValueFlow=hello
     sink(y) // $ MISSING: hasValueFlow=captureList
  }()
}

I feel like we should be able to catch sink(y) (but for some reason we still don't). We don't catch the sink(x) one because we're missing an SSA definition for x = hello() and that work is blocked on #14567 (which I've promised Paolo to look at after this is merged).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep I'm comfortable calling the the above an issue with capture lists. I've added a version of this test in 65e13aa without a capture list, and that works fine. So I think we should delay further work on this until #14567 is merged.

@MathiasVP
Copy link
Contributor Author

I didn't bother investigating captures through constructors in this PR

Is there an issue for / test showing this?

For sure not an issue. I haven't checked if we have a test for it (I suspect not, but I'll add one before this PR is merged).

@geoffw0
Copy link
Contributor

geoffw0 commented Oct 25, 2023

Forgot to add, DCA LGTM as well.

@MathiasVP
Copy link
Contributor Author

For sure not an issue. I haven't checked if we have a test for it (I suspect not, but I'll add one before this PR is merged).

I've now investigated this. It seems like this is simply irrelevant for Swift because of fantastic language design choices 😂 (see b41ec37 for more details).

@MathiasVP
Copy link
Contributor Author

@geoffw0 @aschackmull I believe I've addressed all review comments. There are a few unresolved ones (#14577 (comment) and #14577 (comment)) that are blocked on work that I don't want to include in this PR (but which we now have issues/other ongoing PRs for).

@aschackmull
Copy link
Contributor

SGTM. I'll leave approval to @geoffw0 as I haven't looked at the tests at all.

Copy link
Contributor

@geoffw0 geoffw0 left a comment

Choose a reason for hiding this comment

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

Yes I'm happy. There's quite a bit of follow-up work but I agree it's time to get this merged and start on incremental improvements.

@MathiasVP MathiasVP merged commit 4aed638 into github:main Oct 27, 2023
17 of 18 checks passed
@geoffw0
Copy link
Contributor

geoffw0 commented Oct 27, 2023

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation no-change-note-required This PR does not need a change note Swift
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants