Skip to content

Commit ceca298

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] Tested-by: Andreas Hindborg <a.hindborg@kernel.org> Reviewed-by: Gary Guo <gary@garyguo.net> Signed-off-by: Benno Lossin <lossin@kernel.org>
1 parent d26732e commit ceca298

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
@@ -62,6 +62,7 @@ impl InitializerKind {
6262

6363
enum InitializerAttribute {
6464
DefaultError(DefaultErrorAttribute),
65+
DisableInitializedFieldAccess,
6566
}
6667

6768
struct DefaultErrorAttribute {
@@ -85,7 +86,6 @@ pub(crate) fn expand(
8586
let error = error.map_or_else(
8687
|| {
8788
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
88-
#[expect(irrefutable_let_patterns)]
8989
if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr {
9090
Some(ty.clone())
9191
} else {
@@ -145,7 +145,15 @@ pub(crate) fn expand(
145145
};
146146
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
147147
let data = Ident::new("__data", Span::mixed_site());
148-
let init_fields = init_fields(&fields, pinned, &data, &slot);
148+
let init_fields = init_fields(
149+
&fields,
150+
pinned,
151+
!attrs
152+
.iter()
153+
.any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)),
154+
&data,
155+
&slot,
156+
);
149157
let field_check = make_field_check(&fields, init_kind, &path);
150158
Ok(quote! {{
151159
// 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
228236
fn init_fields(
229237
fields: &Punctuated<InitializerField, Token![,]>,
230238
pinned: bool,
239+
generate_initialized_accessors: bool,
231240
data: &Ident,
232241
slot: &Ident,
233242
) -> TokenStream {
@@ -263,44 +272,45 @@ fn init_fields(
263272
unsafe { &mut (*#slot).#ident }
264273
}
265274
};
275+
let accessor = generate_initialized_accessors.then(|| {
276+
quote! {
277+
#(#cfgs)*
278+
#[allow(unused_variables)]
279+
let #ident = #accessor;
280+
}
281+
});
266282
quote! {
267283
#(#attrs)*
268284
{
269285
#value_prep
270286
// SAFETY: TODO
271287
unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) };
272288
}
273-
#(#cfgs)*
274-
#[allow(unused_variables)]
275-
let #ident = #accessor;
289+
#accessor
276290
}
277291
}
278292
InitializerKind::Init { ident, value, .. } => {
279293
// Again span for better diagnostics
280294
let init = format_ident!("init", span = value.span());
281-
if pinned {
295+
let (value_init, accessor) = if pinned {
282296
let project_ident = format_ident!("__project_{ident}");
283-
quote! {
284-
#(#attrs)*
285-
{
286-
let #init = #value;
297+
(
298+
quote! {
287299
// SAFETY:
288300
// - `slot` is valid, because we are inside of an initializer closure, we
289301
// return when an error/panic occurs.
290302
// - We also use `#data` to require the correct trait (`Init` or `PinInit`)
291303
// for `#ident`.
292304
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-
}
305+
},
306+
quote! {
307+
// SAFETY: TODO
308+
unsafe { #data.#project_ident(&mut (*#slot).#ident) }
309+
},
310+
)
299311
} else {
300-
quote! {
301-
#(#attrs)*
302-
{
303-
let #init = #value;
312+
(
313+
quote! {
304314
// SAFETY: `slot` is valid, because we are inside of an initializer
305315
// closure, we return when an error/panic occurs.
306316
unsafe {
@@ -309,12 +319,27 @@ fn init_fields(
309319
::core::ptr::addr_of_mut!((*#slot).#ident),
310320
)?
311321
};
312-
}
322+
},
323+
quote! {
324+
// SAFETY: TODO
325+
unsafe { &mut (*#slot).#ident }
326+
},
327+
)
328+
};
329+
let accessor = generate_initialized_accessors.then(|| {
330+
quote! {
313331
#(#cfgs)*
314-
// SAFETY: TODO
315332
#[allow(unused_variables)]
316-
let #ident = unsafe { &mut (*#slot).#ident };
333+
let #ident = #accessor;
334+
}
335+
});
336+
quote! {
337+
#(#attrs)*
338+
{
339+
let #init = #value;
340+
#value_init
317341
}
342+
#accessor
318343
}
319344
}
320345
InitializerKind::Code { block: value, .. } => quote! {
@@ -446,6 +471,10 @@ impl Parse for Initializer {
446471
if a.path().is_ident("default_error") {
447472
a.parse_args::<DefaultErrorAttribute>()
448473
.map(InitializerAttribute::DefaultError)
474+
} else if a.path().is_ident("disable_initialized_field_access") {
475+
a.meta
476+
.require_path_only()
477+
.map(|_| InitializerAttribute::DisableInitializedFieldAccess)
449478
} else {
450479
Err(syn::Error::new_spanned(a, "unknown initializer attribute"))
451480
}

0 commit comments

Comments
 (0)