xref: /linux/rust/zerocopy-derive/lib.rs (revision 7c306f00835a237a70024a8f968dbc3bc8db8fcf)
1 // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
2 //
3 // Copyright 2019 The Fuchsia Authors
4 //
5 // Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
6 // <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
7 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
8 // This file may not be copied, modified, or distributed except according to
9 // those terms.
10 
11 //! Derive macros for [zerocopy]'s traits.
12 //!
13 //! [zerocopy]: https://docs.rs/zerocopy
14 
15 // Sometimes we want to use lints which were added after our MSRV.
16 // `unknown_lints` is `warn` by default and we deny warnings in CI, so without
17 // this attribute, any unknown lint would cause a CI failure when testing with
18 // our MSRV.
19 #![allow(unknown_lints)]
20 #![deny(renamed_and_removed_lints)]
21 #![deny(
22     clippy::all,
23     clippy::missing_safety_doc,
24     clippy::multiple_unsafe_ops_per_block,
25     clippy::undocumented_unsafe_blocks
26 )]
27 // We defer to own discretion on type complexity.
28 #![allow(clippy::type_complexity)]
29 // Inlining format args isn't supported on our MSRV.
30 #![allow(clippy::uninlined_format_args)]
31 // `cargo-zerocopy` supplies this cfg for pinned-nightly tests. During UI tests,
32 // `testutil::UiTestRunner` explicitly supplies it to the host-built proc macro;
33 // ordinary `RUSTFLAGS` are not sufficient when Cargo receives `--target`.
34 #![cfg_attr(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS, feature(proc_macro_def_site))]
35 #![deny(
36     rustdoc::bare_urls,
37     rustdoc::broken_intra_doc_links,
38     rustdoc::invalid_codeblock_attributes,
39     rustdoc::invalid_html_tags,
40     rustdoc::invalid_rust_codeblocks,
41     rustdoc::missing_crate_level_docs,
42     rustdoc::private_intra_doc_links
43 )]
44 #![recursion_limit = "128"]
45 
46 macro_rules! ident {
47     (($fmt:literal $(, $arg:expr)*), $span:expr) => {
48         syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span)
49     };
50 }
51 
52 mod derive;
53 mod invariant;
54 #[cfg(test)]
55 mod output_tests;
56 mod repr;
57 mod util;
58 
59 use syn::{DeriveInput, Error};
60 
61 use crate::util::*;
62 
63 // FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be
64 // made better if we could add multiple lines of error output like this:
65 //
66 // error: unsupported representation
67 //   --> enum.rs:28:8
68 //    |
69 // 28 | #[repr(transparent)]
70 //    |
71 // help: required by the derive of FromBytes
72 //
73 // Instead, we have more verbose error messages like "unsupported representation
74 // for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
75 //
76 // This will probably require Span::error
77 // (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
78 // which is currently unstable. Revisit this once it's stable.
79 
80 /// Defines a derive function named `$outer` which parses its input
81 /// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
82 ///
83 /// Note that the separate `$outer` parameter is required - proc macro functions
84 /// are currently required to live at the crate root, and so the caller must
85 /// specify the name in order to avoid name collisions.
86 macro_rules! derive {
87     ($(#[$attr:meta])* $trait:ident => $outer:ident => $inner:path) => {
88         $(#[$attr])*
89         #[proc_macro_derive($trait, attributes(zerocopy))]
90         pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
91             let ast = syn::parse_macro_input!(ts as DeriveInput);
92             let ctx = match Ctx::try_from_derive_input(ast) {
93                 Ok(ctx) => ctx,
94                 Err(e) => return e.into_compile_error().into(),
95             };
96             let ts = $inner(&ctx, Trait::$trait).into_ts();
97             // Apply generated-code lint allowances as a backstop, except
98             // around caller-authored invariant expressions.
99             let ts = if matches!(Trait::$trait, Trait::TryFromBytes) {
100                 ctx.const_block([Some(ts)])
101             } else {
102                 const_block([Some(ts)])
103             };
104             #[cfg(test)]
105             crate::util::testutil::check_hygiene(ts.clone());
106             ts.into()
107         }
108     };
109 }
110 
111 trait IntoTokenStream {
112     fn into_ts(self) -> proc_macro2::TokenStream;
113 }
114 
115 impl IntoTokenStream for proc_macro2::TokenStream {
116     fn into_ts(self) -> proc_macro2::TokenStream {
117         self
118     }
119 }
120 
121 impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> {
122     fn into_ts(self) -> proc_macro2::TokenStream {
123         match self {
124             Ok(ts) => ts,
125             Err(err) => err.to_compile_error(),
126         }
127     }
128 }
129 
130 derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive);
131 derive!(Immutable => derive_immutable => crate::derive::derive_immutable);
132 derive!(#[doc(hidden)] Project => derive_project => crate::derive::project::derive);
133 derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes);
134 derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros);
135 derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes);
136 derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes);
137 derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned);
138 derive!(ByteHash => derive_hash => crate::derive::derive_hash);
139 derive!(ByteEq => derive_eq => crate::derive::derive_eq);
140 derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);
141 
142 /// Generates a struct whose two identically-printed field types resolve to
143 /// different types. This is used to test that derives preserve type identity
144 /// across macro hygiene contexts.
145 #[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
146 #[doc(hidden)]
147 #[proc_macro]
148 pub fn __test_hygienically_mixed_into_bytes(
149     _input: proc_macro::TokenStream,
150 ) -> proc_macro::TokenStream {
151     use proc_macro::{Group, Ident, Span, TokenStream, TokenTree};
152 
153     fn rewrite(input: TokenStream) -> TokenStream {
154         input
155             .into_iter()
156             .map(|token| match token {
157                 TokenTree::Ident(ident) if ident.to_string() == "CallT" => {
158                     TokenTree::Ident(Ident::new("T", Span::call_site()))
159                 }
160                 TokenTree::Ident(ident) if ident.to_string() == "DefT" => {
161                     TokenTree::Ident(Ident::new("T", Span::def_site()))
162                 }
163                 TokenTree::Group(group) => {
164                     let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream()));
165                     rewritten.set_span(group.span());
166                     TokenTree::Group(rewritten)
167                 }
168                 token => token,
169             })
170             .collect()
171     }
172 
173     rewrite(
174         "#[derive(zerocopy_renamed::IntoBytes)] \
175          #[zerocopy(crate = \"zerocopy_renamed\")] \
176          #[repr(C)] \
177          struct IntoBytes15<DefT>(CallT, DefT);"
178             .parse()
179             .expect("test input must parse"),
180     )
181 }
182 
183 /// Constructs an invariant whose field declaration and reference have different
184 /// syntax contexts, while preserving all other caller tokens.
185 #[doc(hidden)]
186 #[proc_macro]
187 pub fn __test_hygienically_mixed_invariant(
188     input: proc_macro::TokenStream,
189 ) -> proc_macro::TokenStream {
190     // Cross-compilation does not necessarily pass the nightly test cfg to
191     // host proc macros. Omit the fixture and its test together in that case.
192     #[cfg(not(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS))]
193     {
194         let _ = input;
195         proc_macro::TokenStream::new()
196     }
197     #[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
198     {
199         use proc_macro::{Group, Ident, Span, TokenStream, TokenTree};
200 
201         fn rewrite(input: TokenStream) -> TokenStream {
202             input
203                 .into_iter()
204                 .map(|token| match token {
205                     TokenTree::Ident(ident) if ident.to_string() == "DefField" => {
206                         TokenTree::Ident(Ident::new("field", Span::def_site()))
207                     }
208                     TokenTree::Ident(ident) if ident.to_string() == "CallField" => {
209                         TokenTree::Ident(Ident::new("field", Span::call_site()))
210                     }
211                     TokenTree::Group(group) => {
212                         let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream()));
213                         rewritten.set_span(group.span());
214                         TokenTree::Group(rewritten)
215                     }
216                     token => token,
217                 })
218                 .collect()
219         }
220 
221         rewrite(input)
222     }
223 }
224 
225 #[cfg_attr(not(zerocopy_unstable_linux), doc(hidden))]
226 #[proc_macro_derive(most_traits, attributes(zerocopy))]
227 pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
228     let ast = syn::parse_macro_input!(ts as DeriveInput);
229     let ctx = match Ctx::try_from_derive_input(ast) {
230         Ok(ctx) => ctx,
231         Err(e) => return e.into_compile_error().into(),
232     }
233     .skip_on_error();
234 
235     // top-level traits for which to attempt a derive
236     let derives: [(fn(&Ctx, Trait) -> _, _); 6] = [
237         (crate::derive::known_layout::derive, Trait::KnownLayout),
238         (crate::derive::derive_immutable, Trait::Immutable),
239         (crate::derive::from_bytes::derive_from_bytes, Trait::FromBytes),
240         (crate::derive::into_bytes::derive_into_bytes, Trait::IntoBytes),
241         (crate::derive::derive_split_at, Trait::SplitAt),
242         (crate::derive::unaligned::derive_unaligned, Trait::Unaligned),
243     ];
244 
245     let mut tokens = proc_macro2::TokenStream::new();
246     for (derive, t) in derives {
247         tokens.extend(derive(&ctx, t))
248     }
249     // Invariants prevent `FromBytes` from generating its usual supertrait
250     // impls, but still permit checked conversions through `TryFromBytes`.
251     if ctx.invariant_span.is_some() {
252         tokens.extend(crate::derive::try_from_bytes::derive_try_from_bytes(
253             &ctx,
254             Trait::TryFromBytes,
255         ));
256     }
257 
258     // Apply generated-code lint allowances as a backstop, except around
259     // caller-authored invariant expressions.
260     let ts = ctx.const_block([Some(tokens)]);
261     #[cfg(test)]
262     crate::util::testutil::check_hygiene(ts.clone());
263     ts.into()
264 }
265 
266 /// Deprecated: prefer [`FromZeros`] instead.
267 #[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
268 #[doc(hidden)]
269 #[proc_macro_derive(FromZeroes)]
270 pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
271     derive_from_zeros(ts)
272 }
273 
274 /// Deprecated: prefer [`IntoBytes`] instead.
275 #[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
276 #[doc(hidden)]
277 #[proc_macro_derive(AsBytes)]
278 pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
279     derive_into_bytes(ts)
280 }
281