about summary refs log tree commit diff
path: root/src/liballoc/sync.rs
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-01-16 00:47:45 +0000
committerbors <bors@rust-lang.org>2020-01-16 00:47:45 +0000
commite02c475da5c7d942cac2e3cf9683ea4117d4fe46 (patch)
tree3d4c105ed7145390297374b92272aa1b63a90dee /src/liballoc/sync.rs
parent3291ae33907f2a866ea6cea89113200555038d06 (diff)
parenteb77f7ec6e9460c1ca70fbb7bb655f1a0a1bacfc (diff)
downloadrust-e02c475da5c7d942cac2e3cf9683ea4117d4fe46.tar.gz
rust-e02c475da5c7d942cac2e3cf9683ea4117d4fe46.zip
Auto merge of #67339 - CAD97:rc-provenance, r=sfackler
Use pointer offset instead of deref for A/Rc::into_raw

Internals thread: https://internals.rust-lang.org/t/rc-and-internal-mutability/11463/2?u=cad97

The current implementation of (`A`)`Rc::into_raw` uses the `Deref::deref` implementation to get the pointer-to-data that is returned. This is problematic in the proposed Stacked Borrow rules, as this only gets shared provenance over the data location. (Note that the strong/weak counts are `UnsafeCell` (`Cell`/`Atomic`) so shared provenance can still mutate them, but the data itself is not.) When promoted back to a real reference counted pointer, the restored pointer can be used for mutation through `::get_mut` (if it is the only surviving reference). However, this mutates through a pointer ultimately derived from a `&T` borrow, violating the Stacked Borrow rules.

There are three known potential solutions to this issue:

- Stacked Borrows is wrong, liballoc is correct.
- Fully admit (`A`)`Rc` as an "internal mutability" type and store the data payload in an `UnsafeCell` like the strong/weak counts are. (Note: this is not needed generally since the `RcBox`/`ArcInner` is stored behind a shared `NonNull` which maintains shared write provenance as a raw pointer.)
- Adjust `into_raw` to do direct manipulation of the pointer (like `from_raw`) so that it maintains write provenance and doesn't derive the pointer from a reference.

This PR implements the third option, as recommended by @RalfJung.

Potential future work: provide `as_raw` and `clone_raw` associated functions to allow the [`&T` -> (`A`)`Rc<T>` pattern](https://internals.rust-lang.org/t/rc-and-internal-mutability/11463/2?u=cad97) to be used soundly without creating (`A`)`Rc` from references.
Diffstat (limited to 'src/liballoc/sync.rs')
-rw-r--r--src/liballoc/sync.rs14
1 files changed, 12 insertions, 2 deletions
diff --git a/src/liballoc/sync.rs b/src/liballoc/sync.rs
index dc53ad28407..4aa0190b149 100644
--- a/src/liballoc/sync.rs
+++ b/src/liballoc/sync.rs
@@ -545,9 +545,19 @@ impl<T: ?Sized> Arc<T> {
     /// ```
     #[stable(feature = "rc_raw", since = "1.17.0")]
     pub fn into_raw(this: Self) -> *const T {
-        let ptr: *const T = &*this;
+        let ptr: *mut ArcInner<T> = NonNull::as_ptr(this.ptr);
+        let fake_ptr = ptr as *mut T;
         mem::forget(this);
-        ptr
+
+        // SAFETY: This cannot go through Deref::deref.
+        // Instead, we manually offset the pointer rather than manifesting a reference.
+        // This is so that the returned pointer retains the same provenance as our pointer.
+        // This is required so that e.g. `get_mut` can write through the pointer
+        // after the Arc is recovered through `from_raw`.
+        unsafe {
+            let offset = data_offset(&(*ptr).data);
+            set_data_ptr(fake_ptr, (ptr as *mut u8).offset(offset))
+        }
     }
 
     /// Constructs an `Arc` from a raw pointer.