summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenno Lossin <lossin@kernel.org>2026-01-16 13:54:28 +0300
committerBenno Lossin <lossin@kernel.org>2026-01-17 12:51:43 +0300
commitceca298c53f9300ea689207f9ae9a3da3b4b4c4f (patch)
tree4bc5f9ef64f1a05e968adfd165b78a3e4e01f7fc
parentd26732e57b06ef32dadfc32d5de9ac39262698cb (diff)
downloadlinux-ceca298c53f9300ea689207f9ae9a3da3b4b4c4f.tar.xz
rust: pin-init: internal: init: add escape hatch for referencing initialized fields
The initializer macro emits mutable references for already initialized fields, which allows modifying or accessing them later in code blocks or when initializing other fields. This behavior results in compiler errors when combining with packed structs, since those do not permit creating references to misaligned fields. For example: #[repr(C, packed)] struct Foo { a: i8, b: i32, } fn main() { let _ = init!(Foo { a: -42, b: 42 }); } This will lead to an error like this: error[E0793]: reference to field of packed struct is unaligned --> tests/ui/compile-fail/init/packed_struct.rs:10:13 | 10 | 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) This was requested by Janne Grunau [1] and will most certainly be used by the kernel when we eventually end up with trying to initialize packed structs. Thus add an initializer attribute `#[disable_initialized_field_access]` that does what the name suggests: do not generate references to already initialized fields. There is space for future work: add yet another attribute which can be applied on fields of initializers that ask for said field to be made accessible. We can add that when the need arises. Requested-by: Janne Grunau <j@jannau.net> Link: https://lore.kernel.org/all/20251206170214.GE1097212@robin.jannau.net [1] Tested-by: Andreas Hindborg <a.hindborg@kernel.org> Reviewed-by: Gary Guo <gary@garyguo.net> Signed-off-by: Benno Lossin <lossin@kernel.org>
-rw-r--r--rust/pin-init/internal/src/init.rs75
1 files changed, 52 insertions, 23 deletions
diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs
index e8b93b444f7e..ed2e1462e176 100644
--- a/rust/pin-init/internal/src/init.rs
+++ b/rust/pin-init/internal/src/init.rs
@@ -62,6 +62,7 @@ impl InitializerKind {
enum InitializerAttribute {
DefaultError(DefaultErrorAttribute),
+ DisableInitializedFieldAccess,
}
struct DefaultErrorAttribute {
@@ -85,7 +86,6 @@ 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,7 +145,15 @@ 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, &data, &slot);
+ let init_fields = init_fields(
+ &fields,
+ pinned,
+ !attrs
+ .iter()
+ .any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
+ &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
@@ -228,6 +236,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi
fn init_fields(
fields: &Punctuated<InitializerField, Token![,]>,
pinned: bool,
+ generate_initialized_accessors: bool,
data: &Ident,
slot: &Ident,
) -> TokenStream {
@@ -263,6 +272,13 @@ fn init_fields(
unsafe { &mut (*#slot).#ident }
}
};
+ let accessor = generate_initialized_accessors.then(|| {
+ quote! {
+ #(#cfgs)*
+ #[allow(unused_variables)]
+ let #ident = #accessor;
+ }
+ });
quote! {
#(#attrs)*
{
@@ -270,37 +286,31 @@ fn init_fields(
// SAFETY: TODO
unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) };
}
- #(#cfgs)*
- #[allow(unused_variables)]
- let #ident = #accessor;
+ #accessor
}
}
InitializerKind::Init { ident, value, .. } => {
// Again span for better diagnostics
let init = format_ident!("init", span = value.span());
- if pinned {
+ let (value_init, accessor) = if pinned {
let project_ident = format_ident!("__project_{ident}");
- quote! {
- #(#attrs)*
- {
- let #init = #value;
+ (
+ 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(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? };
- }
- #(#cfgs)*
- // SAFETY: TODO
- #[allow(unused_variables)]
- let #ident = unsafe { #data.#project_ident(&mut (*#slot).#ident) };
- }
+ },
+ quote! {
+ // SAFETY: TODO
+ unsafe { #data.#project_ident(&mut (*#slot).#ident) }
+ },
+ )
} else {
- quote! {
- #(#attrs)*
- {
- let #init = #value;
+ (
+ quote! {
// SAFETY: `slot` is valid, because we are inside of an initializer
// closure, we return when an error/panic occurs.
unsafe {
@@ -309,12 +319,27 @@ fn init_fields(
::core::ptr::addr_of_mut!((*#slot).#ident),
)?
};
- }
+ },
+ quote! {
+ // SAFETY: TODO
+ unsafe { &mut (*#slot).#ident }
+ },
+ )
+ };
+ let accessor = generate_initialized_accessors.then(|| {
+ quote! {
#(#cfgs)*
- // SAFETY: TODO
#[allow(unused_variables)]
- let #ident = unsafe { &mut (*#slot).#ident };
+ let #ident = #accessor;
+ }
+ });
+ quote! {
+ #(#attrs)*
+ {
+ let #init = #value;
+ #value_init
}
+ #accessor
}
}
InitializerKind::Code { block: value, .. } => quote! {
@@ -446,6 +471,10 @@ impl Parse for Initializer {
if a.path().is_ident("default_error") {
a.parse_args::<DefaultErrorAttribute>()
.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"))
}