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