Skip to content

Commit 045dc7d

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 4c09883 commit 045dc7d

1 file changed

Lines changed: 53 additions & 23 deletions

File tree

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

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ impl InitializerKind {
5858
}
5959
}
6060

61+
#[expect(clippy::large_enum_variant)]
6162
enum InitializerAttribute {
6263
DefaultError(DefaultErrorAttribute),
64+
DisableInitializedFieldAccess,
6365
}
6466

6567
struct DefaultErrorAttribute {
@@ -83,7 +85,6 @@ pub(crate) fn expand(
8385
let error = error.map_or_else(
8486
|| {
8587
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
86-
#[expect(irrefutable_let_patterns)]
8788
if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr {
8889
Some(ty.clone())
8990
} else {
@@ -146,7 +147,15 @@ pub(crate) fn expand(
146147
};
147148
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
148149
let data = Ident::new("__data", Span::mixed_site());
149-
let init_fields = init_fields(&fields, pinned, &data, &slot);
150+
let init_fields = init_fields(
151+
&fields,
152+
pinned,
153+
!attrs
154+
.iter()
155+
.any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
156+
&data,
157+
&slot,
158+
);
150159
let field_check = make_field_check(&fields, init_kind, &path);
151160
quote! {{
152161
#macro_error
@@ -230,6 +239,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, macro_error: &mut crate::Erro
230239
fn init_fields(
231240
fields: &Punctuated<InitializerField, Token![,]>,
232241
pinned: bool,
242+
generate_initialized_accessors: bool,
233243
data: &Ident,
234244
slot: &Ident,
235245
) -> TokenStream {
@@ -265,44 +275,45 @@ fn init_fields(
265275
unsafe { &mut (*#slot).#ident }
266276
}
267277
};
278+
let accessor = generate_initialized_accessors.then(|| {
279+
quote! {
280+
#(#cfgs)*
281+
#[allow(unused_variables)]
282+
let #ident = #accessor;
283+
}
284+
});
268285
quote! {
269286
#(#attrs)*
270287
{
271288
#value_prep
272289
// SAFETY: TODO
273290
unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) };
274291
}
275-
#(#cfgs)*
276-
#[allow(unused_variables)]
277-
let #ident = #accessor;
292+
#accessor
278293
}
279294
}
280295
InitializerKind::Init { ident, value, .. } => {
281296
// Again span for better diagnostics
282297
let init = format_ident!("init", span = value.span());
283-
if pinned {
298+
let (value_init, accessor) = if pinned {
284299
let project_ident = format_ident!("__project_{ident}");
285-
quote! {
286-
#(#attrs)*
287-
{
288-
let #init = #value;
300+
(
301+
quote! {
289302
// SAFETY:
290303
// - `slot` is valid, because we are inside of an initializer closure, we
291304
// return when an error/panic occurs.
292305
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
293306
// for `#ident`.
294307
unsafe { #data.#ident(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? };
295-
}
296-
#(#cfgs)*
297-
// SAFETY: TODO
298-
#[allow(unused_variables)]
299-
let #ident = unsafe { #data.#project_ident(&mut (*#slot).#ident) };
300-
}
308+
},
309+
quote! {
310+
// SAFETY: TODO
311+
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
312+
},
313+
)
301314
} else {
302-
quote! {
303-
#(#attrs)*
304-
{
305-
let #init = #value;
315+
(
316+
quote! {
306317
// SAFETY: `slot` is valid, because we are inside of an initializer
307318
// closure, we return when an error/panic occurs.
308319
unsafe {
@@ -311,12 +322,27 @@ fn init_fields(
311322
::core::ptr::addr_of_mut!((*#slot).#ident),
312323
)?
313324
};
314-
}
325+
},
326+
quote! {
327+
// SAFETY: TODO
328+
unsafe { &mut (*#slot).#ident }
329+
},
330+
)
331+
};
332+
let accessor = generate_initialized_accessors.then(|| {
333+
quote! {
315334
#(#cfgs)*
316-
// SAFETY: TODO
317335
#[allow(unused_variables)]
318-
let #ident = unsafe { &mut (*#slot).#ident };
336+
let #ident = #accessor;
337+
}
338+
});
339+
quote! {
340+
#(#attrs)*
341+
{
342+
let #init = #value;
343+
#value_init
319344
}
345+
#accessor
320346
}
321347
}
322348
InitializerKind::Code { block: value, .. } => quote! {
@@ -448,6 +474,10 @@ impl Parse for Initializer {
448474
if a.path().is_ident("default_error") {
449475
a.parse_args::<DefaultErrorAttribute>()
450476
.map(InitializerAttribute::DefaultError)
477+
} else if a.path().is_ident("disable_initialized_field_access") {
478+
a.meta
479+
.require_path_only()
480+
.map(|_| InitializerAttribute::DisableInitializedFieldAccess)
451481
} else {
452482
Err(syn::Error::new_spanned(a, "unknown initializer attribute"))
453483
}

0 commit comments

Comments
 (0)