Skip to content

Commit 61919f0

Browse files
committed
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> Signed-off-by: Benno Lossin <lossin@kernel.org>
1 parent 07d039a commit 61919f0

1 file changed

Lines changed: 53 additions & 23 deletions

File tree

internal/src/init.rs

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

63+
#[expect(clippy::large_enum_variant)]
6364
enum InitializerAttribute {
6465
DefaultError(DefaultErrorAttribute),
66+
DisableInitializedFieldAccess,
6567
}
6668

6769
struct DefaultErrorAttribute {
@@ -85,7 +87,6 @@ pub(crate) fn expand(
8587
let error = error.map_or_else(
8688
|| {
8789
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
88-
#[expect(irrefutable_let_patterns)]
8990
if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr {
9091
Some(ty.clone())
9192
} else {
@@ -145,7 +146,15 @@ pub(crate) fn expand(
145146
};
146147
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
147148
let data = Ident::new("__data", Span::mixed_site());
148-
let init_fields = init_fields(&fields, pinned, &data, &slot);
149+
let init_fields = init_fields(
150+
&fields,
151+
pinned,
152+
!attrs
153+
.iter()
154+
.any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
155+
&data,
156+
&slot,
157+
);
149158
let field_check = make_field_check(&fields, init_kind, &path);
150159
Ok(quote! {{
151160
// We do not want to allow arbitrary returns, so we declare this type as the `Ok` return
@@ -228,6 +237,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi
228237
fn init_fields(
229238
fields: &Punctuated<InitializerField, Token![,]>,
230239
pinned: bool,
240+
generate_initialized_accessors: bool,
231241
data: &Ident,
232242
slot: &Ident,
233243
) -> TokenStream {
@@ -263,44 +273,45 @@ fn init_fields(
263273
unsafe { &mut (*#slot).#ident }
264274
}
265275
};
276+
let accessor = generate_initialized_accessors.then(|| {
277+
quote! {
278+
#(#cfgs)*
279+
#[allow(unused_variables)]
280+
let #ident = #accessor;
281+
}
282+
});
266283
quote! {
267284
#(#attrs)*
268285
{
269286
#value_prep
270287
// SAFETY: TODO
271288
unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) };
272289
}
273-
#(#cfgs)*
274-
#[allow(unused_variables)]
275-
let #ident = #accessor;
290+
#accessor
276291
}
277292
}
278293
InitializerKind::Init { ident, value, .. } => {
279294
// Again span for better diagnostics
280295
let init = format_ident!("init", span = value.span());
281-
if pinned {
296+
let (value_init, accessor) = if pinned {
282297
let project_ident = format_ident!("__project_{ident}");
283-
quote! {
284-
#(#attrs)*
285-
{
286-
let #init = #value;
298+
(
299+
quote! {
287300
// SAFETY:
288301
// - `slot` is valid, because we are inside of an initializer closure, we
289302
// return when an error/panic occurs.
290303
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
291304
// for `#ident`.
292305
unsafe { #data.#ident(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? };
293-
}
294-
#(#cfgs)*
295-
// SAFETY: TODO
296-
#[allow(unused_variables)]
297-
let #ident = unsafe { #data.#project_ident(&mut (*#slot).#ident) };
298-
}
306+
},
307+
quote! {
308+
// SAFETY: TODO
309+
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
310+
},
311+
)
299312
} else {
300-
quote! {
301-
#(#attrs)*
302-
{
303-
let #init = #value;
313+
(
314+
quote! {
304315
// SAFETY: `slot` is valid, because we are inside of an initializer
305316
// closure, we return when an error/panic occurs.
306317
unsafe {
@@ -309,12 +320,27 @@ fn init_fields(
309320
::core::ptr::addr_of_mut!((*#slot).#ident),
310321
)?
311322
};
312-
}
323+
},
324+
quote! {
325+
// SAFETY: TODO
326+
unsafe { &mut (*#slot).#ident }
327+
},
328+
)
329+
};
330+
let accessor = generate_initialized_accessors.then(|| {
331+
quote! {
313332
#(#cfgs)*
314-
// SAFETY: TODO
315333
#[allow(unused_variables)]
316-
let #ident = unsafe { &mut (*#slot).#ident };
334+
let #ident = #accessor;
335+
}
336+
});
337+
quote! {
338+
#(#attrs)*
339+
{
340+
let #init = #value;
341+
#value_init
317342
}
343+
#accessor
318344
}
319345
}
320346
InitializerKind::Code { block: value, .. } => quote! {
@@ -446,6 +472,10 @@ impl Parse for Initializer {
446472
if a.path().is_ident("default_error") {
447473
a.parse_args::<DefaultErrorAttribute>()
448474
.map(InitializerAttribute::DefaultError)
475+
} else if a.path().is_ident("disable_initialized_field_access") {
476+
a.meta
477+
.require_path_only()
478+
.map(|_| InitializerAttribute::DisableInitializedFieldAccess)
449479
} else {
450480
Err(syn::Error::new_spanned(a, "unknown initializer attribute"))
451481
}

0 commit comments

Comments
 (0)