Skip to content

Commit 5436546

Browse files
committed
fix incorrect accessor reference lifetime
When a field has been initialized, `init!`/`pin_init!` create a reference or pinned reference to the field so it can be accessed later during the initialization of other fields. However, the reference it created is incorrectly `&'static` rather than just the scope of the initializer. This means that you can do init!(Foo { a: 1, _: { let b: &'static u32 = a; } }) which is unsound. This is caused by `&mut (*#slot).#ident`, which actually allows arbitrary lifetime, so this is effectively `'static`. Somewhat ironically, the safety justification of creating the accessor is.. "SAFETY: TODO". Fix it by adding `let_binding` method on `DropGuard` to shorten lifetime. This results exactly what we want for these accessors. The safety and invariant comments of `DropGuard` have been reworked; instead of reasoning about what caller can do with the guard, express it in a way that the ownership is transferred to the guard and `forget` takes it back, so the unsafe operations within the `DropGuard` can be more easily justified. Fixes: db96c51 ("add references to previously initialized fields") Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent 1f5ec9c commit 5436546

4 files changed

Lines changed: 97 additions & 68 deletions

File tree

internal/src/init.rs

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -249,80 +249,45 @@ fn init_fields(
249249
});
250250
// Again span for better diagnostics
251251
let write = quote_spanned!(ident.span()=> ::core::ptr::write);
252-
let accessor = if pinned {
253-
let project_ident = format_ident!("__project_{ident}");
254-
quote! {
255-
// SAFETY: TODO
256-
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
257-
}
258-
} else {
259-
quote! {
260-
// SAFETY: TODO
261-
unsafe { &mut (*#slot).#ident }
262-
}
263-
};
264252
quote! {
265253
#(#attrs)*
266254
{
267255
#value_prep
268256
// SAFETY: TODO
269257
unsafe { #write(&raw mut (*#slot).#ident, #value_ident) };
270258
}
271-
#(#cfgs)*
272-
#[allow(unused_variables)]
273-
let #ident = #accessor;
274259
}
275260
}
276261
InitializerKind::Init { ident, value, .. } => {
277262
// Again span for better diagnostics
278263
let init = format_ident!("init", span = value.span());
279-
// NOTE: the field accessor ensures that the initialized field is properly aligned.
280-
// Unaligned fields will cause the compiler to emit E0793. We do not support
281-
// unaligned fields since `Init::__init` requires an aligned pointer; the call to
282-
// `ptr::write` below has the same requirement.
283-
let (value_init, accessor) = if pinned {
284-
let project_ident = format_ident!("__project_{ident}");
285-
(
286-
quote! {
287-
// SAFETY:
288-
// - `slot` is valid, because we are inside of an initializer closure, we
289-
// return when an error/panic occurs.
290-
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
291-
// for `#ident`.
292-
unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
293-
},
294-
quote! {
295-
// SAFETY: TODO
296-
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
297-
},
298-
)
264+
let value_init = if pinned {
265+
quote! {
266+
// SAFETY:
267+
// - `slot` is valid, because we are inside of an initializer closure, we
268+
// return when an error/panic occurs.
269+
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
270+
// for `#ident`.
271+
unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
272+
}
299273
} else {
300-
(
301-
quote! {
302-
// SAFETY: `slot` is valid, because we are inside of an initializer
303-
// closure, we return when an error/panic occurs.
304-
unsafe {
305-
::pin_init::Init::__init(
306-
#init,
307-
&raw mut (*#slot).#ident,
308-
)?
309-
};
310-
},
311-
quote! {
312-
// SAFETY: TODO
313-
unsafe { &mut (*#slot).#ident }
314-
},
315-
)
274+
quote! {
275+
// SAFETY: `slot` is valid, because we are inside of an initializer
276+
// closure, we return when an error/panic occurs.
277+
unsafe {
278+
::pin_init::Init::__init(
279+
#init,
280+
&raw mut (*#slot).#ident,
281+
)?
282+
};
283+
}
316284
};
317285
quote! {
318286
#(#attrs)*
319287
{
320288
let #init = #value;
321289
#value_init
322290
}
323-
#(#cfgs)*
324-
#[allow(unused_variables)]
325-
let #ident = #accessor;
326291
}
327292
}
328293
InitializerKind::Code { block: value, .. } => quote! {
@@ -335,18 +300,41 @@ fn init_fields(
335300
if let Some(ident) = kind.ident() {
336301
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
337302
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());
303+
304+
// NOTE: The reference is derived from the guard so that it only lives as long as the
305+
// guard does and cannot escape the scope. If it's created via `&mut (*#slot).#ident`
306+
// like the unaligned field guard, it will become effectively `'static`.
307+
let accessor = if pinned {
308+
let project_ident = format_ident!("__project_{ident}");
309+
quote! {
310+
// SAFETY: the initialization is pinned.
311+
unsafe { #data.#project_ident(#guard.let_binding()) }
312+
}
313+
} else {
314+
quote! {
315+
#guard.let_binding()
316+
}
317+
};
318+
338319
res.extend(quote! {
339320
#(#cfgs)*
340-
// Create the drop guard:
321+
// Create the drop guard.
341322
//
342-
// We rely on macro hygiene to make it impossible for users to access this local
343-
// variable.
344-
// SAFETY: We forget the guard later when initialization has succeeded.
345-
let #guard = unsafe {
323+
// SAFETY:
324+
// - `&raw mut (*slot).#ident` is valid.
325+
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
326+
// - `(*slot).#ident` has been initialized above.
327+
// - We only need the ownership to the pointee back when initialization has
328+
// succeeded, where we `forget` the guard.
329+
let mut #guard = unsafe {
346330
::pin_init::__internal::DropGuard::new(
347331
&raw mut (*slot).#ident
348332
)
349333
};
334+
335+
#(#cfgs)*
336+
#[allow(unused_variables)]
337+
let #ident = #accessor;
350338
});
351339
guards.push(guard);
352340
guard_attrs.push(cfgs);

src/__internal.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,32 +238,42 @@ fn stack_init_reuse() {
238238
/// When a value of this type is dropped, it drops a `T`.
239239
///
240240
/// Can be forgotten to prevent the drop.
241+
///
242+
/// # Invariants
243+
///
244+
/// - `ptr` is valid and properly aligned.
245+
/// - `*ptr` is initialized and owned by this guard.
241246
pub struct DropGuard<T: ?Sized> {
242247
ptr: *mut T,
243248
}
244249

245250
impl<T: ?Sized> DropGuard<T> {
246-
/// Creates a new [`DropGuard<T>`]. It will [`ptr::drop_in_place`] `ptr` when it gets dropped.
251+
/// Creates a drop guard and transfer the ownership of the pointer content.
247252
///
248-
/// # Safety
253+
/// The ownership is only relinguished if the guard is forgotten via [`core::mem::forget`].
249254
///
250-
/// `ptr` must be a valid pointer.
255+
/// # Safety
251256
///
252-
/// It is the callers responsibility that `self` will only get dropped if the pointee of `ptr`:
253-
/// - has not been dropped,
254-
/// - is not accessible by any other means,
255-
/// - will not be dropped by any other means.
257+
/// - `ptr` is valid and properly aligned.
258+
/// - `*ptr` is initialized, and the ownership is transferred to this guard.
256259
#[inline]
257260
pub unsafe fn new(ptr: *mut T) -> Self {
261+
// INVARIANT: By safety requirement.
258262
Self { ptr }
259263
}
264+
265+
/// Create a let binding for accessor use.
266+
#[inline]
267+
pub fn let_binding(&mut self) -> &mut T {
268+
// SAFETY: Per type invariant.
269+
unsafe { &mut *self.ptr }
270+
}
260271
}
261272

262273
impl<T: ?Sized> Drop for DropGuard<T> {
263274
#[inline]
264275
fn drop(&mut self) {
265-
// SAFETY: A `DropGuard` can only be constructed using the unsafe `new` function
266-
// ensuring that this operation is safe.
276+
// SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard.
267277
unsafe { ptr::drop_in_place(self.ptr) }
268278
}
269279
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use pin_init::*;
2+
3+
struct Foo {
4+
x: usize,
5+
}
6+
7+
fn main() {
8+
let _ = init!(Foo {
9+
x: 0,
10+
_: {
11+
let _: &'static usize = x;
12+
},
13+
});
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0505]: cannot move out of value because it is borrowed
2+
--> tests/ui/compile-fail/init/accessor_lifetime.rs:8:13
3+
|
4+
8 | let _ = init!(Foo {
5+
| _____________^
6+
9 | | x: 0,
7+
10 | | _: {
8+
11 | | let _: &'static usize = x;
9+
| | -------------- type annotation requires that borrow lasts for `'static`
10+
12 | | },
11+
13 | | });
12+
| | ^
13+
| | |
14+
| |______move out of value occurs here
15+
| borrow of value occurs here
16+
|
17+
= note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)