nail down the final validity rules: references and unions - #2337
nail down the final validity rules: references and unions#2337RalfJung wants to merge 2 commits into
Conversation
|
@rustbot label +I-lang-nominated |
75ee1eb to
caf7145
Compare
caf7145 to
93d25cc
Compare
|
A proposal came up in rust-lang/unsafe-code-guidelines#620 to slightly strengthen the requirements for references / @scottmcm do you have suggestions for how that should be worded? |
| For unsized types, this check considers dynamic information from the metadata: | ||
| If `T` has an unsized tail of slice type (`[U]`), it is inhabited of the length encoded in the metadata is 0 or if `U` is inhabited. |
There was a problem hiding this comment.
These two lines are for rust-lang/unsafe-code-guidelines#620. We didn't explicitly discuss this case in the opsem FCP.
a094ac6 to
7aec9f3
Compare
7aec9f3 to
c8ce01e
Compare
I don't have anything concrete. At least as a mental model -- whether it can be phrased well for a reference I'm less sure -- I wonder if there's a general rule here like saying that the type in the reference must, including looking at the metadata, represent a well-formed and inhabited type. (Part of me wants to say it comes from an unsizing, but there's nothing to unsize to a Basically I wish the "length has to be zero for uninhabited elements" and "length bound for non-ZST elements is |
|
So what do you think about the wording I put in this PR? |
|
@traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
|
I reviewed the proposed rules both for unions and for references, and this looks reasonable to me. @rfcbot reviewed |
|
This makes me want a separate language construct for "no, I'm not doing partial overwrite silliness" because this rule has meant that unions are not usable for the cases I wanted, but if this is the right answer for FFI unions and such then so be it. @rfcbot reviewed |
|
🔔 This is now entering its final comment period, as per the review above. 🔔 |
FWIW the unsized type part hasn't been FCP'd by us yet, as mentioned above. We can do that in a separate issue or a joint FCP here. |
|
@rfcbot fcp cancel |
|
@traviscross proposal cancelled. |
|
Thanks @RalfJung. @rfcbot fcp merge lang,opsem (In addition to what was previously FCPed by opsem, opsem is signing off here on what RalfJ mentions in #2337 (comment).) |
|
@traviscross has proposed to merge this. The next step is review by the rest of the tagged team members:
No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
|
@rfcbot reviewed |
|
FWIW @saethlin has ticked their box in rust-lang/unsafe-code-guidelines#620, that should be carried over. |
|
@rfcbot reviewed |
| For unsized types, this check considers dynamic information from the metadata: | ||
| In particular, if `T` has an unsized tail of slice type `[U]`, and if `U` is uninhabited, and if the length encoded in the metadata is non-zero, then the pointee is uninhabited. |
There was a problem hiding this comment.
I feel like making inhabitedness depend on metadata values is not right / clean.
I would rather make this a separate restriction on the value of the metadata. That is, I would say something along the lines of "A reference or Box<T> must ... have valid metadata" and "slice metadata for a slice type with uninhabited elements, it must be 0".
This allows adding other restrictions in the future, such as the one mentioned by @scottmcm that slice metadata can at most be usize::MAX / size_of::<T>() (for non-ZSTs).
There was a problem hiding this comment.
We generally pretty consistently treat the metadata as telling us the "actual type" that we use to inspect the pointee. I think it would be odd do do something else here. It would look like an arbitrary set of rules, rather than just something that falls out of a more general principle.
This allows adding other restrictions in the future, such as the one mentioned by @scottmcm that slice metadata can at most be usize::MAX / size_of::() (for non-ZSTs).
As I mentioned in reply to Scott, that restriction is already present. It follows from the fact that the reference has to be dereferenceable for its actual dynamic size.
There was a problem hiding this comment.
Hm. It still feels weird to me to do it this way for slices, but if we are consistently doing it this way I suppose that is better.
I suppose the reason it feels weird to me is that in the compiler "inhabitedness" is a property of types, and does not concern the value in any way. If we would like exploit this UB in the compiler, we would have to add an arbitrary rule in the form of "if the tail is a slice of an uninhabited type, assume(len == 0)".
In a way, considering the "actual type" feels more arbitrary to me.
That being said, I imagine we have to consider the "actual type" for trait objects, at which point it's better do the same thing for slices too.
(I'd resolve this conversation, but it appears I don't have permissions for that)
There was a problem hiding this comment.
The compiler can only statically approximate all these properties using the types, yes. But that's expected, everything on this page is written assuming full knowledge of the current dynamic state (think: Miri, MiniRust). The compiler might get better at approximating things and that should not change the spec!
IOW, I think you're thinking too much like a compiler writer and not enough like a language specifier. :)
There was a problem hiding this comment.
guilty as charged I'm afraid ^^'
There was a problem hiding this comment.
If we would like exploit this UB in the compiler, we would have to add an arbitrary rule in the form of "if the tail is a slice of an uninhabited type, assume(len == 0)".
To also reply to this specifically -- the compiler does not have to exploit this UB if we think it's too weird or doesn't fit the current compiler architecture. But I would like to keep the door open to the compiler potentially exploiting this UB in the future, as the compiler architecture may change and make this less weird. Maybe one day it becomes trivial to make it so that the length metadata of &[!] is annotated with range information saying it has to be 0. This seems quite plausible, don't we already encode that the length is at most isize::MAX for non-zero-sized types?
There was a problem hiding this comment.
in many contexts we do encode that the length is 0..=floor(isize::MAX/size_of::<pointee>), in particular function arguments. in a few context we do not (due to perf regressions and dubious benefit), and we never do for unsized tails, it's only &[T].
for example, fn meow(x: &[u16]) is i64 noundef range(i64 0, 4611686018427387904) for the length arg, but struct Meow { val: [u16] } fn meow(x: &Meow) is a plain i64 noundef.
There was a problem hiding this comment.
@RalfJung I was not arguing against adding arbitrary rules to the compiler (it's full of them!), I was more so arguing that it should be fine to have them here too (which I don't believe anymore); thanks for explaining your view though :)
@asquared31415 do you know why we are only adding range metadata for Turns out this metadata is calculated with a special case in cg_llvm: https://github.com/rust-lang/rust/blob/c4c4a576936e9e67717d0deb8e74e02dd5dd10de/compiler/rustc_codegen_llvm/src/abi.rs#L561-L579 As the comment suggests, I don't think this is a good way to handle this, hm...&[T] and not &Meow? Seems like an oversight...
There was a problem hiding this comment.
I looked into it as a part of another PR, and it ended up regressing codegen in most cases, with minimal benefits: rust-lang/rust#159921 (comment) unfortunately I don't have numbers on hand, but llvm was making some bad assembly involving 64 bit immediates and sometimes even using a whole extra register (in a contrived test, but I'm unsure if it would go away even after inlining in normal usage)
The attributes are set here in rustc_codegen_llvm, where it matches only Ref(Slice(T)) types, and there's even a note that it's suboptimal.
During the aforementioned PR, I even hit the layout cycles that the comment alludes to, so it's definitely still an issue as of july-ish.
There was a problem hiding this comment.
@scottmcm might have some additional context as the original author of that hack. They mentioned rust-lang/rust#152843 could help get some of the layout info we would like on DSTs, but IIRC I also touched that code in similar ways (with nuw and/or nsw on that math) and ended up getting similar "llvm suddenly emits 64 bit literals and extra registers".
This translates the following t-opsem FCPs into Reference text:
Cc @rust-lang/opsem
@rust-lang/lang you have not FCP'd this yet; please let us know if you want this to go through your own decision process as well.
Fixes rust-lang/unsafe-code-guidelines#413
Fixes rust-lang/unsafe-code-guidelines#414
Fixes rust-lang/unsafe-code-guidelines#438