vasi_macro/
lib.rs

1// https://github.com/rust-lang/rfcs/blob/master/text/2585-unsafe-block-in-unsafe-fn.md
2#![deny(unsafe_op_in_unsafe_fn)]
3
4use proc_macro2::TokenStream;
5use quote::quote;
6use syn::{Attribute, GenericParam, Generics, Type, parse_quote};
7
8/// Implement `vasi::VirtualAddressSpaceIndependent` for the annotated type.
9/// Requires all fields to implement `vasi::VirtualAddressSpaceIndependent`.
10///
11/// An empty struct fails becase Rust doesn't consider fieldless structs to be
12/// FFI-safe:
13/// ```compile_fail
14/// use vasi::VirtualAddressSpaceIndependent;
15///
16/// #[derive(VirtualAddressSpaceIndependent)]
17/// #[repr(C)]
18/// struct Foo {}
19/// ```
20///
21/// FFI-safe structs containing only `VirtualAddressSpaceIndependent`
22/// fields qualify:
23/// ```
24/// use vasi::VirtualAddressSpaceIndependent;
25///
26/// #[repr(C)]
27/// #[derive(VirtualAddressSpaceIndependent)]
28/// struct Foo {
29///   x: i32,
30/// }
31/// ```
32///
33/// `#[repr(transparent)]` is OK too.
34/// ```
35/// use vasi::VirtualAddressSpaceIndependent;
36///
37/// #[repr(transparent)]
38/// #[derive(VirtualAddressSpaceIndependent)]
39/// struct Foo {
40///   x: i32,
41/// }
42/// ```
43///
44/// A struct containing a *reference* doesn't qualify:
45/// ```compile_fail
46/// use vasi::VirtualAddressSpaceIndependent;
47///
48/// #[repr(C)]
49/// #[derive(VirtualAddressSpaceIndependent)]
50/// struct Foo<'a> {
51///   x: &'a i32,
52/// }
53/// ```
54///
55/// A struct containing a [Box] doesn't qualify:
56/// ```compile_fail
57/// use vasi::VirtualAddressSpaceIndependent;
58///
59/// #[repr(C)]
60/// #[derive(VirtualAddressSpaceIndependent)]
61/// struct Foo {
62///   x: Box<i32>,
63/// }
64/// ```
65///
66/// A struct containing a *pointer* doesn't qualify:
67/// ```compile_fail
68/// use vasi::VirtualAddressSpaceIndependent;
69///
70/// #[repr(C)]
71/// #[derive(VirtualAddressSpaceIndependent)]
72/// struct Foo {
73///   x: *const i32,
74/// }
75/// ```
76///
77/// A field can be allow-listed with the attribute `unsafe_assume_virtual_address_space_independent`:
78/// ```
79/// use vasi::VirtualAddressSpaceIndependent;
80///
81/// #[repr(C)]
82/// #[derive(VirtualAddressSpaceIndependent)]
83/// struct Foo {
84///   // SAFETY: we ensure the pointer isn't dereferenced
85///   // outside of its original virtual address space.
86///   #[unsafe_assume_virtual_address_space_independent]
87///   x: *const i32,
88/// }
89/// ```
90///
91/// A union containing only `VirtualAddressSpaceIndependent` fields qualifies:
92/// ```
93/// use vasi::VirtualAddressSpaceIndependent;
94///
95/// #[repr(C)]
96/// #[derive(VirtualAddressSpaceIndependent)]
97/// union Foo {
98///   x: i32,
99///   y: i32,
100/// }
101/// ```
102///
103/// A union containing a non-vasi member doesn't qualify:
104/// ```compile_fail
105/// use vasi::VirtualAddressSpaceIndependent;
106///
107/// #[repr(C)]
108/// #[derive(VirtualAddressSpaceIndependent)]
109/// struct Foo {
110///   x: i32,
111///   y: *const i32,
112/// }
113/// ```
114///
115/// An enum containing only `VirtualAddressSpaceIndependent` variants qualifies:
116/// ```
117/// use vasi::VirtualAddressSpaceIndependent;
118///
119/// #[repr(C)]
120/// #[derive(VirtualAddressSpaceIndependent)]
121/// enum Foo {
122///   Bar(i32),
123///   Baz(i32),
124/// }
125/// ```
126///
127/// An enum containing a non-vasi variant doesn't qualify:
128/// ```compile_fail
129/// use vasi::VirtualAddressSpaceIndependent;
130///
131/// #[repr(C)]
132/// #[derive(VirtualAddressSpaceIndependent)]
133/// enum Foo {
134///   Bar(i32),
135///   Baz(*const i32),
136/// }
137/// ```
138///
139/// A generic type *conditionally* implements VirtualAddressSpaceIndependent,
140/// if its type parameters do (as the derive macros in the std crate behave).
141/// ```
142/// use vasi::VirtualAddressSpaceIndependent;
143///
144/// #[repr(C)]
145/// #[derive(VirtualAddressSpaceIndependent)]
146/// struct MyWrapper<T> {
147///   val: T,
148/// }
149///
150/// static_assertions::assert_impl_all!(MyWrapper<i32>: vasi::VirtualAddressSpaceIndependent);
151/// static_assertions::assert_not_impl_all!(MyWrapper<* const i32>: vasi::VirtualAddressSpaceIndependent);
152/// ```
153///
154/// Generic type with existing bounds are also supported.
155/// ```
156/// use vasi::VirtualAddressSpaceIndependent;
157///
158/// #[repr(C)]
159/// #[derive(VirtualAddressSpaceIndependent)]
160/// struct MyWrapper<T: Copy> {
161///   val: T,
162/// }
163/// static_assertions::assert_impl_all!(MyWrapper<i32>: vasi::VirtualAddressSpaceIndependent);
164/// static_assertions::assert_not_impl_all!(MyWrapper<* const i32>: vasi::VirtualAddressSpaceIndependent);
165///
166/// #[repr(C)]
167/// #[derive(VirtualAddressSpaceIndependent)]
168/// struct MyWrapper2<T> where T: Copy {
169///   val: T,
170/// }
171/// static_assertions::assert_impl_all!(MyWrapper2<i32>: vasi::VirtualAddressSpaceIndependent);
172/// static_assertions::assert_not_impl_all!(MyWrapper2<* const i32>: vasi::VirtualAddressSpaceIndependent);
173/// ```
174///
175/// As with e.g. Copy and Clone, a field that is dependent on a type parameter
176/// but still isn't VirtualAddressSpaceIndependent will cause the macro not to
177/// compile:
178/// ```compile_fail
179/// use vasi::VirtualAddressSpaceIndependent;
180///
181/// #[repr(C)]
182/// #[derive(VirtualAddressSpaceIndependent)]
183/// struct MyWrapper<T> {
184///   val: *const T,
185/// }
186/// ```
187#[proc_macro_derive(
188    VirtualAddressSpaceIndependent,
189    attributes(unsafe_assume_virtual_address_space_independent)
190)]
191pub fn derive_virtual_address_space_independent(
192    tokens: proc_macro::TokenStream,
193) -> proc_macro::TokenStream {
194    // Construct a representation of Rust code as a syntax tree
195    // that we can manipulate
196    let ast = syn::parse(tokens).unwrap();
197    // Build the trait implementation
198    impl_derive_virtual_address_space_independent(ast)
199}
200
201// Add a bound `T: VirtualAddressSpaceIndependent` to every type parameter T.
202fn add_trait_bounds(mut generics: Generics) -> Generics {
203    for param in &mut generics.params {
204        if let GenericParam::Type(ref mut type_param) = *param {
205            type_param
206                .bounds
207                .push(parse_quote!(vasi::VirtualAddressSpaceIndependent));
208        }
209    }
210    generics
211}
212
213fn assume_vasi(attrs: &[Attribute]) -> bool {
214    attrs.iter().any(|attr| {
215        attr.path()
216            .is_ident("unsafe_assume_virtual_address_space_independent")
217    })
218}
219
220fn impl_derive_virtual_address_space_independent(ast: syn::DeriveInput) -> proc_macro::TokenStream {
221    let name = &ast.ident;
222    // This will contain calls to a function `check` that accepts VirtualAddressSpaceIndependent types,
223    // which is how we validate that the fields are VirtualAddressSpaceIndependent.
224    // e.g. for an input struct definition
225    // ```
226    // struct MyStruct {
227    //   x: u32,
228    //   y: i32,
229    // }
230    // ```
231    //
232    // We'll end up generating code like:
233    // ```
234    // impl VirtualAddressSpaceIndependent for MyStruct {
235    //     const IGNORE: () = {
236    //         fn check<T: VirtualAddressSpaceIndependent>() {}
237    //         check::<u32>(); // check type of MyStruct::x
238    //         check::<i32>(); // check type of MyStruct::y
239    //     };
240    // }
241    // ```
242    let types: Vec<&Type> = match &ast.data {
243        syn::Data::Struct(s) => s
244            .fields
245            .iter()
246            .filter(|field| !assume_vasi(&field.attrs))
247            .map(|field| &field.ty)
248            .collect(),
249        syn::Data::Enum(e) => e
250            .variants
251            .iter()
252            .flat_map(|variant| {
253                variant
254                    .fields
255                    .iter()
256                    .filter(|field| !assume_vasi(&field.attrs))
257                    .map(|field| &field.ty)
258            })
259            .collect(),
260        syn::Data::Union(u) => u
261            .fields
262            .named
263            .iter()
264            .filter(|field| !assume_vasi(&field.attrs))
265            .map(|field| &field.ty)
266            .collect(),
267    };
268
269    // These will fail to compile if any of the types aren't VirtualAddressSpaceIndependent.
270    let calls_to_check: TokenStream = types
271        .into_iter()
272        .map(|ty| quote! {check::<#ty>();})
273        .collect();
274
275    // Add a bound `T: VirtualAddressSpaceIndependent` to every type parameter T.
276    // This allows generic types to be conditionally VirtualAddressSpaceIndependent,
277    // iff their type parameters are.
278    let generics = add_trait_bounds(ast.generics);
279    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
280
281    quote! {
282        unsafe impl #impl_generics vasi::VirtualAddressSpaceIndependent for #name #ty_generics #where_clause {
283            const IGNORE: () = {
284                const fn check<T: ::vasi::VirtualAddressSpaceIndependent>() {}
285                #calls_to_check
286            };
287        }
288        #[deny(improper_ctypes_definitions)]
289        const _: () = {
290            // Force compilation to fail if the type isn't FFI safe.
291            extern "C" fn _vasi_validate_ffi_safe #impl_generics (_: #name #ty_generics) #where_clause {}
292        };
293    }
294    .into()
295}