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: Dataflow through ?? and ? : #11270

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Conversation

geoffw0
Copy link
Contributor

@geoffw0 geoffw0 commented Nov 15, 2022

Adds dataflow through the nil coalescing operator (??) and the ternary operator (? :), along with more tests of optionals.

Flow through optional chaining (?.) was already working.

Flow through the second argument of ?? does not currently work, despite the code for this path (tested as far as I could). This appears to be because the Expr nodes inside the (typically) AutoClosureExpr there are not DataFlow::Nodes. Assuming @MathiasVP can't suggest a quick fix I will create a follow-up issue to address that.

@geoffw0 geoffw0 added the Swift label Nov 15, 2022
@geoffw0 geoffw0 requested a review from a team as a code owner Nov 15, 2022
Copy link
Contributor

@MathiasVP MathiasVP left a comment

Flow through the second argument of ?? does not currently work, despite the code for this path (tested as far as I could). This appears to be because the Expr nodes inside the (typically) AutoClosureExpr there are not DataFlow::Nodes.
Assuming @MathiasVP can't suggest a quick fix I will create a follow-up issue to address that.

This is probably a CFG issue (since non-reachable code isn't given any DataFlow::Nodes). Let's address this as a follow-up.

I have a couple of stylistic comments, but otherwise the code LGTM!

exists(BinaryExpr nco |
nco.getFunction().(DeclRefExpr).getDecl().(FreeFunctionDecl).getName() = "??(_:_:)" and
(
// value argument
nodeFrom.asExpr() = nco.getAnOperand()
or
// unpack closure (the second argument is typically an `AutoClosureExpr` argument)
nodeFrom.asExpr() =
nco.getAnOperand().(AbstractClosureExpr).getBody().getAnElement().(ReturnStmt).getResult()
) and
nodeTo.asExpr() = nco
)
Copy link
Contributor

@MathiasVP MathiasVP Nov 15, 2022

Choose a reason for hiding this comment

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

A couple of cleanup things. I think the second operand of ?? will always be an AutoClosureExpr. In that case, we can use the getReturn predicate on that class. There are two additional cleanups we can do here:

  • Calling getStaticTarget on a BinaryExpr is a bit weird. Maybe we should have a synonym (like getOperator?) defined on BinaryExpr (and similarly for UnaryExprs). And maybe we should have a publically-exposed class for the ?? operator?
  • AutoClosureExpr.getReturn() is necessary in CFG construction, but non-CFG code probably wants a way to get the expression being returned (instead of getting the ReturnStmt). Maybe we should have a getExpr predicate on AutoClosureExpr that's defined as this.getReturn().getResult()?
Suggested change
exists(BinaryExpr nco |
nco.getFunction().(DeclRefExpr).getDecl().(FreeFunctionDecl).getName() = "??(_:_:)" and
(
// value argument
nodeFrom.asExpr() = nco.getAnOperand()
or
// unpack closure (the second argument is typically an `AutoClosureExpr` argument)
nodeFrom.asExpr() =
nco.getAnOperand().(AbstractClosureExpr).getBody().getAnElement().(ReturnStmt).getResult()
) and
nodeTo.asExpr() = nco
)
exists(BinaryExpr nco |
nco.getStaticTarget().(FreeFunctionDecl).getName() = "??(_:_:)" and
nodeTo.asExpr() = nco
|
// value argument
nodeFrom.asExpr() = nco.getAnOperand()
or
// unpack closure (the second argument is typically an `AutoClosureExpr` argument)
nodeFrom.asExpr() = nco.getAnOperand().(AutoClosureExpr).getReturn().getResult()
)

Copy link
Contributor Author

@geoffw0 geoffw0 Nov 15, 2022

Choose a reason for hiding this comment

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

I think the second operand of ?? will always be an AutoClosureExpr.

My understanding is that you can provide a manual closure, in which case it won't be an AutoClosureExpr. I could be wrong. The @autoclosure tag on the second argument just means (I think) that Swift is allowed to automatically turn an expression in this position into a closure (AutoClosureExpr). So you can write:

x ?? 0

rather than:

x ?? { return 0 }

or something like that.


I'll add a BinaryExpr.getOperator (or something similar, maybe .getOperation?).


I agree about AutoClosureExpr.getExpr but I don't think I want to use AutoClosureExpr in this PR.

Copy link
Contributor Author

@geoffw0 geoffw0 Nov 15, 2022

Choose a reason for hiding this comment

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

My understanding is that you can provide a manual closure, in which case it won't be an AutoClosureExpr.

... every doc I read backs me up that @autoclosure "lets you omit braces" or similar phrasing, but swiftc is not happy if I leave them in. So it appears you're right, @autoclosure actually means you must omit the braces, and you presumably always get an AutoClosureExpr as a result.

Copy link
Contributor Author

@geoffw0 geoffw0 Nov 15, 2022

Choose a reason for hiding this comment

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

All points addressed, I think. There's no test coverage for the unary expr predicates though.

Co-authored-by: Mathias Vorreiter Pedersen <mathiasvp@github.com>
@geoffw0
Copy link
Contributor Author

geoffw0 commented Nov 15, 2022

Issue for the missing CFG: github/codeql-c-team#1384

MathiasVP
MathiasVP previously approved these changes Nov 16, 2022
Copy link
Contributor

@MathiasVP MathiasVP left a comment

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants