From e194b1c06b602a0dd768baba9f7758f6ff2c1ef0 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Fri, 27 Feb 2026 20:12:01 +0100 Subject: [PATCH 1/2] internal: init: remove `#[disable_initialized_field_access]` Gary noticed [1] that the initializer macros as well as the `[Pin]Init` traits cannot support unaligned fields, since they use operations that require aligned pointers. This means that any code using structs with unaligned fields in pin-init is unsound. By default, the `init!` macro generates references to initialized fields, which makes the compiler check that those fields are aligned. However, we added the `#[disable_initialized_field_access]` attribute to avoid this behavior in 71988db4a9b8 ("internal: init: add escape hatch for referencing initialized fields"). Thus remove the `#[disable_initialized_field_access]` attribute from `init!`, which is the only safe way to create an initializer handling unaligned fields. If support for in-place initializing structs with unaligned fields is required in the future, we could figure out a solution. This is tracked in [2]. Reported-by: Gary Guo Link: https://rust-for-linux.zulipchat.com/#narrow/channel/561532-pin-init/topic/initialized.20field.20accessor.20detection/with/576210658 [1] Link: https://github.com/Rust-for-Linux/pin-init/issues/112 [2] Fixes: 71988db4a9b8 ("internal: init: add escape hatch for referencing initialized fields") Signed-off-by: Benno Lossin --- CHANGELOG.md | 1 - internal/src/init.rs | 39 ++-------- tests/ui/compile-fail/init/no_field_access.rs | 19 ----- .../compile-fail/init/no_field_access.stderr | 5 -- tests/ui/expand/no_field_access.expanded.rs | 76 ------------------- tests/ui/expand/no_field_access.rs | 1 - 6 files changed, 8 insertions(+), 133 deletions(-) delete mode 100644 tests/ui/compile-fail/init/no_field_access.rs delete mode 100644 tests/ui/compile-fail/init/no_field_access.stderr delete mode 100644 tests/ui/expand/no_field_access.expanded.rs delete mode 120000 tests/ui/expand/no_field_access.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b248b3..c203f7ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `[pin_]init!` now supports attributes on fields (such as `#[cfg(...)]`). - Add a `#[default_error()]` attribute to `[pin_]init!` to override the default error (when no `? Error` is specified). -- Support packed struct in `[pin_]init!` with `#[disable_initialized_field_access]`. ### Removed diff --git a/internal/src/init.rs b/internal/src/init.rs index 27bf9cfe..1b8b7081 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -62,7 +62,6 @@ impl InitializerKind { enum InitializerAttribute { DefaultError(DefaultErrorAttribute), - DisableInitializedFieldAccess, } struct DefaultErrorAttribute { @@ -86,6 +85,7 @@ pub(crate) fn expand( let error = error.map_or_else( || { if let Some(default_error) = attrs.iter().fold(None, |acc, attr| { + #[expect(irrefutable_let_patterns)] if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr { Some(ty.clone()) } else { @@ -145,15 +145,7 @@ pub(crate) fn expand( }; // `mixed_site` ensures that the data is not accessible to the user-controlled code. let data = Ident::new("__data", Span::mixed_site()); - let init_fields = init_fields( - &fields, - pinned, - !attrs - .iter() - .any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)), - &data, - &slot, - ); + let init_fields = init_fields(&fields, pinned, &data, &slot); let field_check = make_field_check(&fields, init_kind, &path); Ok(quote! {{ // We do not want to allow arbitrary returns, so we declare this type as the `Ok` return @@ -242,7 +234,6 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi fn init_fields( fields: &Punctuated, pinned: bool, - generate_initialized_accessors: bool, data: &Ident, slot: &Ident, ) -> TokenStream { @@ -278,13 +269,6 @@ fn init_fields( unsafe { &mut (*#slot).#ident } } }; - let accessor = generate_initialized_accessors.then(|| { - quote! { - #(#cfgs)* - #[allow(unused_variables)] - let #ident = #accessor; - } - }); quote! { #(#attrs)* { @@ -292,7 +276,9 @@ fn init_fields( // SAFETY: TODO unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) }; } - #accessor + #(#cfgs)* + #[allow(unused_variables)] + let #ident = #accessor; } } InitializerKind::Init { ident, value, .. } => { @@ -332,20 +318,15 @@ fn init_fields( }, ) }; - let accessor = generate_initialized_accessors.then(|| { - quote! { - #(#cfgs)* - #[allow(unused_variables)] - let #ident = #accessor; - } - }); quote! { #(#attrs)* { let #init = #value; #value_init } - #accessor + #(#cfgs)* + #[allow(unused_variables)] + let #ident = #accessor; } } InitializerKind::Code { block: value, .. } => quote! { @@ -472,10 +453,6 @@ impl Parse for Initializer { if a.path().is_ident("default_error") { a.parse_args::() .map(InitializerAttribute::DefaultError) - } else if a.path().is_ident("disable_initialized_field_access") { - a.meta - .require_path_only() - .map(|_| InitializerAttribute::DisableInitializedFieldAccess) } else { Err(syn::Error::new_spanned(a, "unknown initializer attribute")) } diff --git a/tests/ui/compile-fail/init/no_field_access.rs b/tests/ui/compile-fail/init/no_field_access.rs deleted file mode 100644 index 752a6393..00000000 --- a/tests/ui/compile-fail/init/no_field_access.rs +++ /dev/null @@ -1,19 +0,0 @@ -use pin_init::*; - -#[repr(C, packed)] -struct Foo { - a: i8, - b: i32, - c: i32, -} - -fn main() { - let _ = init!( - #[disable_initialized_field_access] - Foo { - c: -42, - b: *c, - a: 0, - } - ); -} diff --git a/tests/ui/compile-fail/init/no_field_access.stderr b/tests/ui/compile-fail/init/no_field_access.stderr deleted file mode 100644 index e04e24f2..00000000 --- a/tests/ui/compile-fail/init/no_field_access.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0425]: cannot find value `c` in this scope - --> tests/ui/compile-fail/init/no_field_access.rs:15:17 - | -15 | b: *c, - | ^ not found in this scope diff --git a/tests/ui/expand/no_field_access.expanded.rs b/tests/ui/expand/no_field_access.expanded.rs deleted file mode 100644 index 2b935432..00000000 --- a/tests/ui/expand/no_field_access.expanded.rs +++ /dev/null @@ -1,76 +0,0 @@ -use pin_init::*; -#[repr(C, packed)] -struct Foo { - a: i8, - b: i32, - c: i32, -} -fn main() { - let _ = { - struct __InitOk; - let __data = unsafe { - use ::pin_init::__internal::HasInitData; - Foo::__init_data() - }; - let init = ::pin_init::__internal::InitData::make_closure::< - _, - __InitOk, - ::core::convert::Infallible, - >( - __data, - move |slot| { - { - struct __InitOk; - { - let c = -42; - unsafe { ::core::ptr::write(&raw mut (*slot).c, c) }; - } - let __c_guard = unsafe { - ::pin_init::__internal::DropGuard::new(&raw mut (*slot).c) - }; - { - let b = *c; - unsafe { ::core::ptr::write(&raw mut (*slot).b, b) }; - } - let __b_guard = unsafe { - ::pin_init::__internal::DropGuard::new(&raw mut (*slot).b) - }; - { - let a = 0; - unsafe { ::core::ptr::write(&raw mut (*slot).a, a) }; - } - let __a_guard = unsafe { - ::pin_init::__internal::DropGuard::new(&raw mut (*slot).a) - }; - ::core::mem::forget(__c_guard); - ::core::mem::forget(__b_guard); - ::core::mem::forget(__a_guard); - #[allow(unreachable_code, clippy::diverging_sub_expression)] - let _ = || unsafe { - ::core::ptr::write( - slot, - Foo { - c: ::core::panicking::panic("explicit panic"), - b: ::core::panicking::panic("explicit panic"), - a: ::core::panicking::panic("explicit panic"), - }, - ) - }; - } - Ok(__InitOk) - }, - ); - let init = move | - slot, - | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) - }; - let init = unsafe { - ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) - }; - #[allow( - clippy::let_and_return, - reason = "some clippy versions warn about the let binding" - )] init - }; -} diff --git a/tests/ui/expand/no_field_access.rs b/tests/ui/expand/no_field_access.rs deleted file mode 120000 index f2aacbf0..00000000 --- a/tests/ui/expand/no_field_access.rs +++ /dev/null @@ -1 +0,0 @@ -../compile-fail/init/no_field_access.rs \ No newline at end of file From 14ea16fbf958f8bc7352c89c6d346a5a426b1a66 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 28 Feb 2026 12:08:31 +0100 Subject: [PATCH 2/2] internal: init: document load-bearing fact of field accessors The functions `[Pin]Init::__[pinned_]init` and `ptr::write` called from the `init!` macro require the passed pointer to be aligned. This fact is ensured by the creation of field accessors to previously initialized fields. Since we missed this very important fact from the beginning [1], document it in the code. Link: https://rust-for-linux.zulipchat.com/#narrow/channel/561532-pin-init/topic/initialized.20field.20accessor.20detection/with/576210658 [1] Signed-off-by: Benno Lossin --- internal/src/init.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/src/init.rs b/internal/src/init.rs index 1b8b7081..3bf1054e 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -257,6 +257,10 @@ 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! { @@ -284,6 +288,10 @@ fn init_fields( 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}"); (