Skip to content

Commit 94f00f4

Browse files
Fix memory leak in os impl
1 parent 4d32b9a commit 94f00f4

File tree

3 files changed

+85
-31
lines changed

3 files changed

+85
-31
lines changed

library/std/src/sys/thread_local/os.rs

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use super::key::{Key, LazyKey, get, set};
22
use super::{abort_on_dtor_unwind, guard};
3-
use crate::alloc::Layout;
3+
use crate::alloc::{self, Layout};
44
use crate::cell::Cell;
55
use crate::marker::PhantomData;
6-
use crate::ptr;
6+
use crate::mem::ManuallyDrop;
7+
use crate::ops::Deref;
8+
use crate::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
9+
use crate::ptr::{self, NonNull};
710

811
#[doc(hidden)]
912
#[allow_internal_unstable(thread_local_internals)]
@@ -110,6 +113,60 @@ pub const fn value_align<T: 'static>() -> usize {
110113
crate::mem::align_of::<Value<T>>()
111114
}
112115

116+
/// Equivalent to `Box<Value<T>>`, but potentially over-aligned.
117+
struct AlignedBox<T: 'static, const ALIGN: usize> {
118+
ptr: NonNull<Value<T>>,
119+
}
120+
121+
impl<T: 'static, const ALIGN: usize> AlignedBox<T, ALIGN> {
122+
#[inline]
123+
fn new(v: Value<T>) -> Self {
124+
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
125+
126+
let ptr: *mut Value<T> = (unsafe { alloc::alloc(layout) }).cast();
127+
let Some(ptr) = NonNull::new(ptr) else {
128+
alloc::handle_alloc_error(layout);
129+
};
130+
unsafe { ptr.write(v) };
131+
Self { ptr }
132+
}
133+
134+
#[inline]
135+
fn into_raw(b: Self) -> *mut Value<T> {
136+
let md = ManuallyDrop::new(b);
137+
md.ptr.as_ptr()
138+
}
139+
140+
#[inline]
141+
unsafe fn from_raw(ptr: *mut Value<T>) -> Self {
142+
Self { ptr: unsafe { NonNull::new_unchecked(ptr) } }
143+
}
144+
}
145+
146+
impl<T: 'static, const ALIGN: usize> Deref for AlignedBox<T, ALIGN> {
147+
type Target = Value<T>;
148+
149+
#[inline]
150+
fn deref(&self) -> &Self::Target {
151+
unsafe { &*(self.ptr.as_ptr()) }
152+
}
153+
}
154+
155+
impl<T: 'static, const ALIGN: usize> Drop for AlignedBox<T, ALIGN> {
156+
#[inline]
157+
fn drop(&mut self) {
158+
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
159+
160+
unsafe {
161+
let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place()));
162+
alloc::dealloc(self.ptr.as_ptr().cast(), layout);
163+
if let Err(payload) = unwind_result {
164+
resume_unwind(payload);
165+
}
166+
}
167+
}
168+
}
169+
113170
impl<T: 'static, const ALIGN: usize> Storage<T, ALIGN> {
114171
pub const fn new() -> Storage<T, ALIGN> {
115172
Storage { key: LazyKey::new(Some(destroy_value::<T, ALIGN>)), marker: PhantomData }
@@ -148,15 +205,11 @@ impl<T: 'static, const ALIGN: usize> Storage<T, ALIGN> {
148205
return ptr::null();
149206
}
150207

151-
// Manually allocate with the requested alignment
152-
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
153-
let ptr: *mut Value<T> = (unsafe { crate::alloc::alloc(layout) }).cast();
154-
if ptr.is_null() {
155-
crate::alloc::handle_alloc_error(layout);
156-
}
157-
unsafe {
158-
ptr.write(Value { value: i.and_then(Option::take).unwrap_or_else(f), key });
159-
}
208+
let value = AlignedBox::<T, ALIGN>::new(Value {
209+
value: i.and_then(Option::take).unwrap_or_else(f),
210+
key,
211+
});
212+
let ptr = AlignedBox::into_raw(value);
160213

161214
// SAFETY:
162215
// * key came from a `LazyKey` and is thus correct.
@@ -174,10 +227,7 @@ impl<T: 'static, const ALIGN: usize> Storage<T, ALIGN> {
174227
// initializer has already returned and the next scope only starts
175228
// after we return the pointer. Therefore, there can be no references
176229
// to the old value.
177-
unsafe {
178-
old.drop_in_place();
179-
crate::alloc::dealloc(old.cast(), layout);
180-
}
230+
drop(unsafe { AlignedBox::<T, ALIGN>::from_raw(old) });
181231
}
182232

183233
// SAFETY: We just created this value above.
@@ -196,22 +246,13 @@ unsafe extern "C" fn destroy_value<T: 'static, const ALIGN: usize>(ptr: *mut u8)
196246
// Note that to prevent an infinite loop we reset it back to null right
197247
// before we return from the destructor ourselves.
198248
abort_on_dtor_unwind(|| {
199-
let value_ptr: *mut Value<T> = ptr.cast();
200-
unsafe {
201-
let key = (*value_ptr).key;
202-
203-
// SAFETY: `key` is the TLS key `ptr` was stored under.
204-
set(key, ptr::without_provenance_mut(1));
205-
206-
// drop and deallocate the value
207-
let layout =
208-
Layout::from_size_align_unchecked(crate::mem::size_of::<Value<T>>(), ALIGN);
209-
value_ptr.drop_in_place();
210-
crate::alloc::dealloc(ptr, layout);
211-
212-
// SAFETY: `key` is the TLS key `ptr` was stored under.
213-
set(key, ptr::null_mut());
214-
};
249+
let ptr = unsafe { AlignedBox::<T, ALIGN>::from_raw(ptr as *mut Value<T>) };
250+
let key = ptr.key;
251+
// SAFETY: `key` is the TLS key `ptr` was stored under.
252+
unsafe { set(key, ptr::without_provenance_mut(1)) };
253+
drop(ptr);
254+
// SAFETY: `key` is the TLS key `ptr` was stored under.
255+
unsafe { set(key, ptr::null_mut()) };
215256
// Make sure that the runtime cleanup will be performed
216257
// after the next round of TLS destruction.
217258
guard::enable();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
thread_local! {
2+
static LOCAL: u64 = panic!();
3+
4+
}
5+
6+
fn main() {
7+
let _ = std::panic::catch_unwind(|| LOCAL.with(|_| {}));
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
thread 'main' ($TID) panicked at tests/pass/thread_local-panic.rs:LL:CC:
3+
explicit panic
4+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
5+
note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect

0 commit comments

Comments
 (0)