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}