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>] [day] [month] [year] [list]
Message-ID: <20250304225536.2033853-3-benno.lossin@proton.me>
Date: Tue, 04 Mar 2025 22:57:00 +0000
From: Benno Lossin <benno.lossin@...ton.me>
To: Benno Lossin <benno.lossin@...ton.me>, Miguel Ojeda <ojeda@...nel.org>, Alex Gaynor <alex.gaynor@...il.com>, Boqun Feng <boqun.feng@...il.com>, Gary Guo <gary@...yguo.net>, 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>
Cc: linux-kernel@...r.kernel.org, rust-for-linux@...r.kernel.org
Subject: [RFC PATCH 2/6] rust: pin-init: internal: add syn version of `pinned_drop` proc macro

Implement the `pinned_drop` attribute macro using syn to simplify
parsing by not going through an additional declarative macro.
This not only simplifies the code by a lot, increasing maintainability
and making it easier to implement new features. But also improves the
user experience by improving the error messages one gets when giving
incorrect inputs to the macro.
For example in this piece of code, there is a `drop` function missing:

    use pin_init::*;

    #[pin_data(PinnedDrop)]
    struct Foo {}

    #[pinned_drop]
    impl PinnedDrop for Foo {}

But this error is communicated very poorly in the declarative macro
version:

    error: no rules expected `)`
      |
    6 | #[pinned_drop]
      | ^^^^^^^^^^^^^^ no rules expected this token in macro call
      |
    note: while trying to match keyword `fn`
     --> src/macros.rs
      |
      |             fn drop($($sig:tt)*) {
      |             ^^
      = note: this error originates in the attribute macro `pinned_drop` (in Nightly builds, run with -Z macro-backtrace for more info)

    error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied
      |
    3 | #[pin_data(PinnedDrop)]
      | ^^^^^^^^^^^^^^^^^^^^^^^
      | |
      | the trait `PinnedDrop` is not implemented for `Foo`
      | required by a bound introduced by this call
      |
      = 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 syn version is much more concise and right to the point:

    error[E0046]: not all trait items implemented, missing: `drop`
      |
    7 | impl PinnedDrop for Foo {}
      | ^^^^^^^^^^^^^^^^^^^^^^^ missing `drop` in implementation
      |
      = help: implement the missing item: `fn drop(self: Pin<&mut Self>, _: OnlyCallFromDrop) { todo!() }`

Another example is the following:

    use pin_init::*;
    use std::pin::Pin;

    #[pin_data(PinnedDrop)]
    struct Foo {}

    #[pinned_drop]
    impl PinnedDrop for Foo {
        fn drop(self: Pin<&mut Self>) {}

        const BAZ: usize = 0;
    }

It produces this error in the declarative macro version:

    error: no rules expected keyword `const`
       |
    10 |     const BAZ: usize = 0;
       |     ^^^^^ no rules expected this token in macro call
       |
    note: while trying to match `)`
      --> src/macros.rs
       |
       |         ),
       |         ^

    error[E0277]: the trait bound `Foo: PinnedDrop` is not satisfied
      |
    3 | #[pin_data(PinnedDrop)]
      | ^^^^^^^^^^^^^^^^^^^^^^^
      | |
      | the trait `PinnedDrop` is not implemented for `Foo`
      | required by a bound introduced by this call
      |
      = 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)

In the syn version, we get instead:

    error[E0438]: const `BAZ` is not a member of trait `pinned_init::PinnedDrop`
       |
    11 |     const BAZ: usize = 0;
       |     ^^^^^^^^^^^^^^^^^^^^^ not a member of trait `pinned_init::PinnedDrop`

The syn version is only enabled in the user-space version and disabled
in the kernel until syn becomes available there.

Signed-off-by: Benno Lossin <benno.lossin@...ton.me>
---
 rust/pin-init/internal/src/lib.rs             |  4 +
 rust/pin-init/internal/src/syn_pinned_drop.rs | 77 +++++++++++++++++++
 2 files changed, 81 insertions(+)
 create mode 100644 rust/pin-init/internal/src/syn_pinned_drop.rs

diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs
index 5019efe22662..761fbf8b9c68 100644
--- a/rust/pin-init/internal/src/lib.rs
+++ b/rust/pin-init/internal/src/lib.rs
@@ -29,10 +29,14 @@
 
 mod helpers;
 mod pin_data;
+#[cfg(kernel)]
 mod pinned_drop;
 #[cfg(kernel)]
 mod zeroable;
 
+#[cfg(not(kernel))]
+#[path = "syn_pinned_drop.rs"]
+mod pinned_drop;
 #[cfg(not(kernel))]
 #[path = "syn_zeroable.rs"]
 mod zeroable;
diff --git a/rust/pin-init/internal/src/syn_pinned_drop.rs b/rust/pin-init/internal/src/syn_pinned_drop.rs
new file mode 100644
index 000000000000..1e70ee29adba
--- /dev/null
+++ b/rust/pin-init/internal/src/syn_pinned_drop.rs
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+    parse_macro_input, parse_quote, spanned::Spanned, Error, ImplItem, ImplItemFn, ItemImpl,
+    Result, Token,
+};
+
+pub(crate) fn pinned_drop(
+    args: proc_macro::TokenStream,
+    input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    parse_macro_input!(args as syn::parse::Nothing);
+    do_impl(parse_macro_input!(input as ItemImpl))
+        .unwrap_or_else(|e| e.into_compile_error())
+        .into()
+}
+
+fn do_impl(mut input: ItemImpl) -> Result<TokenStream> {
+    let Some((_, path, _)) = &mut input.trait_ else {
+        return Err(Error::new_spanned(
+            input,
+            "expected an `impl` block implementing `PinnedDrop`",
+        ));
+    };
+    if !is_pinned_drop(path) {
+        return Err(Error::new_spanned(
+            input,
+            "expected an `impl` block implementing `PinnedDrop`",
+        ));
+    }
+    let mut error = None;
+    if let Some(unsafety) = input.unsafety.take() {
+        error = Some(
+            Error::new_spanned(
+                unsafety,
+                "implementing the trait `PinnedDrop` via `#[pinned_drop]` is not unsafe",
+            )
+            .into_compile_error(),
+        );
+    }
+    input.unsafety = Some(Token![unsafe](input.impl_token.span()));
+    if path.segments.len() != 2 {
+        path.segments.insert(0, parse_quote!(pin_init));
+    }
+    path.leading_colon.get_or_insert(Token![::](path.span()));
+    for item in &mut input.items {
+        match item {
+            ImplItem::Fn(ImplItemFn { sig, .. }) if sig.ident == "drop" => {
+                sig.inputs
+                    .push(parse_quote!(_: ::pin_init::__internal::OnlyCallFromDrop));
+            }
+            _ => {}
+        }
+    }
+    Ok(quote! {
+        #error
+        #input
+    })
+}
+
+fn is_pinned_drop(path: &syn::Path) -> bool {
+    if path.segments.len() > 2 {
+        return false;
+    }
+    // If there is a `::`, then the path needs to be `::pin_init::PinnedDrop`.
+    if path.leading_colon.is_some() && path.segments.len() != 2 {
+        return false;
+    }
+    for (actual, expected) in path.segments.iter().rev().zip(["PinnedDrop", "pin_init"]) {
+        if actual.ident != expected {
+            return false;
+        }
+    }
+    true
+}
-- 
2.48.1



Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ