about summary refs log tree commit diff
path: root/library/core
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-12-22 21:59:24 +0100
committerGitHub <noreply@github.com>2024-12-22 21:59:24 +0100
commit6ade237b6c624b5b19214b321395ca43e60a18ad (patch)
tree36e26188c457c6cca58b262f6955b5ced5615065 /library/core
parentbd160f11f00823c5a3a9ee615907e238e44f2e81 (diff)
parent2305012e6a819805fdcf9207d865fa4dbda58f81 (diff)
downloadrust-6ade237b6c624b5b19214b321395ca43e60a18ad.tar.gz
rust-6ade237b6c624b5b19214b321395ca43e60a18ad.zip
Rollup merge of #134583 - Enselic:maybe-uninit-transmute, r=workingjubilee
docs: `transmute<&mut T, &mut MaybeUninit<T>>` is unsound when exposed to safe code

Closes #66699

On my system (Edit: And also in the [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=90529e2a9900599cb759e4bfaa5b5efe)) the example program terminates with an unpredictable exit code:
```console
$ cargo +nightly build && target/debug/bin ; echo $?
255
$ cargo +nightly build && target/debug/bin ; echo $?
253
```

And miri considers the code to have undefined behavior:
```console
$ cargo +nightly miri run
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
  --> src/main.rs:12:24
   |
12 |     std::process::exit(*code); // UB! Accessing uninitialized memory
   |                        ^^^^^ using uninitialized data, but this operation requires initialized memory
   |
error: aborting due to 1 previous error
```
Diffstat (limited to 'library/core')
-rw-r--r--library/core/src/mem/maybe_uninit.rs20
1 files changed, 20 insertions, 0 deletions
diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs
index 9b3d6902098..58fb5be5812 100644
--- a/library/core/src/mem/maybe_uninit.rs
+++ b/library/core/src/mem/maybe_uninit.rs
@@ -232,6 +232,26 @@ use crate::{fmt, intrinsics, ptr, slice};
 /// remain `#[repr(transparent)]`. That said, `MaybeUninit<T>` will *always* guarantee that it has
 /// the same size, alignment, and ABI as `T`; it's just that the way `MaybeUninit` implements that
 /// guarantee may evolve.
+///
+/// Note that even though `T` and `MaybeUninit<T>` are ABI compatible it is still unsound to
+/// transmute `&mut T` to `&mut MaybeUninit<T>` and expose that to safe code because it would allow
+/// safe code to access uninitialized memory:
+///
+/// ```rust,no_run
+/// use core::mem::MaybeUninit;
+///
+/// fn unsound_transmute<T>(val: &mut T) -> &mut MaybeUninit<T> {
+///     unsafe { core::mem::transmute(val) }
+/// }
+///
+/// fn main() {
+///     let mut code = 0;
+///     let code = &mut code;
+///     let code2 = unsound_transmute(code);
+///     *code2 = MaybeUninit::uninit();
+///     std::process::exit(*code); // UB! Accessing uninitialized memory.
+/// }
+/// ```
 #[stable(feature = "maybe_uninit", since = "1.36.0")]
 // Lang item so we can wrap other types in it. This is useful for coroutines.
 #[lang = "maybe_uninit"]