-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
References into repr(packed) structs should be unsafe
.
#1240
Conversation
Taking a reference to a member of a packed struct is a very wrong thing to do, unless...
Now going to step back and consider what the actual uses of
Given the above, this sounds like the correct approach to take. I just have a few points to bring up:
|
I'm confused, I don't really understand your comment in the light of rust-lang/rust#27060 (comment) . Is "this" the design the RFC has? (i.e. are you broadly in favour?)
NB. I do cover this in the Alternatives section. However, I think it's relatively low-benefit and can be introduced later if necessary: I don't recall crater bringing up a single instance of
Yes, also covered in Alternatives. Like with generics, it should be backwards compatible to allow, and the main reason it is explicitly not supported is to make the implementation easier.
This seems unrelated to the changes this RFC is proposing?
(Incidentally we can probably do this in libraries if we had some way to specify the desired alignment of a struct, via something similar to " |
Right, just wanted to cast my vote in.
I changed my mind! As I went through the use cases, my thought process drifted from "packed is nice for mapping data" to "packed is almost completely useless what we actually need is ergonomic safe abstractions instead". So yes, in favour of this RFC - though I worry people will ignore the warning and take unsafe references without understanding the concequences. It's rather subtle and dangerous, though I'm not a fan of denying the references outright either.
Just another pain point of dealing with packed types. Tangential, yes, but I'm very concerned about the ergonomics surrounding packed data! |
Also to expand slightly on the constexpr point, if that were working, it would be possible to write the generic |
Really great RFC work! Preparation, motivation, all of it! It's a good plan, but I'd like to do it without allowing references at all — it's an additional rule for unsafe blocks, and do we really need that? |
We debated that, but there are legitimate use-cases for creating a reference, and if we don't permit |
Would it then make sense to only disallow references to improperly aligned fields, but still allow those the compiler can statically determine are safe? In the context of packed, anything with an alignment of 1, such as u8 arrays, would still be safe to reference. I dislike removing functionality completely, but an unaligned reference is pretty much always the wrong thing to do. If anything, you want a pointer, not a reference. Which could still be done:
|
Agree, I think creating an unaligned reference breaks against Behavior considered undefined which I think unsafe code blocks shouldn't violate either. |
On Fri, Aug 07, 2015 at 06:54:24AM -0700, arcnmx wrote:
This would be a more specific rule, yes. It is harder to enforce
This is an interesting pattern, but it seems unfortunate to be forced |
A limited implementation would be to whitelist the types that are guaranteed to be 1-aligned on all architectures, which I believe is anything of size 1 or 0? Which again, may not be guaranteed for many types, but at least u8, (), [_; 0]... bool? Not sure. Could probably be done as an OIBIT marker trait. We can also use OIBIT markers with EDIT: I like the OIBIT approach because it makes it possible to use packed structs within other packed structs properly. Any reason why this wouldn't work?
Unfortunate, sure, but there are very few legitimate reasons for doing this if 1-aligned type references are safe and allowed. It was more an example of how to do it if you really needed to, without manual pointer offset counting.
Hm, even with |
Well, I don't know that we've ever thought about it that hard before. But I wouldn't be surprised if there were paths where the compiler would say "ah, that's a |
|
Well, that is the question, isn't it? I don't think this is entirely clear. That said, it's not clear how we would be able to really optimize by doing such changes, and it's certainly counterintuitive. |
To address some comments so far: Being able to determine which fields are definitely aligned is difficult to do currently. However, even if it was possible, I don't think it should be done. A simple rule like "Cannot take references to fields of packed structures in safe code" will be easier to explain and maintain compared to "Cannot take references to fields of packed structures, unless we're sure they're aligned properly". The idea that taking a reference to one field is safe, but another is not seems strange. i'd much rather that the behaviour was consistent across all the fields of the struct. |
I think that an intrinsic for doing unaligned loads would be useful given the issue this is solving. The linked issue is related to SIMD where (on x86) there are separate instructions for aligned and unaligned loads and the alignment information we pass to LLVM causes it use the former based on the assumption that the location is aligned. Some platforms don't allow unaligned loads at all, so being able to still load from an unaligned (for example) |
The usual way I know to write an unaligned load is to use memcpy / |
Hm, I suppose it could be seen as a bit of an inconsistent rule. Is it that difficult to implement using OIBIT? My view as someone trying to use it would basically be "this is perfectly safe why is the compiler getting in my way here?!" In any case I'm more and more convinced that using |
I'm okay with making references into packed structs be unsafe to create, as well as the restrictions on no |
Hear ye, hear ye. This RFC is entering final comment period. |
Would this be forwards compatible with later making it safe again by means of first copying the value out onto the stack (and then back afterwards for |
I feel like this is the easiest approach to take for now, but it certainly merits some further discussion in a followup RFC on how to handle it properly. A
This seems strange to me, especially if the lifetime of the value outlives the current scope. I might be misinterpreting though: copying to stack then referencing will definitely be safe with RFC, and safe+compatible with whatever comes later. |
@glaebhoerl I'm not sure. It's certainly unlikely to be backwards incompatible due to causing some |
(The |
generic type) in a `repr(packed)` type, since the destructor of `T` is | ||
passed a reference to that `T`. The crater run (see appendix) found no | ||
crate that needs to use `repr(packed)` to store a `Drop` type (or a | ||
generic type). The generic type rule is conservatively approximated by |
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.
Just want to point out that we already have generic repr(packed)
structs in rustc.
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.
Oh the irony.
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.
Haha, rustc isn't covered by crater.
It looks like that case could be handled by a trait that uses a monomorphic #[repr(packed)]
internally to do an unaligned load, since it is just used to load a small set of types ({u,i}{8,16,32,64,size}
?).
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.
In this case - yes (though it'd be still annoying with 10 permutations).
But imagine you wanted to write a generic (de)serializer function... If we go with "no generics" rule, IMHO we should consider adding unaligned_load
/store
intrinsics.
Yeah, that's the kind of thing I was concerned about as well.
I'm not certain. I think we'd pretty likely want it for |
Is the idea that the syntax for taking a reference into a packed struct would be the same, it would just be required to be within an unsafe block or function? I'm concerned that if the syntax is the same as the common, safe operation of taking a reference, it would be easy to take a reference to a member of a packed struct without realizing it, because one is already writing an unsafe function, or inside an unsafe block for other reasons. It would also be harder to catch during code reviews, as it would be easy to look over. Knowing it was unsafe would require realizing the struct definition (which may be in a different part of the code) specified I agree that obtaining a reference to a member of a packed struct should be unsafe, but I also think doing so should have a different syntax from the normal, safe operation to make it more apparent that special care is required. Additionally, I would expect such an operation to return a raw pointer. |
Has anyone looked at function arguments which aren't references, but might be optimized to a pointer? Maybe |
In those cases the correct thing to do would be to copy it out onto the stack to get an aligned value and then pass a pointer. Whether we actually do that, I don't know. |
@retep998 Agreed, that sounds reasonable given how _un_reasonable my example code is. If there's ever a real (performance) problem from that, you'd just have to find it by profiling. I guess the answer for correctness is to add a second test, for that case. (Just checking the alignment, no need to try forcing a crash with simd). |
I find this appealing. I wonder what the syntax would be, though? |
@nikomatsakis Another usecase for getting a raw pointer could be for uninitialized memory (potentially going through some sort of safe uninitialized reference?). |
I like the idea of having alternate syntax which gets a raw pointer directly and can be used when getting a safe pointer wouldn't be safe to do. |
Also in favour of an alternative that retrieves a pointer (and doesn't require We could introduce an |
And if we do introduce new syntax we'd need to make sure that while it is unstable, the old method of just taking a reference would still work in stable Rust, albeit with a warning. |
Wasn't the point of this RFC to be an intentional breaking change and remove packed references in stable though, because they're completely wrong and unsafe? |
Oh yeah, and on second thought, I don't need to be able to get pointers to those fields regardless, I just need to be able to have those packed structs, and be able to access their fields at all. So as long as that stays in, I don't mind. |
Mm, the uses for |
So, I'm slow I guess, but for some reason I thought that this patch actually detected cases that took the address of a packed structure. But re-reading this, I realize that this was just a way to detect where |
@nikomatsakis I was, but I've moved on to using I can see it being somewhat useful (mostly for referencing other inner packed members, or calling methods by |
This seems too limiting since for instance on x86 unaligned accesses work fine on scalars and there are instructions for unaligned SIMD access (and of course it's always possible to emulate them since atomicity doesn't matter because & has no mutable aliasing) A better solution would seem to be adding an "unaligned" modifier to pointers, so that taking such an unaligned reference would produce an "&unaligned T". Obviously this would not be compatible with normal references, so for instance methods that take "&self" and "&mut self" would not be callable; on the other hand, it should be possible to implicitly convert "&T" to "&unaligned T" and explicitly converting &unaligned T to something like Either<&T, &unaligned T> with a runtime check. Also this way there is no need for unsafe code. |
@bill-myers introducing On Thu, Sep 10, 2015 at 8:37 AM, bill-myers [email protected]
|
Huzzah! The language design subteam has decided to accept this RFC. Regarding the proposal to remove such references altogether, or introduce a new operator, the general feeling was that (as discussed earlier on the thread) references have value, but an entirely new operator did not feel warranted. |
I just noticed a hole in this RFC, it doesn't cover by-ref bindings in patterns. So technically |
Indeed. I consider that to be implied. It's worth noting that in the MIR, On Fri, Oct 2, 2015 at 5:23 PM, James Miller [email protected]
|
@nikomatsakis still, it seems like it should probably be mentioned in the RFC text. At least for the sake of being thorough. |
agreed On Sun, Oct 4, 2015 at 10:22 PM, James Miller [email protected]
|
This is a message from the future: does anyone know where I can find logs of the rationale for this decision ? Meeting minutes, IRC logs, etc ? I'm writing a post about what we can learn about what happened with Also if anyone that was around here back then wants to proof-read it, I would appreciate it. |
@gnzlbg You may already know of these, but there's https://github.com/rust-lang/meeting-minutes and https://github.com/rust-lang/subteams with historical info... however neither of them have much info about (I'd be happy to proof-read and/or answer questions about the history. Feel free email me at the email in my profile.) |
Rendered.
cc rust-lang/rust#27060