about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAurelia Molzer <5550310+HeroicKatora@users.noreply.github.com>2025-08-09 17:54:43 +0200
committerAurelia Molzer <5550310+197g@users.noreply.github.com>2025-08-09 18:12:56 +0200
commit21bdfa14cbd99aba173751a2c723fb1e65de401b (patch)
tree7ffcb6487fe32dc127660be1cc34f6a240e12717
parentca77504943887037504c7fc0b9bf06dab3910373 (diff)
downloadrust-21bdfa14cbd99aba173751a2c723fb1e65de401b.tar.gz
rust-21bdfa14cbd99aba173751a2c723fb1e65de401b.zip
Ensure consistent drop for hint::select_unpredictable
There are a few alternatives to the implementation. The principal
problem is that the selected value must be owned (in the sense of having
any drop flag of sorts) when the unselected value is dropped, such that
panic unwind goes through the drop of both. This ownership must then be
passed on in return when the drop went smoothly. The basic way of
achieving this is by extracting the selected value first, at the cost of
relying on the optimizer a little more for detecting the copy as
constructing the return value despite having a place in the body.
-rw-r--r--library/core/src/hint.rs6
-rw-r--r--library/coretests/tests/hint.rs37
2 files changed, 42 insertions, 1 deletions
diff --git a/library/core/src/hint.rs b/library/core/src/hint.rs
index c72eeb9a9c9..53c17d6c887 100644
--- a/library/core/src/hint.rs
+++ b/library/core/src/hint.rs
@@ -790,8 +790,12 @@ pub fn select_unpredictable<T>(condition: bool, true_val: T, false_val: T) -> T
     // is returned. This is necessary because the intrinsic doesn't drop the
     // value that is  not selected.
     unsafe {
+        // Extract the selected value first, ensure it is dropped as well if dropping the unselected
+        // value panics.
+        let ret = crate::intrinsics::select_unpredictable(condition, &true_val, &false_val)
+            .assume_init_read();
         crate::intrinsics::select_unpredictable(!condition, &mut true_val, &mut false_val)
             .assume_init_drop();
-        crate::intrinsics::select_unpredictable(condition, true_val, false_val).assume_init()
+        ret
     }
 }
diff --git a/library/coretests/tests/hint.rs b/library/coretests/tests/hint.rs
index 032bbc1dcc8..9ef5567681e 100644
--- a/library/coretests/tests/hint.rs
+++ b/library/coretests/tests/hint.rs
@@ -21,3 +21,40 @@ fn select_unpredictable_drop() {
     assert!(a_dropped.get());
     assert!(b_dropped.get());
 }
+
+#[test]
+#[should_panic]
+fn select_unpredictable_drop_on_panic() {
+    use core::cell::Cell;
+
+    struct X<'a> {
+        cell: &'a Cell<u16>,
+        expect: u16,
+        write: u16,
+    }
+
+    impl Drop for X<'_> {
+        fn drop(&mut self) {
+            let value = self.cell.get();
+            self.cell.set(self.write);
+            assert_eq!(value, self.expect);
+        }
+    }
+
+    let cell = Cell::new(0);
+
+    // Trigger a double-panic if the selected cell was not dropped during panic.
+    let _armed = X { cell: &cell, expect: 0xdead, write: 0 };
+    let selected = X { cell: &cell, write: 0xdead, expect: 1 };
+    let unselected = X { cell: &cell, write: 1, expect: 0xff };
+
+    // The correct drop order is:
+    //
+    // 1. `unselected` drops, writes 1, and panics as 0 != 0xff
+    // 2. `selected` drops during unwind, writes 0xdead and does not panic as 1 == 1
+    // 3. `armed` drops during unwind, writes 0 and does not panic as 0xdead == 0xdead
+    //
+    // If `selected` is not dropped, `armed` panics as 1 != 0xdead
+    let _unreachable =
+        core::hint::select_unpredictable(core::hint::black_box(true), selected, unselected);
+}