-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Thread locals do not guard against recursive initialization. #30228
Comments
triage: I-nominated Seems bad! Haven't had too much of a chance to digest yet, but doesn't seem earth-shatteringly bad on the surface at least. |
I... actually don't think this is related to thread locals. The line you've highlighted I believe is indeed the "one at fault", but this seems like it may be a codegen problem? Currently, for code like this: pub fn foo(a: &mut Box<i32>, b: Box<i32>) {
*a = b;
} This is codegen'd as:
Note that this is precisely what's happening on that line of This seems like an interesting phenomenon, however, because if the destructor in the function above panicked, that means that we've possibly got a hole in memory! As a proof of concept, let's take a look at this: struct A(Box<i32>);
struct B<'a>(&'a mut A);
impl Drop for A {
fn drop(&mut self) {
if *self.0 == 0 {
panic!("oh no");
} else {
// ...
}
}
}
impl<'a> Drop for B<'a> {
fn drop(&mut self) {
let a = &self.0;
println!("{}", a.0);
}
}
fn foo(b: &mut B) {
*b.0 = A(Box::new(1));
}
fn main() {
let mut bomb = A(Box::new(0));
let mut b = B(&mut bomb);
foo(&mut b);
} Here we create a bomb that's "ready to go off" via When running this program, I get:
(of course results may vary). tl;dr; I think this is a codegen bug, not a TLS bug. Recategorizing as T-compiler, but we should probably still fix |
Ah, cc @rust-lang/compiler, tagging this with |
That sounds like there are two problems here:
I agree that the more general problem is that assignment drops need special handling, but doing it, via swaps for example, in every case seems like overkill and can potentially ruin performance. It might be worth splitting this into two issues (or 3, one of them tracking the various instances of the general case). As for the panic case, it might be worth implementing try {
drop_in_place(a);
} finally {
ptr::write(a, b);
} |
The panicking destructor problem is present in MIR. |
#[rustc_mir(graphviz="file.gv")]
pub fn foo<T>(a: &mut T, b: T) {
*a = b;
} generates the MIR at https://gist.github.com/arielb1/952593c64e4fffdbf9ed#file-mir-svg |
Interesting =) It seems like the way we should codegen |
We can have the landing pad write the rvalue into the destination. That would however require a |
That would basically be the MIR
|
Not sure how relevant it is, but this is similar to what Swift does (according to this presentation ~38 minute onward) |
Due to rust-lang#30228 it's not currently sound to do `*ptr = Some(value)`, so instead use `mem::replace` which fixes the soundness hole for now.
On Tue, Dec 08, 2015 at 08:05:03AM -0800, arielb1 wrote:
Clever. Worth measuring. Obviously this is somewhat observable (But |
Due to #30228 it's not currently sound to do `*ptr = Some(value)`, so instead use `mem::replace` which fixes the soundness hole for now.
That kind of wacky |
Hmm, perhaps it is. I'm trying to come up with an example of something reasonable but haven't been able to yet. |
The point here is that the destructor is run "in the middle of the assignment", and can try to observe the value in-mid-assignment. If the assignment was to a dereference of a However, you can also assign to a dereference of a |
I think I will just close this issue and reopen it under the appropriate title because the issues are separate enough. |
Hmm I think that if we fix #30380 we will still want an independent regression test for this issue. Reopening (with expectation that it will be flagged as needstest after other other bug in depends sues fixed) |
@alexcrichton's PR definitely did come with a test. The TLS problem was aliasing violation (UB) triggered by accessing a value in the middle of its destructor. |
In current stable (1.4), beta, and nightly the above snippet produces aliased references instead of triggering library asserts.
Currently, libstd assumes there is no value already in the slot when assigning the one produced by the latest initializer call.
It should either drop one of the two values (perhaps after putting the TLS slot in "destructor being called" mode) or panic, if a multiple initialization condition is detected.
The text was updated successfully, but these errors were encountered: