lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <DFOJAWAGBPBD.1SKKFVK435H9O@garyguo.net>
Date: Wed, 14 Jan 2026 18:47:00 +0000
From: "Gary Guo" <gary@...yguo.net>
To: "Benno Lossin" <lossin@...nel.org>, "Gary Guo" <gary@...yguo.net>,
 "Miguel Ojeda" <ojeda@...nel.org>, "Boqun Feng" <boqun.feng@...il.com>,
 Björn Roy Baron <bjorn3_gh@...tonmail.com>, "Andreas
 Hindborg" <a.hindborg@...nel.org>, "Alice Ryhl" <aliceryhl@...gle.com>,
 "Trevor Gross" <tmgross@...ch.edu>, "Danilo Krummrich" <dakr@...nel.org>,
 "Fiona Behrens" <me@...enk.dev>, "Tamir Duberstein" <tamird@...il.com>,
 "Alban Kurti" <kurti@...icto.ai>
Cc: <linux-kernel@...r.kernel.org>, <rust-for-linux@...r.kernel.org>
Subject: Re: [PATCH v3 07/15] rust: pin-init: rewrite `#[pin_data]` using
 `syn`

On Wed Jan 14, 2026 at 6:18 PM GMT, Benno Lossin wrote:
> Rewrite the attribute macro `#[pin_data]` using `syn`. No functional
> changes intended aside from improved error messages on syntactic and
> semantical errors. For example if one forgets a comma at the end of a
> field:
>
>     #[pin_data]
>     struct Foo {
>         a: Box<Foo>
>         b: Box<Foo>
>     }
>
> The declarative macro reports the following errors:
>
>     error: expected `,`, or `}`, found `b`
>      --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16
>       |
>     5 |     a: Box<Foo>
>       |                ^ help: try adding a comma: `,`
>
>     error: recursion limit reached while expanding `$crate::__pin_data!`
>      --> tests/ui/compile-fail/pin_data/missing_comma.rs:3:1
>       |
>     3 | #[pin_data]
>       | ^^^^^^^^^^^
>       |
>       = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`)
>       = note: this error originates in the macro `$crate::__pin_data` which comes from the expansion of the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info)
>
> The new `syn` version reports:
>
>     error: expected `,`, or `}`, found `b`
>      --> tests/ui/compile-fail/pin_data/missing_comma.rs:5:16
>       |
>     5 |     a: Box<Foo>
>       |                ^ help: try adding a comma: `,`
>
>     error: expected `,`
>      --> tests/ui/compile-fail/pin_data/missing_comma.rs:6:5
>       |
>     6 |     b: Box<Foo>
>       |     ^
>
> Tested-by: Andreas Hindborg <a.hindborg@...nel.org>
> Signed-off-by: Benno Lossin <lossin@...nel.org>
> ---
> Changes in v3:
> * use DiagCtxt error handling
> Changes in v2:
> * improved error handling
> * fix clippy warnings
> * fix typos and variable names
> * collect the information about the pinned/not pinned fields only once
>   at the beginning
> ---
>  rust/pin-init/internal/src/helpers.rs  | 149 ------
>  rust/pin-init/internal/src/lib.rs      |   7 +-
>  rust/pin-init/internal/src/pin_data.rs | 633 ++++++++++++++++++++-----
>  rust/pin-init/src/macros.rs            | 574 ----------------------
>  4 files changed, 525 insertions(+), 838 deletions(-)
>  delete mode 100644 rust/pin-init/internal/src/helpers.rs
>
> diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs
> index 86a53b37cc66..fb4b8507c01d 100644
> --- a/rust/pin-init/internal/src/pin_data.rs
> +++ b/rust/pin-init/internal/src/pin_data.rs
> @@ -1,126 +1,535 @@
>  // SPDX-License-Identifier: Apache-2.0 OR MIT
>  
> -use crate::helpers::{parse_generics, Generics};
> -use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree};
> -use quote::quote;
> +use proc_macro2::TokenStream;
> +use quote::{format_ident, quote};
> +use syn::{
> +    parse::{End, Nothing, Parse},
> +    parse_quote, parse_quote_spanned,
> +    spanned::Spanned,
> +    visit_mut::VisitMut,
> +    Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause,
> +};
>  
> -pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
> -    // This proc-macro only does some pre-parsing and then delegates the actual parsing to
> -    // `pin_init::__pin_data!`.
> +use crate::diagnostics::{DiagCtxt, ErrorGuaranteed};
>  
> -    let (
> -        Generics {
> -            impl_generics,
> -            decl_generics,
> -            ty_generics,
> -        },
> -        rest,
> -    ) = parse_generics(input);
> -    // The struct definition might contain the `Self` type. Since `__pin_data!` will define a new
> -    // type with the same generics and bounds, this poses a problem, since `Self` will refer to the
> -    // new type as opposed to this struct definition. Therefore we have to replace `Self` with the
> -    // concrete name.
> -
> -    // Errors that occur when replacing `Self` with `struct_name`.
> -    let mut errs = TokenStream::new();
> -    // The name of the struct with ty_generics.
> -    let struct_name = rest
> -        .iter()
> -        .skip_while(|tt| !matches!(tt, TokenTree::Ident(i) if i == "struct"))
> -        .nth(1)
> -        .and_then(|tt| match tt {
> -            TokenTree::Ident(_) => {
> -                let tt = tt.clone();
> -                let mut res = vec![tt];
> -                if !ty_generics.is_empty() {
> -                    // We add this, so it is maximally compatible with e.g. `Self::CONST` which
> -                    // will be replaced by `StructName::<$generics>::CONST`.
> -                    res.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
> -                    res.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
> -                    res.push(TokenTree::Punct(Punct::new('<', Spacing::Alone)));
> -                    res.extend(ty_generics.iter().cloned());
> -                    res.push(TokenTree::Punct(Punct::new('>', Spacing::Alone)));
> -                }
> -                Some(res)
> +pub(crate) mod kw {
> +    syn::custom_keyword!(PinnedDrop);
> +}
> +
> +pub(crate) enum Args {
> +    Nothing(Nothing),
> +    #[allow(dead_code)]
> +    PinnedDrop(kw::PinnedDrop),
> +}
> +
> +impl Parse for Args {
> +    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
> +        let lh = input.lookahead1();
> +        if lh.peek(End) {
> +            input.parse().map(Self::Nothing)
> +        } else if lh.peek(kw::PinnedDrop) {
> +            let res = input.parse().map(Self::PinnedDrop)?;
> +            let lh = input.lookahead1();
> +            if lh.peek(End) {
> +                Ok(res)
> +            } else {
> +                Err(lh.error())
>              }

I believe that this `End` peek is unnecessary?

> -            _ => None,
> +        } else {
> +            Err(lh.error())
> +        }
> +    }
> +}
> +
> +pub(crate) fn pin_data(
> +    args: Args,
> +    input: Item,
> +    dcx: &mut DiagCtxt,
> +) -> Result<TokenStream, ErrorGuaranteed> {
> +    let mut struct_ = match input {
> +        Item::Struct(struct_) => struct_,
> +        Item::Enum(enum_) => {
> +            return Err(dcx.error(
> +                enum_.enum_token,
> +                "`#[pin_data]` only supports structs for now",
> +            ));
> +        }
> +        Item::Union(union) => {
> +            return Err(dcx.error(
> +                union.union_token,
> +                "`#[pin_data]` only supports structs for now",
> +            ));
> +        }
> +        rest => {
> +            return Err(dcx.error(
> +                rest,
> +                "`#[pin_data]` can only be applied to struct, enum and union definitions",
> +            ));
> +        }
> +    };
> +
> +    // The generics might contain the `Self` type. Since this macro will define a new type with the
> +    // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed
> +    // to this struct definition. Therefore we have to replace `Self` with the concrete name.
> +    let mut replacer = {
> +        let name = &struct_.ident;
> +        let (_, ty_generics, _) = struct_.generics.split_for_impl();
> +        SelfReplacer(parse_quote!(#name #ty_generics))
> +    };
> +    replacer.visit_generics_mut(&mut struct_.generics);
> +    replacer.visit_fields_mut(&mut struct_.fields);
> +
> +    let fields: Vec<(bool, Field)> = struct_
> +        .fields
> +        .iter()
> +        .cloned()
> +        .map(|mut field| {
> +            let pinned = is_field_structurally_pinned(&field);

There is only one occurance now so you can just check if a field is pinned by
looking at if `field.attrs.len()` is changed the line below (see my syn series
where Tamir and I had some discussion about something similar).

> +            field.attrs.retain(|a| !a.path().is_ident("pin"));
> +            (pinned, field)
>          })
> -        .unwrap_or_else(|| {
> -            // If we did not find the name of the struct then we will use `Self` as the replacement
> -            // and add a compile error to ensure it does not compile.
> -            errs.extend(
> -                "::core::compile_error!(\"Could not locate type name.\");"
> -                    .parse::<TokenStream>()
> -                    .unwrap(),
> +        .collect();
> +
> +    for (pinned, field) in &fields {
> +        if !pinned && is_phantom_pinned(&field.ty) {
> +            dcx.error(
> +                field,
> +                format!(
> +                    "The field `{}` of type `PhantomPinned` only has an effect \
> +                    if it has the `#[pin]` attribute",
> +                    field.ident.as_ref().unwrap(),
> +                ),
>              );
> -            "Self".parse::<TokenStream>().unwrap().into_iter().collect()
> -        });
> -    let impl_generics = impl_generics
> -        .into_iter()
> -        .flat_map(|tt| replace_self_and_deny_type_defs(&struct_name, tt, &mut errs))
> -        .collect::<Vec<_>>();
> -    let mut rest = rest
> -        .into_iter()
> -        .flat_map(|tt| {
> -            // We ignore top level `struct` tokens, since they would emit a compile error.
> -            if matches!(&tt, TokenTree::Ident(i) if i == "struct") {
> -                vec![tt]
> -            } else {
> -                replace_self_and_deny_type_defs(&struct_name, tt, &mut errs)
> +        }
> +    }
> +
> +    let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields);
> +    let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args);
> +    let projections =
> +        generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields);
> +    let the_pin_data =
> +        generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields);
> +
> +    strip_pin_annotations(&mut struct_);

I think this can be removed if you clone the fields after the `retain` -- or
even better, just avoid the clone of fields.

> +
> +    Ok(quote! {
> +        #struct_
> +        #projections
> +        // We put the rest into this const item, because it then will not be accessible to anything
> +        // outside.
> +        const _: () = {
> +            #the_pin_data
> +            #unpin_impl
> +            #drop_impl
> +        };
> +    })
> +}
> +
> +fn is_phantom_pinned(ty: &Type) -> bool {
> +    match ty {
> +        Type::Path(TypePath { qself: None, path }) => {
> +            // Cannot possibly refer to `PhantomPinned` (except alias, but that's on the user).
> +            if path.segments.len() > 3 {
> +                return false;
>              }
> -        })
> -        .collect::<Vec<_>>();
> -    // This should be the body of the struct `{...}`.
> -    let last = rest.pop();
> -    let mut quoted = quote!(::pin_init::__pin_data! {
> -        parse_input:
> -        @args(#args),
> -        @sig(#(#rest)*),
> -        @impl_generics(#(#impl_generics)*),
> -        @ty_generics(#(#ty_generics)*),
> -        @decl_generics(#(#decl_generics)*),
> -        @body(#last),
> -    });
> -    quoted.extend(errs);
> -    quoted
> +            // If there is a `::`, then the path needs to be `::core::marker::PhantomPinned` or
> +            // `::std::marker::PhantomPinned`.
> +            if path.leading_colon.is_some() && path.segments.len() != 3 {
> +                return false;
> +            }
> +            let expected: Vec<&[&str]> = vec![&["PhantomPinned"], &["marker"], &["core", "std"]];
> +            for (actual, expected) in path.segments.iter().rev().zip(expected) {
> +                if !actual.arguments.is_empty() || expected.iter().all(|e| actual.ident != e) {
> +                    return false;
> +                }
> +            }
> +            true
> +        }
> +        _ => false,
> +    }
>  }
>  
> -/// Replaces `Self` with `struct_name` and errors on `enum`, `trait`, `struct` `union` and `impl`
> -/// keywords.
> -///
> -/// The error is appended to `errs` to allow normal parsing to continue.
> -fn replace_self_and_deny_type_defs(
> -    struct_name: &Vec<TokenTree>,
> -    tt: TokenTree,
> -    errs: &mut TokenStream,
> -) -> Vec<TokenTree> {
> -    match tt {
> -        TokenTree::Ident(ref i)
> -            if i == "enum" || i == "trait" || i == "struct" || i == "union" || i == "impl" =>
> +fn is_field_structurally_pinned(field: &Field) -> bool {
> +    field.attrs.iter().any(|a| a.path().is_ident("pin"))
> +}
> +
> +fn generate_unpin_impl(
> +    ident: &Ident,
> +    generics: &Generics,
> +    fields: &[(bool, Field)],
> +) -> TokenStream {
> +    let generics_with_pin_lt = {
> +        let mut g = generics.clone();
> +        g.params.insert(0, parse_quote!('__pin));
> +        let _ = g.make_where_clause();
> +        g
> +    };
> +    let (
> +        impl_generics_with_pin_lt,
> +        ty_generics_with_pin_lt,
> +        Some(WhereClause {
> +            where_token,
> +            predicates,
> +        }),
> +    ) = generics_with_pin_lt.split_for_impl()
> +    else {
> +        unreachable!()
> +    };
> +    let (_, ty_generics, _) = generics.split_for_impl();
> +    let pinned_fields = fields.iter().filter_map(|(b, f)| b.then_some(f));
> +    quote! {
> +        // This struct will be used for the unpin analysis. It is needed, because only structurally
> +        // pinned fields are relevant whether the struct should implement `Unpin`.
> +        #[allow(dead_code)] // The fields below are never used.
> +        struct __Unpin #generics_with_pin_lt
> +        #where_token
> +            #predicates
>          {
> -            errs.extend(
> -                format!(
> -                    "::core::compile_error!(\"Cannot use `{i}` inside of struct definition with \
> -                        `#[pin_data]`.\");"
> +            __phantom_pin: ::core::marker::PhantomData<fn(&'__pin ()) -> &'__pin ()>,
> +            __phantom: ::core::marker::PhantomData<
> +                fn(#ident #ty_generics) -> #ident #ty_generics
> +            >,
> +            #(#pinned_fields),*
> +        }
> +
> +        #[doc(hidden)]
> +        impl #impl_generics_with_pin_lt ::core::marker::Unpin for #ident #ty_generics
> +        #where_token
> +            __Unpin #ty_generics_with_pin_lt: ::core::marker::Unpin,
> +            #predicates
> +        {}
> +    }
> +}
> +
> +fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenStream {
> +    let (impl_generics, ty_generics, whr) = generics.split_for_impl();
> +    let has_pinned_drop = matches!(args, Args::PinnedDrop(_));
> +    // We need to disallow normal `Drop` implementation, the exact behavior depends on whether
> +    // `PinnedDrop` was specified in `args`.
> +    if has_pinned_drop {
> +        // When `PinnedDrop` was specified we just implement `Drop` and delegate.
> +        quote! {
> +            impl #impl_generics ::core::ops::Drop for #ident #ty_generics
> +                #whr
> +            {
> +                fn drop(&mut self) {
> +                    // SAFETY: Since this is a destructor, `self` will not move after this function
> +                    // terminates, since it is inaccessible.
> +                    let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) };
> +                    // SAFETY: Since this is a drop function, we can create this token to call the
> +                    // pinned destructor of this type.
> +                    let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() };
> +                    ::pin_init::PinnedDrop::drop(pinned, token);
> +                }
> +            }
> +        }
> +    } else {
> +        // When no `PinnedDrop` was specified, then we have to prevent implementing drop.
> +        quote! {
> +            // We prevent this by creating a trait that will be implemented for all types implementing
> +            // `Drop`. Additionally we will implement this trait for the struct leading to a conflict,
> +            // if it also implements `Drop`
> +            trait MustNotImplDrop {}
> +            #[expect(drop_bounds)]
> +            impl<T: ::core::ops::Drop> MustNotImplDrop for T {}
> +            impl #impl_generics MustNotImplDrop for #ident #ty_generics
> +                #whr
> +            {}
> +            // We also take care to prevent users from writing a useless `PinnedDrop` implementation.
> +            // They might implement `PinnedDrop` correctly for the struct, but forget to give
> +            // `PinnedDrop` as the parameter to `#[pin_data]`.
> +            #[expect(non_camel_case_types)]
> +            trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {}
> +            impl<T: ::pin_init::PinnedDrop>
> +                UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {}
> +            impl #impl_generics
> +                UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics
> +                #whr
> +            {}
> +        }
> +    }
> +}
> +
> +fn generate_projections(
> +    vis: &Visibility,
> +    ident: &Ident,
> +    generics: &Generics,
> +    fields: &[(bool, Field)],
> +) -> TokenStream {
> +    let (impl_generics, ty_generics, _) = generics.split_for_impl();
> +    let mut generics = generics.clone();
> +    generics.params.insert(0, parse_quote!('__pin));
> +    let (_, ty_generics_with_pin_lt, whr) = generics.split_for_impl();

This part uses split-modify-split while `generate_unpin_impl` uses
clone-then-split. I am fine with either but please make them consistent.

Best,
Gary

> +    let projection = format_ident!("{ident}Projection");
> +    let this = format_ident!("this");
> +
> +    let (fields_decl, fields_proj) = collect_tuple(fields.iter().map(
> +        |(
> +            pinned,
> +            Field {
> +                vis,
> +                ident,
> +                ty,
> +                attrs,
> +                ..
> +            },
> +        )| {
> +            let mut attrs = attrs.clone();
> +            attrs.retain(|a| !a.path().is_ident("pin"));
> +            let mut no_doc_attrs = attrs.clone();
> +            no_doc_attrs.retain(|a| !a.path().is_ident("doc"));
> +            let ident = ident
> +                .as_ref()
> +                .expect("only structs with named fields are supported");
> +            if *pinned {
> +                (
> +                    quote!(
> +                        #(#attrs)*
> +                        #vis #ident: ::core::pin::Pin<&'__pin mut #ty>,
> +                    ),
> +                    quote!(
> +                        #(#no_doc_attrs)*
> +                        // SAFETY: this field is structurally pinned.
> +                        #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) },
> +                    ),
>                  )
> -                .parse::<TokenStream>()
> -                .unwrap()
> -                .into_iter()
> -                .map(|mut tok| {
> -                    tok.set_span(tt.span());
> -                    tok
> -                }),
> -            );
> -            vec![tt]
> -        }
> -        TokenTree::Ident(i) if i == "Self" => struct_name.clone(),
> -        TokenTree::Literal(_) | TokenTree::Punct(_) | TokenTree::Ident(_) => vec![tt],
> -        TokenTree::Group(g) => vec![TokenTree::Group(Group::new(
> -            g.delimiter(),
> -            g.stream()
> -                .into_iter()
> -                .flat_map(|tt| replace_self_and_deny_type_defs(struct_name, tt, errs))
> -                .collect(),
> -        ))],
> +            } else {
> +                (
> +                    quote!(
> +                        #(#attrs)*
> +                        #vis #ident: &'__pin mut #ty,
> +                    ),
> +                    quote!(
> +                        #(#no_doc_attrs)*
> +                        #ident: &mut #this.#ident,
> +                    ),
> +                )
> +            }
> +        },
> +    ));
> +    let structurally_pinned_fields_docs = fields
> +        .iter()
> +        .filter_map(|(pinned, field)| pinned.then_some(field))
> +        .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap()));
> +    let not_structurally_pinned_fields_docs = fields
> +        .iter()
> +        .filter_map(|(pinned, field)| (!pinned).then_some(field))
> +        .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap()));
> +    let docs = format!(" Pin-projections of [`{ident}`]");
> +    quote! {
> +        #[doc = #docs]
> +        #[allow(dead_code)]
> +        #[doc(hidden)]
> +        #vis struct #projection #generics {
> +            #(#fields_decl)*
> +            ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>,
> +        }
> +
> +        impl #impl_generics #ident #ty_generics
> +            #whr
> +        {
> +            /// Pin-projects all fields of `Self`.
> +            ///
> +            /// These fields are structurally pinned:
> +            #(#[doc = #structurally_pinned_fields_docs])*
> +            ///
> +            /// These fields are **not** structurally pinned:
> +            #(#[doc = #not_structurally_pinned_fields_docs])*
> +            #[inline]
> +            #vis fn project<'__pin>(
> +                self: ::core::pin::Pin<&'__pin mut Self>,
> +            ) -> #projection #ty_generics_with_pin_lt {
> +                // SAFETY: we only give access to `&mut` for fields not structurally pinned.
> +                let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
> +                #projection {
> +                    #(#fields_proj)*
> +                    ___pin_phantom_data: ::core::marker::PhantomData,
> +                }
> +            }
> +        }
> +    }
> +}

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ