diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b248b3..e52a5bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Automatically scan the initializer body in `[pin_]init!` to conditionally generate field + accessors; this reduces stack bloat and prevents alignment errors on packed structs by + only creating references when a field is actually used. - `#[pin_data]` now generates a `*Projection` struct similar to the `pin-project` crate. - Add initializer code blocks to `[try_][pin_]init!` macros: make initializer macros accept any number of `_: {/* arbitrary code */},` & make them run the @@ -22,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make the `[try_][pin_]init!` macros expose initialized fields via a `let` binding as `&mut T` or `Pin<&mut T>` for later fields. - Rewrote all proc-macros (`[pin_]init!`, `#[pin_data]`, `#[pinned_drop]`, - `derive([Maybe]Zeroable)`), using `syn` with better diagnostics. + `derive([Maybe]Zeroable)`), using `syn` with better diagnostics. - `derive([Maybe]Zeroable)` now support tuple structs. - `[pin_]init!` now supports attributes on fields (such as `#[cfg(...)]`). - Add a `#[default_error()]` attribute to `[pin_]init!` to override the diff --git a/internal/Cargo.toml b/internal/Cargo.toml index d3af5351..1d7d5dd0 100644 --- a/internal/Cargo.toml +++ b/internal/Cargo.toml @@ -15,7 +15,7 @@ proc-macro = true [dependencies] quote = "1.0" proc-macro2 = "1.0" -syn = { version = "2.0.86", features = ["full", "parsing", "visit-mut"] } +syn = { version = "2.0.86", features = ["full", "parsing", "visit", "visit-mut"] } [build-dependencies] rustc_version = "0.4" diff --git a/internal/src/init.rs b/internal/src/init.rs index 42936f91..f3ce728b 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -2,17 +2,34 @@ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; +use std::collections::HashSet; use syn::{ braced, parse::{End, Parse}, parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, + visit::{self, Visit}, + Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +struct AccessorScanner<'a> { + field_names: HashSet<&'a Ident>, + used_fields: HashSet, +} + +impl<'a> Visit<'_> for AccessorScanner<'a> { + fn visit_ident(&mut self, i: &Ident) { + if self.field_names.contains(i) { + self.used_fields.insert(i.clone()); + } + visit::visit_ident(self, i); + } +} + pub(crate) struct Initializer { attrs: Vec, this: Option, @@ -145,6 +162,27 @@ 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 mut field_names = HashSet::new(); + for field in &fields { + if let Some(ident) = field.kind.ident() { + field_names.insert(ident); + } + } + let mut scanner = AccessorScanner { + field_names, + used_fields: HashSet::new(), + }; + for field in &fields { + match &field.kind { + InitializerKind::Value { + value: Some((_, expr)), + .. + } => scanner.visit_expr(expr), + InitializerKind::Init { value, .. } => scanner.visit_expr(value), + InitializerKind::Code { block, .. } => scanner.visit_block(block), + _ => {} + } + } let init_fields = init_fields( &fields, pinned, @@ -153,6 +191,7 @@ pub(crate) fn expand( .any(|attr| matches!(attr, InitializerAttribute::DisableInitializedFieldAccess)), &data, &slot, + &scanner.used_fields, ); let field_check = make_field_check(&fields, init_kind, &path); Ok(quote! {{ @@ -239,6 +278,7 @@ fn init_fields( generate_initialized_accessors: bool, data: &Ident, slot: &Ident, + used_fields: &HashSet, ) -> TokenStream { let mut guards = vec![]; let mut guard_attrs = vec![]; @@ -272,13 +312,14 @@ fn init_fields( unsafe { &mut (*#slot).#ident } } }; - let accessor = generate_initialized_accessors.then(|| { - quote! { - #(#cfgs)* - #[allow(unused_variables)] - let #ident = #accessor; - } - }); + let accessor = (generate_initialized_accessors && used_fields.contains(ident)) + .then(|| { + quote! { + #(#cfgs)* + #[allow(unused_variables)] + let #ident = #accessor; + } + }); quote! { #(#attrs)* { @@ -326,13 +367,14 @@ fn init_fields( }, ) }; - let accessor = generate_initialized_accessors.then(|| { - quote! { - #(#cfgs)* - #[allow(unused_variables)] - let #ident = #accessor; - } - }); + let accessor = (generate_initialized_accessors && used_fields.contains(ident)) + .then(|| { + quote! { + #(#cfgs)* + #[allow(unused_variables)] + let #ident = #accessor; + } + }); quote! { #(#attrs)* { diff --git a/tests/packed.rs b/tests/packed.rs new file mode 100644 index 00000000..6178257f --- /dev/null +++ b/tests/packed.rs @@ -0,0 +1,30 @@ +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +use core::convert::Infallible; +use pin_init::{pin_data, pin_init}; + +#[pin_data] +struct MagicStruct { + a: u32, + b: u32, +} + +#[test] +fn test_scanner_skips_unused() { + let _ = pin_init!(MagicStruct { + a: 1, + b: 2, + } ? Infallible); +} + +#[test] +fn test_scanner_finds_used() { + let _ = pin_init!(MagicStruct { + a: 1, + b: 2, + _: { + let _ = b; + } + } ? Infallible); +} diff --git a/tests/ui/compile-fail/init/packed_struct.rs b/tests/ui/compile-fail/init/packed_struct.rs deleted file mode 100644 index d8b1eee1..00000000 --- a/tests/ui/compile-fail/init/packed_struct.rs +++ /dev/null @@ -1,11 +0,0 @@ -use pin_init::*; - -#[repr(C, packed)] -struct Foo { - a: i8, - b: i32, -} - -fn main() { - let _ = init!(Foo { a: -42, b: 42 }); -} diff --git a/tests/ui/compile-fail/init/packed_struct.stderr b/tests/ui/compile-fail/init/packed_struct.stderr deleted file mode 100644 index 1fc91d06..00000000 --- a/tests/ui/compile-fail/init/packed_struct.stderr +++ /dev/null @@ -1,10 +0,0 @@ -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)