Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upThread locals do not guard against recursive initialization. #30228
Comments
eddyb
added
A-libs
labels
Dec 6, 2015
eddyb
referenced this issue
Dec 6, 2015
Closed
Thread locals do not guard against recursive initialization. #1395
This comment has been minimized.
This comment has been minimized.
|
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. |
alexcrichton
added
T-libs
I-nominated
labels
Dec 7, 2015
This comment has been minimized.
This comment has been minimized.
|
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 |
alexcrichton
added
T-compiler
I-unsound 💥
and removed
A-libs
T-libs
labels
Dec 8, 2015
This comment has been minimized.
This comment has been minimized.
|
Ah, cc @rust-lang/compiler, tagging this with |
alexcrichton
removed
the
I-wrong
label
Dec 8, 2015
This comment has been minimized.
This comment has been minimized.
|
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);
} |
alexcrichton
added a commit
to alexcrichton/rust
that referenced
this issue
Dec 8, 2015
alexcrichton
referenced this issue
Dec 8, 2015
Merged
std: Use mem::replace in TLS initialization #30267
This comment has been minimized.
This comment has been minimized.
|
The panicking destructor problem is present in MIR. |
This comment has been minimized.
This comment has been minimized.
#[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 |
This comment has been minimized.
This comment has been minimized.
|
Interesting =) It seems like the way we should codegen |
This comment has been minimized.
This comment has been minimized.
|
We can have the landing pad write the rvalue into the destination. That would however require a |
This comment has been minimized.
This comment has been minimized.
|
That would basically be the MIR
|
This comment has been minimized.
This comment has been minimized.
Not sure how relevant it is, but this is similar to what Swift does (according to this presentation ~38 minute onward) |
alexcrichton
added a commit
to alexcrichton/rust
that referenced
this issue
Dec 8, 2015
This comment has been minimized.
This comment has been minimized.
|
On Tue, Dec 08, 2015 at 08:05:03AM -0800, arielb1 wrote:
Clever. Worth measuring. Obviously this is somewhat observable (But |
bors
added a commit
that referenced
this issue
Dec 10, 2015
This comment has been minimized.
This comment has been minimized.
|
That kind of wacky |
This comment has been minimized.
This comment has been minimized.
Hmm, perhaps it is. I'm trying to come up with an example of something reasonable but haven't been able to yet. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
I think I will just close this issue and reopen it under the appropriate title because the issues are separate enough. |
arielb1
closed this
Dec 14, 2015
arielb1
referenced this issue
Dec 14, 2015
Closed
Assignments leave their place partially-destroyed if the destructor panics #30380
This comment has been minimized.
This comment has been minimized.
|
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) |
pnkfelix
reopened this
Dec 15, 2015
This comment has been minimized.
This comment has been minimized.
|
@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. |
eddyb commentedDec 6, 2015
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.