-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
New lint: transmute(Vec) is undefined behavior #4484
Comments
Yes, the potential issue is that Vec::from_raw_parts(v_orig.as_mut_ptr() as *mut Option<&i32>,
v_orig.len(),
v_orig.capacity()) asserts / requires that However, the original pointer it comes from, the one from The solution is to " let store = [0, 1, 2, 3];
let v_orig = store.iter().collect::<Vec<&i32>>();
// Using from_raw_parts
let v_from_raw = unsafe {
// prepare for an auto-forget of the initial vec:
let v_orig: &mut Vec<_> = &mut *ManuallyDrop::new(v_orig);
Vec::from_raw_parts(v_orig.as_mut_ptr() as *mut Option<&i32>,
v_orig.len(),
v_orig.capacity())
// v_orig is never used again, so no aliasing issue
}; |
That's a lot of code. Perhaps it might be a good idea to encapsulate it in a |
There is currently no trait for this in core or std, so crates have to make up their own. |
@llogiq sounds like a good idea to me. Care to get the ball rolling with an issue against stdlib and/or an RFC? |
This actually could potentially be a lot more broad and useful, though I don't know how much of this info that clippy really has access to when doing an analysis:
Related:
|
@Shnatsel Sure thing. I'll write up that RFC. |
As a follow-up to @Lokathor: currently, a lint would be always correct to trigger when both type arguments to A lint specifically for |
I think that's basically rust-lang/rust#50842.
@llogiq has since corrected that in the docs. |
#4592 should have closed this. |
This comment has been minimized.
This comment has been minimized.
Correct. Because If needed, you could convert a slice of Outer to slice of Inner via core::slice::from_raw_parts and pointer casting and all that, but not with Vec. |
While I understand why For example, the most common usecase for transmute for me is when a newtype wraps another one with #[repr(transparent)]
struct NewType<T>(T); Isn't in this case transmuting |
No, because the implementation of Pretend that Said another way:
See also #4484 (comment) |
Nope. Because Vec itself is repr(Rust), all transmutation between different monomorphizations is UB. Now, sure, the data layout is likely to be the same and so it is likely to work despite being UB, but that's how a lot of UB is until one day it breaks. |
sniped :( |
@shepmaster Hmm. Is this something specific to Generally I'd expect If that's not the case, that significantly reduces usefulness of |
UPD: Oh I see, it's worse than I thought - even structs without newtypes are not guaranteed to be deterministic :/ https://rust-lang.github.io/unsafe-code-guidelines/layout/structs-and-tuples.html#unresolved-question-guaranteeing-compatible-layouts That's pretty bad... I don't want to switch all my types to |
These optimizations are possible only because the layout is not guaranteed. You have the choice between optimized layout and predictable layout. Having both at the same time is just impossible. |
Sure, but there is (usually) a difference between "layout is not guaranteed" as in "it's unspecified, so don't rely on exact representation" and "layout is not deterministic" as in "even structurally equivalent types are not guaranteed to have compatible layout". Most of these optimisations require only the former, and as a user I'm perfectly fine with not relying on exact memory representation, but I'd still like to rely on compatibility between types, as that's pretty much the only way to write efficient code in many cases. |
It could potentially be deterministic without being fixed for all time. However, there's a lot of work to do there, so it's UB until that happens, if that happens at all. |
People are experimenting with safe transmutation methods as we speak. In general, I believe a type should be able to state what types it can be transmuted into and how (though to do this ergonomically might require us to extend the type system, e.g. to have |
Indeed! Beyond the safe-transmute project there's also various crates that are feeding ideas into the project that are already all capable of being used. |
Documentation on std::mem::transmute() specifically calls out that transmuting a Vec of any type is UB.
Code with UB from documentation:
Correct code from documentation (assuming interior types can be transmuted):
What exactly is wrong with it is not currently documented (I've opened rust-lang/rust#64073 to clarify this) but my understanding is that the problem stems from the following:
I.e. while it's safe to transmute the data on the heap assuming the types are compatible, it's never safe to transmute the (pointer, capacity, length) triplet on the stack.
I'm not 100% convinced that the "correct" code proposed in documentation correctly upholds aliasing invariants. It's possible that
std::mem::forget()
must be called before the new Vec is constructed, or that it's only safe to do withManuallyDrop
.cc @RalfJung
The text was updated successfully, but these errors were encountered: