Skip to content

#[thread_local] static mut is allowed to have 'static lifetime #54366

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
nikomatsakis opened this issue Sep 19, 2018 · 16 comments
Open

#[thread_local] static mut is allowed to have 'static lifetime #54366

nikomatsakis opened this issue Sep 19, 2018 · 16 comments
Labels
A-NLL Area: Non-lexical lifetimes (NLL) F-thread_local `#![feature(thread_local)]` NLL-sound Working towards the "invalid code does not compile" goal P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Sep 19, 2018

Spinning off from #51269:

The MIR borrow checker (i.e., NLL) permits #[thread_local] static mut to have 'static lifetime. This is probably a bug. It arises because we ignore borrows of "unsafe places", which includes static mut.

We probably ought to stop doing that — at least not ignoring them entirely — but if we do so, we have to ensure that we continue to accept overlapping borrows of static mut (even though that is basically guaranteed UB), since it compiles today:

fn main() {
 static mut X: usize = 22;
 unsafe {
  let p = &mut X;
  let q = &mut X;
  *p += 1;
  *q += 1;
  *p += 1;
 }
}
@nikomatsakis nikomatsakis added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-NLL Area: Non-lexical lifetimes (NLL) NLL-deferred labels Sep 19, 2018
kennytm added a commit to kennytm/rust that referenced this issue Sep 20, 2018
…borrow-test, r=pnkfelix

Add regression test for thread local static mut borrows

FIXME(rust-lang#54366) - We probably shouldn't allow `#[thread_local] static mut` to get a `'static` lifetime, but for now, we should at least test the behavior that `rustc` currently has.
@pnkfelix
Copy link
Member

Re-triaging for #56754. Based on #29594 (comment) I am tagging this as P-medium.

@pnkfelix pnkfelix added P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. NLL-deferred labels Dec 21, 2018
@nikomatsakis nikomatsakis added the NLL-sound Working towards the "invalid code does not compile" goal label Jan 23, 2019
@matthewjasper matthewjasper added F-thread_local `#![feature(thread_local)]` requires-nightly This issue requires a nightly compiler in some way. labels Aug 11, 2019
@oli-obk
Copy link
Contributor

oli-obk commented Dec 26, 2019

Fixing this issue is easy now, but it will be super hard to do with a future incompat lint, because the real change is happening elsewhere. The fix is to change https://github.com/rust-lang/rust/blob/a916ac22b9f7f1f0f7aba0a41a789b3ecd765018/src/librustc/ty/util.rs#L666.L667 to

if self.is_mutable_static(def_id) {
    self.mk_mut_ref(self.lifetimes.re_erased, static_ty)

Because then borrowck will pick it up correctly and detect the thread local annotation and not give it the static lifetime

@bstrie
Copy link
Contributor

bstrie commented Feb 3, 2020

@oli-obk, is a future incompat lint necessary if this feature is still unstable? It doesn't sound like that change would affect stable users.

@nikomatsakis
Copy link
Contributor Author

I agree a lint is probably not needed. I'm not sure whether I think @oli-obk's suggested patch is correct (like, I literally haven't looked into it -- it probably is).

@oli-obk
Copy link
Contributor

oli-obk commented Apr 16, 2020

While attempting to fix #70685 (see #71192 for the changes) I got all of our tests to pass, except for https://github.com/rust-lang/rust/blob/master/src/test/ui/nll/borrowck-thread-local-static-mut-borrow-outlives-fn.rs which references this issue. I have reinstated the current behaviour, but the site of the required change that I suggested above has changed. It's now in the Rvalue::ty method.

@RalfJung
Copy link
Member

Curiously, doing the same thing with a non-mut static is detected by the borrow checker.

@RalfJung
Copy link
Member

@oli-obk

The fix is to change https://github.com/rust-lang/rust/blob/a916ac22b9f7f1f0f7aba0a41a789b3ecd765018/src/librustc/ty/util.rs#L666.L667 to

Isn't that a problem with unsafety checking? You said elsewhere that this crucially is a raw pointer to ensure that accesses are unsafe.

@oli-obk
Copy link
Contributor

oli-obk commented Nov 20, 2020

We do unsafety checking properly nowadays:

// If the projection root is an artifical local that we introduced when
// desugaring `static`, give a more specific error message
// (avoid the general "raw pointer" clause below, that would only be confusing).
if let Some(box LocalInfo::StaticRef { def_id, .. }) = decl.local_info {
if self.tcx.is_mutable_static(def_id) {
self.require_unsafe(
UnsafetyViolationKind::General,
UnsafetyViolationDetails::UseOfMutableStatic,
);
return;

So the raw pointer checks aren't actually necessary, even though they are a good safety net for our use of LocalInfo for soundness.

@RalfJung
Copy link
Member

"properly" for some notion of "properly". ;) Relying on LocalInfo feels rather fragile.

@VorpalBlade
Copy link

Will this be solved in edition 2024 by the changes to static mut being done? Or will this still be an issue?

@arielb1
Copy link
Contributor

arielb1 commented Nov 19, 2024

What is the bug here? Why is borrowing a static mut for 'static "less sound" than anything else unsafe you can do with a static mut?

Is the problem that static mut tempts people to write functions taking &'static mut Self in an incorrect manner? In that case I believe the 2024 changes would solve it.

@RalfJung
Copy link
Member

The borrow checker clearly tries to account for the fact that thread_local statics don't have 'static lifetime... but then that doesn't apply to thread_local static mut. IMO that's clearly a bug. But it's not a soundness bug since creating such a reference is still unsafe.

@arielb1
Copy link
Contributor

arielb1 commented Nov 20, 2024

So the annoying thing is that the thread local rvalue needs to return *mut T to avoid taking a reference (which would invalidate all pre-existing shared pointers), but that does not remember the lifetime.

Or does

static mut X: (u32, u32) = (0, 0);

fn a() {
    &mut X.1;
}

translating to

_0: &mut (u32, u32) = ThreadLocalRef(X);
_1: &mut u32 = &mut (*_0).0

actually does not annoy MIRI too much

Therefore, there is a semantic reason for static muts being a dereference of a raw pointer, which means no lifetime checking.

Other way of describing this:

static mut X: Foo = ...;
X

is the same as

static X: RacyUnsafeCell<Foo> = ...;
X.get()

and since the latter allows getting 'static references, the former should too.

@RalfJung
Copy link
Member

Again, we already must have special code for immutable thread-local statics handling this. The exact same logic should "just" also be applied to mutable thread-local statics. Or is there some reason that does not work?

@arielb1
Copy link
Contributor

arielb1 commented Nov 20, 2024

@RalfJung

All kinds of statics and thread locals work by

_0: Pointer<T> = addr_of_static()
use(_0)

For non-mut statics, Pointer<T> is &T - for thread-locals it has a lifetime that is bounded to the function, for statics it has lifetime 'static.
For mut statics, Pointer<T> can't be &mut T since that would have aliasing problems. So it is *mut T, which doesn't come with a lifetime attached.

So you basically end up with the RacyUnsafeCell "desugaring".

@arielb1
Copy link
Contributor

arielb1 commented Nov 20, 2024

The thing I like about static mut being defined to act like the syntactic sugar for RacyUnsafeCell + a lint that prevents you from taking &mut in the most tempting-unsafe way, is that it makes it 1 less thing for the language to care about.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-NLL Area: Non-lexical lifetimes (NLL) F-thread_local `#![feature(thread_local)]` NLL-sound Working towards the "invalid code does not compile" goal P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants