Skip to content
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

Direct Rc/Weak cycle causes double free #12046

Closed
huonw opened this issue Feb 5, 2014 · 5 comments
Closed

Direct Rc/Weak cycle causes double free #12046

huonw opened this issue Feb 5, 2014 · 5 comments
Labels
I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.

Comments

@huonw
Copy link
Member

huonw commented Feb 5, 2014

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Cycle {
    x: RefCell<Option<Weak<Cycle>>>
}

fn main() {
    let a = Rc::new(Cycle { x: RefCell::new(None) });
    let b = a.clone().downgrade();
    *a.borrow().x.borrow_mut().get() = Some(b);
}

crashes with a double free when run, with and without optimisations.

I'm investigating.

@Skrylar
Copy link

Skrylar commented Feb 5, 2014

I just ran in to this problem while writing some (slightly more advanced code) https://gist.github.com/Skrylar/8779719

Currently breaks many designs of GUIs.

@alexcrichton
Copy link
Member

I believe that the sequence of events looks like this:

  1. The code above executes.
  2. The destructor for Rc<Cycle> begins to run
  3. The recount is decremented to 0, so the destructor of Cycle is run (note that the weak count hasn't been checked yet).
  4. The destructor of Cycle runs, eventually running the destructor of Weak<Cycle>
  5. The weak count is decremented to 0, so the Rc box allocation is freed
  6. The destructor for Rc<Cycle> continues, sees the weak count is 0, and frees the inner box again.

I think that when the destructor for Rc is running we may want to do something like setting the box to immortal? I'm not entirely sure if that would work out.

@huonw
Copy link
Member Author

huonw commented Feb 5, 2014

Looks like you got it in one:

Rc: dropping 0x7fd4dc000de0
Rc: strong = 2, weak = 1
Weak: dropping 0x0
Rc: dropping 0x7fd4dc000de0
Rc: strong = 1, weak = 1
Rc: destroying
Weak: dropping 0x7fd4dc000de0
Weak: strong = 0, weak = 1
Weak: freeing
Rc: freeing

is the RUST_LOG=std::rc output of

diff --git a/src/libstd/rc.rs b/src/libstd/rc.rs
index 7d0ddb2..79d6d68 100644
--- a/src/libstd/rc.rs
+++ b/src/libstd/rc.rs
@@ -78,8 +78,12 @@ impl<T> Drop for Rc<T> {
     fn drop(&mut self) {
+        info!("Rc: dropping {}", self.ptr);
         unsafe {
             if self.ptr != 0 as *mut RcBox<T> {
+                info!("Rc: strong = {}, weak = {}", (*self.ptr).strong, (*self.ptr).weak);
                 (*self.ptr).strong -= 1;
                 if (*self.ptr).strong == 0 {
+                    info!("Rc: destroying");
                     read_ptr(self.borrow()); // destroy the contained object
                     if (*self.ptr).weak == 0 {
+                        info!("Rc: freeing");
                         exchange_free(self.ptr as *u8)
@@ -155,6 +159,9 @@ impl<T> Drop for Weak<T> {
     fn drop(&mut self) {
+        info!("Weak: dropping {}", self.ptr);
         unsafe {
             if self.ptr != 0 as *mut RcBox<T> {
+                info!("Weak: strong = {}, weak = {}", (*self.ptr).strong, (*self.ptr).weak);
                 (*self.ptr).weak -= 1;
                 if (*self.ptr).weak == 0 && (*self.ptr).strong == 0 {
+                    info!("Weak: freeing");
                     exchange_free(self.ptr as *u8)

@huonw
Copy link
Member Author

huonw commented Feb 5, 2014

I think we can just reorder the decrements.

@alexcrichton
Copy link
Member

One side effect of re-ordering the decrements is that a weak pointer could be upgraded while the last reference to the box is being destroyed, which I think may cause problems because you'd have a strong reference to a thing that has had Drop run on it (I think?)

bors added a commit that referenced this issue Feb 6, 2014
A weak pointer inside itself will have its destructor run when the last
strong pointer to that data disappears, so we need to make sure that the
Weak and Rc destructors don't duplicate work (i.e. freeing).

By making the Rcs effectively take a weak pointer, we ensure that no
Weak destructor will free the pointer while still ensuring that Weak
pointers can't be upgraded to strong ones as the destructors run.

This approach of starting weak at 1 is what libstdc++ does.

Fixes #12046.
@huonw huonw closed this as completed in da45340 Feb 6, 2014
alexcrichton added a commit to alexcrichton/stdarch that referenced this issue Mar 5, 2024
This commit is a replacement for rust-lang#1417 now that rust-lang/rust#12046 has
landed. While I was here I went ahead and updated the Wasmtime used in
CI and adapted its command line as well.
Amanieu pushed a commit to rust-lang/stdarch that referenced this issue Mar 5, 2024
This commit is a replacement for #1417 now that rust-lang/rust#12046 has
landed. While I was here I went ahead and updated the Wasmtime used in
CI and adapted its command line as well.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants