Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 84 additions & 100 deletions internal/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,84 +249,45 @@ fn init_fields(
});
// Again span for better diagnostics
let write = quote_spanned!(ident.span()=> ::core::ptr::write);
// NOTE: the field accessor ensures that the initialized field is properly aligned.
// Unaligned fields will cause the compiler to emit E0793. We do not support
// unaligned fields since `Init::__init` requires an aligned pointer; the call to
// `ptr::write` below has the same requirement.
let accessor = if pinned {
let project_ident = format_ident!("__project_{ident}");
quote! {
// SAFETY: TODO
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
}
} else {
quote! {
// SAFETY: TODO
unsafe { &mut (*#slot).#ident }
}
};
quote! {
#(#attrs)*
{
#value_prep
// SAFETY: TODO
unsafe { #write(&raw mut (*#slot).#ident, #value_ident) };
}
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
}
InitializerKind::Init { ident, value, .. } => {
// Again span for better diagnostics
let init = format_ident!("init", span = value.span());
// NOTE: the field accessor ensures that the initialized field is properly aligned.
// Unaligned fields will cause the compiler to emit E0793. We do not support
// unaligned fields since `Init::__init` requires an aligned pointer; the call to
// `ptr::write` below has the same requirement.
let (value_init, accessor) = if pinned {
let project_ident = format_ident!("__project_{ident}");
(
quote! {
// SAFETY:
// - `slot` is valid, because we are inside of an initializer closure, we
// return when an error/panic occurs.
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
// for `#ident`.
unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
},
quote! {
// SAFETY: TODO
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
},
)
let value_init = if pinned {
quote! {
// SAFETY:
// - `slot` is valid, because we are inside of an initializer closure, we
// return when an error/panic occurs.
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
// for `#ident`.
unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? };
}
} else {
(
quote! {
// SAFETY: `slot` is valid, because we are inside of an initializer
// closure, we return when an error/panic occurs.
unsafe {
::pin_init::Init::__init(
#init,
&raw mut (*#slot).#ident,
)?
};
},
quote! {
// SAFETY: TODO
unsafe { &mut (*#slot).#ident }
},
)
quote! {
// SAFETY: `slot` is valid, because we are inside of an initializer
// closure, we return when an error/panic occurs.
unsafe {
::pin_init::Init::__init(
#init,
&raw mut (*#slot).#ident,
)?
};
}
};
quote! {
#(#attrs)*
{
let #init = #value;
#value_init
}
#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
}
}
InitializerKind::Code { block: value, .. } => quote! {
Expand All @@ -339,18 +300,41 @@ fn init_fields(
if let Some(ident) = kind.ident() {
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
let guard = format_ident!("__{ident}_guard", span = Span::mixed_site());

// NOTE: The reference is derived from the guard so that it only lives as long as the
// guard does and cannot escape the scope. If it's created via `&mut (*#slot).#ident`
// like the unaligned field guard, it will become effectively `'static`.
let accessor = if pinned {
let project_ident = format_ident!("__project_{ident}");
quote! {
// SAFETY: the initialization is pinned.
unsafe { #data.#project_ident(#guard.let_binding()) }
}
} else {
quote! {
#guard.let_binding()
}
};
Comment thread
BennoLossin marked this conversation as resolved.

res.extend(quote! {
#(#cfgs)*
// Create the drop guard:
// Create the drop guard.
//
// We rely on macro hygiene to make it impossible for users to access this local
// variable.
// SAFETY: We forget the guard later when initialization has succeeded.
let #guard = unsafe {
// SAFETY:
// - `&raw mut (*slot).#ident` is valid.
// - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned.
// - `(*slot).#ident` has been initialized above.
// - We only need the ownership to the pointee back when initialization has
// succeeded, where we `forget` the guard.
let mut #guard = unsafe {
::pin_init::__internal::DropGuard::new(
&raw mut (*slot).#ident
)
};

#(#cfgs)*
#[allow(unused_variables)]
let #ident = #accessor;
});
guards.push(guard);
guard_attrs.push(cfgs);
Expand All @@ -367,49 +351,49 @@ fn init_fields(
}
}

/// Generate the check for ensuring that every field has been initialized.
/// Generate the check for ensuring that every field has been initialized and aligned.
fn make_field_check(
fields: &Punctuated<InitializerField, Token![,]>,
init_kind: InitKind,
path: &Path,
) -> TokenStream {
let field_attrs = fields
let field_attrs: Vec<_> = fields
.iter()
.filter_map(|f| f.kind.ident().map(|_| &f.attrs));
let field_name = fields.iter().filter_map(|f| f.kind.ident());
match init_kind {
InitKind::Normal => quote! {
// We use unreachable code to ensure that all fields have been mentioned exactly once,
// this struct initializer will still be type-checked and complain with a very natural
// error message if a field is forgotten/mentioned more than once.
#[allow(unreachable_code, clippy::diverging_sub_expression)]
// SAFETY: this code is never executed.
let _ = || unsafe {
::core::ptr::write(slot, #path {
#(
#(#field_attrs)*
#field_name: ::core::panic!(),
)*
})
};
},
InitKind::Zeroing => quote! {
// We use unreachable code to ensure that all fields have been mentioned at most once.
// Since the user specified `..Zeroable::zeroed()` at the end, all missing fields will
// be zeroed. This struct initializer will still be type-checked and complain with a
// very natural error message if a field is mentioned more than once, or doesn't exist.
#[allow(unreachable_code, clippy::diverging_sub_expression, unused_assignments)]
// SAFETY: this code is never executed.
let _ = || unsafe {
::core::ptr::write(slot, #path {
#(
#(#field_attrs)*
#field_name: ::core::panic!(),
)*
..::core::mem::zeroed()
})
};
},
.filter_map(|f| f.kind.ident().map(|_| &f.attrs))
.collect();
let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect();
let zeroing_trailer = match init_kind {
InitKind::Normal => None,
InitKind::Zeroing => Some(quote! {
..::core::mem::zeroed()
}),
};
quote! {
#[allow(unreachable_code, clippy::diverging_sub_expression)]
// We use unreachable code to perform field checks. They're still checked by the compiler.
// SAFETY: this code is never executed.
let _ = || unsafe {
// Create references to ensure that the initialized field is properly aligned.
// Unaligned fields will cause the compiler to emit E0793. We do not support
// unaligned fields since `Init::__init` requires an aligned pointer; the call to
// `ptr::write` for value-initialization case has the same requirement.
#(
#(#field_attrs)*
let _ = &(*slot).#field_name;
)*

// If the zeroing trailer is not present, this checks that all fields have been
// mentioned exactly once. If the zeroing trailer is present, all missing fields will be
// zeroed, so this checks that all fields have been mentioned at most once. The use of
// struct initializer will still generate very natural error messages for any misuse.
::core::ptr::write(slot, #path {
#(
#(#field_attrs)*
#field_name: ::core::panic!(),
)*
#zeroing_trailer
})
};
}
}

Expand Down
28 changes: 19 additions & 9 deletions src/__internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,32 +238,42 @@ fn stack_init_reuse() {
/// When a value of this type is dropped, it drops a `T`.
///
/// Can be forgotten to prevent the drop.
///
/// # Invariants
///
/// - `ptr` is valid and properly aligned.
/// - `*ptr` is initialized and owned by this guard.
pub struct DropGuard<T: ?Sized> {
ptr: *mut T,
}

impl<T: ?Sized> DropGuard<T> {
/// Creates a new [`DropGuard<T>`]. It will [`ptr::drop_in_place`] `ptr` when it gets dropped.
/// Creates a drop guard and transfer the ownership of the pointer content.
///
/// # Safety
/// The ownership is only relinguished if the guard is forgotten via [`core::mem::forget`].
///
/// `ptr` must be a valid pointer.
/// # Safety
///
/// It is the callers responsibility that `self` will only get dropped if the pointee of `ptr`:
/// - has not been dropped,
/// - is not accessible by any other means,
/// - will not be dropped by any other means.
/// - `ptr` is valid and properly aligned.
/// - `*ptr` is initialized, and the ownership is transferred to this guard.
#[inline]
pub unsafe fn new(ptr: *mut T) -> Self {
// INVARIANT: By safety requirement.
Self { ptr }
}

/// Create a let binding for accessor use.
#[inline]
pub fn let_binding(&mut self) -> &mut T {
// SAFETY: Per type invariant.
unsafe { &mut *self.ptr }
}
}

impl<T: ?Sized> Drop for DropGuard<T> {
#[inline]
fn drop(&mut self) {
// SAFETY: A `DropGuard` can only be constructed using the unsafe `new` function
// ensuring that this operation is safe.
// SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard.
unsafe { ptr::drop_in_place(self.ptr) }
}
}
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/compile-fail/init/accessor_lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use pin_init::*;

struct Foo {
x: usize,
}

fn main() {
let _ = init!(Foo {
x: 0,
_: {
let _: &'static usize = x;
},
});
}
17 changes: 17 additions & 0 deletions tests/ui/compile-fail/init/accessor_lifetime.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0505]: cannot move out of value because it is borrowed
--> tests/ui/compile-fail/init/accessor_lifetime.rs:8:13
|
8 | let _ = init!(Foo {
| _____________^
9 | | x: 0,
10 | | _: {
11 | | let _: &'static usize = x;
| | -------------- type annotation requires that borrow lasts for `'static`
12 | | },
13 | | });
| | ^
| | |
| |______move out of value occurs here
| borrow of value occurs here
|
= note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)
5 changes: 5 additions & 0 deletions tests/ui/compile-fail/init/packed_struct.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use pin_init::*;

#[repr(C, packed)]
#[derive(Zeroable)]
struct Foo {
a: i8,
b: i32,
}

fn main() {
let _ = init!(Foo { a: -42, b: 42 });
let _ = init!(Foo {
b: 42,
..Zeroable::init_zeroed()
});
}
19 changes: 17 additions & 2 deletions tests/ui/compile-fail/init/packed_struct.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
error[E0793]: reference to field of packed struct is unaligned
--> tests/ui/compile-fail/init/packed_struct.rs:10:13
--> tests/ui/compile-fail/init/packed_struct.rs:11:13
|
10 | let _ = init!(Foo { a: -42, b: 42 });
11 | let _ = init!(Foo { a: -42, b: 42 });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this struct is 1-byte aligned, but the type of this field may require higher alignment
= note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
= help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
= note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0793]: reference to field of packed struct is unaligned
--> tests/ui/compile-fail/init/packed_struct.rs:12:13
|
12 | let _ = init!(Foo {
| _____________^
13 | | b: 42,
14 | | ..Zeroable::init_zeroed()
15 | | });
| |______^
|
= note: this struct is 1-byte aligned, but the type of this field may require higher alignment
= note: creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)
= help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)
= note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info)