-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Sema] Diagnose unsound pointer conversions #27695
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
[Sema] Diagnose unsound pointer conversions #27695
Conversation
Given how large this PR is, I've split off the commits that can be landed separately with no intended functionality change: #27696. Once that has been landed, I can further peel off the main |
@swift-ci please test |
Build failed |
Build failed |
6e6b044
to
75184a0
Compare
@swift-ci please test |
I've peeled off the |
Build failed |
75184a0
to
cc6fddd
Compare
@swift-ci please smoke test |
FWIW, this is essentially an escape analysis and |
@rjmccall Yup, I wanted to keep this distinct from
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is immensely exciting to me! These checks will definitely catch a massive amount of nasty C interop bugs. By combining these sort of diagnostics with some Swift-for-C-Programmer documentation we can resolve some major pain points. I hope we can turn the diagnostics into errors in the release after next. Should we have a bug for that?
@rjmccall mentioned an escape analysis. I don't see any such thing in this PR, except that the manual additional of @nonEphemeral
could be subsumed by a more general analysis. Eventually we should handle general escaping pointers, such as rdar://problem/25001764 Detect when a temporary pointer to a writeback buffer is persisted after the function returns:
func takesPtr(_ ptr: UnsafePointer<UInt64>) {}
private func _ptr(_ ptr: UnsafePointer<UInt64>) -> UnsafePointer<UInt64> {
return UnsafePointer<UInt64>(ptr)
}
var x = UInt64(0)
takesPtr(_ptr(&x))
At that level, the diagnostic should be a SIL pass. We've been moving diagnostics that depend on dataflow analysis out of the type checker and into SIL passes where they can be expressed more robustly and without adding type checker constraints. I suspect the thing to do at that point would be to layer the diagnostics, so most of the logic in this PR stays in the type checker with more difficult cases handled later. At the SIL level, I don't think we need the @nonEphemeral
attribute, since the job of the analysis is to infer that.
I am curious though... is it necessary for the precision of these diagnostics to be part of the constraint system? I don't see why it would be. In fact, it I seems to me that we do not want pointer lifetime mistakes to affect overload resolution. If I'm missing something, please clarify.
I really don't understand all the implications of adding constraints to the type checker, but overall your implementation looks extremely thorough and high quality.
@atrick Thanks for the review! It would be great to see this combined with a SIL pass to diagnose more tricky cases, and I'd certainly be happy to look into that as a potential follow-up :)
Sure thing, I'll file one after this gets merged.
It's not entirely necessary for this to be a part of the constraint system, though it does allow us to emit better diagnostics for things like ambiguities, where we can inform the user that none of the candidates are viable because they don't accept temporary pointer conversions. It will also allow for composition with other constraint system diagnostics, so we can tell the user earlier that what they're trying to do is invalid. Unfortunately this doesn't work so well at the moment as we discard additional fixes with the same locator, but eventually it would be nice to emit a temporary pointer error on the following along with an optional unwrap error: func foo() {
var x: Int?
let ptr = UnsafePointer<Int>(&x)
} I do agree that ideally |
If @xedin and @jrose-apple are also happy to review this as a whole, then I'll close #27705. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-ConstraintSystem parts seem good, but I'm also pretty unhappy with this being part of overload resolution. It feels like something that MiscDiagnostics or even SIL diagnostics should handle instead—after we've type-checked everything, find out if we relied on an ephemeral pointer.
If you want to keep going with this approach, @xedin (or @hborla) should review the ConstraintSystem parts.
// @_nonEphemeral conversion diagnostics | ||
ERROR(cannot_pass_type_to_non_ephemeral,none, | ||
"cannot pass %0 to parameter; argument #%1 must be a pointer that " | ||
"outlives the call%select{| to %3}2", (Type, unsigned, bool, DeclName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is okay but it'd be nice to get the names in here too (see argument_out_of_order_named_named
). That can be follow-up work for sure, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrose-apple Good idea! I'll do this as a follow-up, as we'll probably want to add a new "get argument label" API to ArgumentMismatchFailure
and FunctionArgApplyInfo
.
@jrose-apple Thanks for the review! I'll go ahead and make those changes. Note that @xedin has reviewed the constraint system parts of this over on #27705. Regarding the fact that |
9a22926
to
01c4c09
Compare
Thanks again everyone for the review! I've gone ahead and changed things such the diagnostic no longer affects overload resolution. I've also rebased to resolve a bunch of merge conflicts. If possible I'd like to keep this logic a part of the constraint system, as it's much easier to detect and attach a fix there when evaluating the various X-to-pointer conversions rather than attempting to traverse the AST in MiscDiagnostics. |
@swift-ci please test |
@swift-ci please test source compatibility |
The thing I like about MiscDiagnostics is that it's not mixed up with deciding how a program behaves. The ConstraintSystem decides what the user said, and MiscDiagnostics figures out whether it's actually what they meant or actually allowed. You could sink all of that down to SIL, I guess, but I'm not sure what benefit you'd get. In my head, MiscDiagnostics would ideally be a pluggable visitor the way SIL optimizer passes are pluggable; if you have an expression-level diagnostic, you just add it to the visitor. Then the visitor does a single walk of the AST. (I might be biased by my static analyzer past; the analyzer's "checkers" work a lot like this.) |
The purpose of sinking the diagnostic into SIL is to leverage being able to infer |
Hamish is right: non-ephemerality is a property of the function even as the body changes, so it can't be inferred from the body. (In particular, if a function happens to not escape a pointer today, it should still be able to reserve the privilege to do so.) |
When |
This non-user-facing attribute is used to denote pointer parameters which do not accept pointers produced from temporary pointer conversions such as array-to-pointer, string-to-pointer, and in some cases inout-to-pointer.
Diagnose ephemeral conversions that are passed to @_nonEphemeral parameters. Currently, this defaults to a warning with a frontend flag to upgrade to an error. Hopefully this will become an error by default in a future language version.
These include the pointer-to-pointer and pointer-to-buffer-pointer initialiser parameters amongst a couple of others, such as `Unmanaged.fromOpaque`, and the source for the `move[...]` family of methods.
These include memberwise initializers for pointer properties and enum case constructors for pointer payloads. In both cases, the pointer is being immediately escaped, so don't allow the user to pass a temporary pointer value.
This commit changes the behaviour of the error for passing a temporary pointer conversion to an @_nonEphemeral parameter such that it doesn't affect overload resolution. This is done by recording the fix with an impact of zero, meaning that we don't touch the solution's score. In addition, this change means we no longer need to perform the ranking hack where we favour array-to-pointer, as the disjunction short-circuiting will continue to happen even with the fix recorded.
Currently this does nothing, as we're warning by default, but once we error by default, this will downgrade the diagnostic to a warning.
9b09611
to
65dda6d
Compare
@swift-ci please test |
@swift-ci please test Linux platform |
@hamishknight I'm fine with this, go ahead and merge. |
@swift-ci please smoke test |
I checked, and @atrick is also happy for this to be merged. |
Thanks everyone! I'll work on adding argument labels to the diagnostics, as well as a changelog entry as follow-ups. |
(This is an updated version of #20467)
In this PR is the logic to emit a diagnostic for unsound array-to-pointer, string-to-pointer, and inout-to-pointer conversions. This is done through the introduction of an underscored attribute
@_nonEphemeral
that denotes a pointer parameter which cannot accept pointer conversions that are only valid for the duration of the call they're passed to.This includes all array-to-pointer and string-to-pointer conversions, and most inout-to-pointer conversions with the exception of:
Various pointer parameters in the stdlib have been marked as
@_nonEphemeral
, primarily pointer parameters for initialisers on pointer types. In addition, this PR infers@_nonEphemeral
on pointer parameters for memberwise initialisers and enum case constructors.By default, the diagnostic emitted will be warning. I have added a frontend flag to allow upgrading to an error, and hopefully this will allow us to transition to an error by default in a future language version.
Resolves SR-2790.