From 54fd39f865589228e9d87de6d781dd34d4b39cc5 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 packed struct, since they use operations that require aligned pointers. This means that any code using packed structs and pin-init is unsound. Thus remove the `#[disable_initialized_field_access]` attribute from `init!`, which is the only safe way to create an initializer of a packed struct. In the future, we can add support for packed structs by changing the trait infrastructure to include `UnalignedInit` or hopefully another mechanism. Reported-by: Gary Guo Link: https://rust-for-linux.zulipchat.com/#narrow/channel/561532-pin-init/topic/initialized.20field.20accessor.20detection/with/576210658 [1] 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 0cd99fe034648f522da56f5ab0cb7978ab4719a8 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 We cannot support packed structs without significant changes [1]. The field accessors ensure that the compiler emits an error if one tries to create an initializer for a packed struct. Link: https://github.com/Rust-for-Linux/pin-init/issues/112 [1] Signed-off-by: Benno Lossin --- internal/src/init.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/src/init.rs b/internal/src/init.rs index 1b8b7081..fe1bda09 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -257,6 +257,11 @@ 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 struct is not + // `repr(packed)`. If it were, the compiler would emit E0793. We do not support + // packed structs, since `Init::__init` requires an aligned pointer; the same + // requirement that the call to `ptr::write` below has. + // For more info see let accessor = if pinned { let project_ident = format_ident!("__project_{ident}"); quote! { @@ -284,6 +289,11 @@ 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 struct is not + // `repr(packed)`. If it were, the compiler would emit E0793. We do not support + // packed structs, since `Init::__init` requires an aligned pointer; the same + // requirement that the call to `ptr::write` below has. + // For more info see let (value_init, accessor) = if pinned { let project_ident = format_ident!("__project_{ident}"); (