Skip to content

Commit 4034668

Browse files
committed
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] Signed-off-by: Benno Lossin <lossin@kernel.org>
1 parent 6921137 commit 4034668

1 file changed

Lines changed: 52 additions & 23 deletions

File tree

rust/pin-init/internal/src/init.rs

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ impl InitializerKind {
5858

5959
enum InitializerAttribute {
6060
DefaultError(DefaultErrorAttribute),
61+
DisableInitializedFieldAccess,
6162
}
6263

6364
struct DefaultErrorAttribute {
@@ -80,7 +81,6 @@ pub(crate) fn expand(
8081
let mut errors = TokenStream::new();
8182
let mut error = error.map(|(_, err)| err);
8283
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
83-
#[expect(irrefutable_let_patterns)]
8484
if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr {
8585
Some(ty.clone())
8686
} else {
@@ -142,7 +142,15 @@ pub(crate) fn expand(
142142
};
143143
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
144144
let data = format_ident!("__data", span = Span::mixed_site());
145-
let init_fields = init_fields(&fields, pinned, &data, &slot);
145+
let init_fields = init_fields(
146+
&fields,
147+
pinned,
148+
!attrs
149+
.iter()
150+
.any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
151+
&data,
152+
&slot,
153+
);
146154
let field_check = make_field_check(&fields, init_kind, &path);
147155
quote! {{
148156
// We do not want to allow arbitrary returns, so we declare this type as the `Ok` return
@@ -225,6 +233,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, errors: &mut TokenStream) ->
225233
fn init_fields(
226234
fields: &Punctuated<InitializerField, Token![,]>,
227235
pinned: bool,
236+
generate_initialized_accessors: bool,
228237
data: &Ident,
229238
slot: &Ident,
230239
) -> TokenStream {
@@ -255,44 +264,45 @@ fn init_fields(
255264
unsafe { &mut (*#slot).#ident }
256265
}
257266
};
267+
let accessor = generate_initialized_accessors.then(|| {
268+
quote! {
269+
#(#attrs)*
270+
#[allow(unused_variables)]
271+
let #ident = #accessor;
272+
}
273+
});
258274
quote! {
259275
#(#attrs)*
260276
{
261277
#value_prep
262278
// SAFETY: TODO
263279
unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) };
264280
}
265-
#(#attrs)*
266-
#[allow(unused_variables)]
267-
let #ident = #accessor;
281+
#accessor
268282
}
269283
}
270284
InitializerKind::Init { ident, value, .. } => {
271285
// Again span for better diagnostics
272286
let init = format_ident!("init", span = value.span());
273-
if pinned {
287+
let (value_init, accessor) = if pinned {
274288
let project_ident = format_ident!("__project_{ident}");
275-
quote! {
276-
#(#attrs)*
277-
{
278-
let #init = #value;
289+
(
290+
quote! {
279291
// SAFETY:
280292
// - `slot` is valid, because we are inside of an initializer closure, we
281293
// return when an error/panic occurs.
282294
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
283295
// for `#ident`.
284296
unsafe { #data.#ident(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? };
285-
}
286-
#(#attrs)*
287-
// SAFETY: TODO
288-
#[allow(unused_variables)]
289-
let #ident = unsafe { #data.#project_ident(&mut (*#slot).#ident) };
290-
}
297+
},
298+
quote! {
299+
// SAFETY: TODO
300+
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
301+
},
302+
)
291303
} else {
292-
quote! {
293-
#(#attrs)*
294-
{
295-
let #init = #value;
304+
(
305+
quote! {
296306
// SAFETY: `slot` is valid, because we are inside of an initializer
297307
// closure, we return when an error/panic occurs.
298308
unsafe {
@@ -301,12 +311,27 @@ fn init_fields(
301311
::core::ptr::addr_of_mut!((*#slot).#ident),
302312
)?
303313
};
304-
}
314+
},
315+
quote! {
316+
// SAFETY: TODO
317+
unsafe { &mut (*#slot).#ident }
318+
},
319+
)
320+
};
321+
let accessor = generate_initialized_accessors.then(|| {
322+
quote! {
305323
#(#attrs)*
306-
// SAFETY: TODO
307324
#[allow(unused_variables)]
308-
let #ident = unsafe { &mut (*#slot).#ident };
325+
let #ident = #accessor;
326+
}
327+
});
328+
quote! {
329+
#(#attrs)*
330+
{
331+
let #init = #value;
332+
#value_init
309333
}
334+
#accessor
310335
}
311336
}
312337
InitializerKind::Code { block: value, .. } => quote! {
@@ -436,6 +461,10 @@ impl Parse for Initializer {
436461
if a.path().is_ident("default_error") {
437462
a.parse_args::<DefaultErrorAttribute>()
438463
.map(InitializerAttribute::DefaultError)
464+
} else if a.path().is_ident("disable_initialized_field_access") {
465+
a.meta
466+
.require_path_only()
467+
.map(|_| InitializerAttribute::DisableInitializedFieldAccess)
439468
} else {
440469
Err(syn::Error::new_spanned(a, "unknown initializer attribute"))
441470
}

0 commit comments

Comments
 (0)