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

Ensure an Rc isn't freed while running its own destructor. #12047

Closed
wants to merge 1 commit into from

Conversation

huonw
Copy link
Member

@huonw huonw commented Feb 5, 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
Copy link
Member Author

huonw commented Feb 5, 2014

cc @alexcrichton: I think this weak solution works, and is certainly nicer than using a sentinel value for the strong count.

@flaper87
Copy link
Contributor

flaper87 commented Feb 5, 2014

this looks like a nice, contained, fix for this issue.

@Skrylar
Copy link

Skrylar commented Feb 5, 2014

That unit test doesn't look like its guaranteed to fail in the event of a double free. Certain events can cause other memory to get pushed in to position (e.g. in my tests the crashes happened after removing a println), and Valgrind will not necessarily kill a program for double freeing (though it will annotate this fact sometimes.)

@huonw
Copy link
Member Author

huonw commented Feb 5, 2014

That test crashed reliably for me, but yeah, it is system dependent what happens on a double free.

Do you happen to have a better/more reliable test?

@@ -80,7 +80,27 @@ impl<T> Drop for Rc<T> {
if self.ptr != 0 as *mut RcBox<T> {
(*self.ptr).strong -= 1;
if (*self.ptr).strong == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a check against 1, with the decrement moved after the branch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be worried about moving the decrements because you want to be sure that during destruction you can't upgrade a weak pointer (you're destroying the contents at that time).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I guess that could be a problem. The libstdc++ implementation considers all of the strong pointers to have an initial weak count then then decreases it when the last strong pointer is freed.

@thestinger
Copy link
Contributor

The libstdc++ way is to set the initial weak count to 1 and consider it the weak count for all of the strong pointers. It then reduces the weak count after destroying the inner object.

This actually removes a branch from the Weak destructor which is nice.

@alexcrichton
Copy link
Member

Removing my r+, @thestinger has some more suggestions on how to fix this.

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 rust-lang#12046.
@Skrylar
Copy link

Skrylar commented Feb 5, 2014

@huonw A global counter for RC creations/frees which exists only under #cfg(test) would probably do it. Alternatively a low level memory allocator which is active at the test configuration which tracks for double-frees (that would be more widely helpful in general.)

While the latter would be more widely helpful, this is somewhat of a show-stopping bug for a lot of designs right now.

@thestinger
Copy link
Contributor

Building the tests with address sanitizer will catch a double-free.

bors added a commit that referenced this pull request 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.
@bors bors closed this Feb 6, 2014
flip1995 pushed a commit to flip1995/rust that referenced this pull request Jan 11, 2024
…s-with-brackets, r=Jarcho

New Lint: empty_enum_variants_with_brackets

This PR:
- adds a new early pass lint that checks for enum variants with no fields that were defined using brackets. **Category: Restriction**
- adds relevant UI tests for the new lint.

Closes rust-lang#12007

```
changelog: New lint: [`empty_enum_variants_with_brackets`]
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Direct Rc/Weak cycle causes double free
6 participants