iceoryx2_ffi_macros/
lib.rs

1// Copyright (c) 2024 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13use proc_macro::TokenStream;
14use proc_macro2::TokenTree;
15use quote::{format_ident, quote};
16use syn::{
17    parse_macro_input, punctuated::Punctuated, Data, DeriveInput, Expr, ExprLit, Fields, Lit,
18    LitStr, Meta, Token,
19};
20
21#[proc_macro_attribute]
22pub fn iceoryx2_ffi(args: TokenStream, input: TokenStream) -> TokenStream {
23    let Args { rust_type: my_type } = parse_attribute_args(args);
24
25    // Parse the input tokens into a syntax tree
26    let my_struct = parse_macro_input!(input as DeriveInput);
27
28    let mut has_repr_c = false;
29    for attr in &my_struct.attrs {
30        if attr.path().is_ident("repr") {
31            let nested = attr
32                .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
33                .expect("'repr' should have at least one argument");
34            for meta in nested {
35                match meta {
36                    // #[repr(C)]
37                    Meta::Path(path) if path.is_ident("C") => {
38                        has_repr_c = true;
39                    }
40                    _ => (),
41                }
42            }
43        }
44    }
45
46    if !has_repr_c {
47        panic!(
48            "The 'repr(C)' attribute is missing from '{}'!",
49            &my_struct.ident.to_string()
50        );
51    }
52
53    // Get the name of the struct we are generating code
54    let struct_name = &my_struct.ident;
55    let stripped_struct_name = struct_name
56        .to_string()
57        .strip_prefix("iox2_")
58        .expect("The struct must have an 'iox2_' prefix")
59        .strip_suffix("_t")
60        .expect("The struct must have a '_t' suffix")
61        .to_string();
62
63    // Define all the names we need
64    let struct_storage_name = format_ident!("iox2_{}_storage_t", stripped_struct_name);
65    let _struct_h_t_name = format_ident!("iox2_{}_h_t", stripped_struct_name);
66    let struct_h_name = format_ident!("iox2_{}_h", stripped_struct_name);
67    let _struct_h_ref_name = format_ident!("iox2_{}_h_ref", stripped_struct_name);
68
69    // NOTE: cbindgen does not play well with adding new structs or fields to existing structs;
70    // this code is kept for reference
71
72    // // Add the additional fields to the struct
73    // match &mut my_struct.data {
74    //     syn::Data::Struct(ref mut struct_data) => match &mut struct_data.fields {
75    //         syn::Fields::Named(fields) => {
76    //             fields.named.push(
77    //                 syn::Field::parse_named
78    //                 .parse2(quote! { value: #struct_storage_name })
79    //                 .unwrap(),
80    //             );
81    //             fields.named.push(
82    //                 syn::Field::parse_named
83    //                 .parse2(quote! { deleter: fn(*mut #struct_name) })
84    //                 .unwrap(),
85    //             );
86    //         }
87    //         _ => (),
88    //     },
89    //     _ => panic!("#### `iceoryx_ffi` has to be used with structs "),
90    // };
91
92    let expanded = quote! {
93        impl #struct_storage_name {
94            const fn assert_storage_layout() {
95                static_assert_ge::<
96                { ::std::mem::align_of::<#struct_storage_name>() },
97                { ::std::mem::align_of::<Option<#my_type>>() },
98                >();
99                static_assert_ge::<
100                { ::std::mem::size_of::<#struct_storage_name>() },
101                { ::std::mem::size_of::<Option<#my_type>>() },
102                >();
103            }
104
105            fn init(&mut self, value: #my_type) {
106                #struct_storage_name::assert_storage_layout();
107
108                unsafe { &mut *(self as *mut Self).cast::<::std::mem::MaybeUninit<Option<#my_type>>>() }
109                .write(Some(value));
110            }
111
112            pub(super) unsafe fn as_option_mut(&mut self) -> &mut Option<#my_type> {
113                &mut *(self as *mut Self).cast::<Option<#my_type>>()
114            }
115
116            pub(super) unsafe fn as_option_ref(&self) -> &Option<#my_type> {
117                &*(self as *const Self).cast::<Option<#my_type>>()
118            }
119
120            pub(super) unsafe fn as_mut(&mut self) -> &mut #my_type {
121                self.as_option_mut().as_mut().unwrap()
122            }
123
124            pub(super) unsafe fn as_ref(&self) -> &#my_type {
125                self.as_option_ref().as_ref().unwrap()
126            }
127        }
128
129        // this is the struct which is annotated with '#[iceoryx2_ffi(Type)]'
130        #my_struct
131
132        impl #struct_name {
133            pub(super) fn as_handle(&mut self) -> #struct_h_name {
134                self as *mut _ as _
135            }
136
137            pub(super) fn take(&mut self) -> Option<#my_type> {
138                unsafe { self.value.as_option_mut().take() }
139            }
140
141            pub(super) fn set(&mut self, value: #my_type) {
142                unsafe { *self.value.as_option_mut() = Some(value) }
143            }
144
145            pub(super) fn alloc() -> *mut #struct_name {
146                unsafe { ::std::alloc::alloc(::std::alloc::Layout::new::<#struct_name>()) as _ }
147            }
148
149            pub(super) fn dealloc(storage: *mut #struct_name) {
150                unsafe {
151                    ::std::alloc::dealloc(storage as _, ::core::alloc::Layout::new::<#struct_name>())
152                }
153            }
154        }
155
156        #[cfg(test)]
157        mod test_generated {
158            use super::*;
159
160            #[test]
161            fn assert_storage_size() {
162                // all const functions; if it compiles, the storage size is sufficient
163                const _STORAGE_LAYOUT_CHECK: () = #struct_storage_name::assert_storage_layout();
164            }
165        }
166    };
167
168    // eprintln!("#### DEBUG\n{}", expanded);
169    TokenStream::from(expanded)
170}
171
172struct Args {
173    rust_type: syn::Type,
174}
175
176fn parse_attribute_args(args: TokenStream) -> Args {
177    let args = proc_macro2::TokenStream::from(args);
178
179    let args = args.into_iter().collect::<Vec<_>>();
180
181    let attribute_format = "Format must be '#[iceoryx2_ffi(Type)]'";
182    if args.len() != 1 {
183        panic!("Invalid attribute definition! {}", attribute_format);
184    }
185
186    let rust_type = match &args[0] {
187        TokenTree::Ident(my_type) => LitStr::new(&my_type.to_string(), my_type.span())
188            .parse::<syn::Type>()
189            .expect("Valid type"),
190        _ => panic!("Invalid type argument! {}", attribute_format),
191    };
192
193    // NOTE: this code is kept for reference if more arguments are added to the attribute
194
195    // match (&args[1], &args[3], &args[5], &args[7]) {
196    //     (TokenTree::Punct(a), TokenTree::Punct(b), TokenTree::Punct(c), TokenTree::Punct(d))
197    //         if a.as_char() == ','
198    //             && b.as_char() == '='
199    //             && c.as_char() == ','
200    //             && d.as_char() == '=' =>
201    //     {
202    //         ()
203    //     }
204    //     _ => panic!("Invalid format! {}", attribute_format),
205    // }
206    //
207    // let size = match (&args[2], &args[4]) {
208    //     (TokenTree::Ident(key), TokenTree::Literal(value)) if key.to_string() == "size" => {
209    //         <proc_macro2::Literal as Into<LitInt>>::into(value.clone())
210    //     }
211    //     _ => panic!("Invalid 'size' argument! {}", attribute_format),
212    // };
213    //
214    // let alignment = match (&args[6], &args[8]) {
215    //     (TokenTree::Ident(key), TokenTree::Literal(value)) if key.to_string() == "alignment" => {
216    //         <proc_macro2::Literal as Into<LitInt>>::into(value.clone())
217    //     }
218    //     _ => panic!("Invalid 'alignment' argument! {}", attribute_format),
219    // };
220
221    Args { rust_type }
222}
223
224/// Implements the [`iceoryx2_bb_elementary::AsCStr`] trait for enums to provide a string representation of each enum variant.
225///
226/// The string representation can be customized using the `CStr` attribute, otherwise it will
227/// convert the variant name to lowercase and replace underscores with spaces.
228///
229/// # Example
230/// ```
231/// use iceoryx2_ffi_macros::CStrRepr;
232/// use iceoryx2_bb_elementary::AsCStr;
233///
234/// #[derive(CStrRepr)]
235/// enum MyEnum {
236///     #[CStr = "custom variant one"]
237///     VariantOne,
238///     VariantTwo,
239/// }
240///
241/// let v1 = MyEnum::VariantOne;
242/// assert_eq!(v1.as_const_cstr(), c"custom variant one");
243///
244/// let v2 = MyEnum::VariantTwo;
245/// assert_eq!(v2.as_const_cstr(), c"variant two");
246/// ```
247#[proc_macro_derive(CStrRepr, attributes(CStr))]
248pub fn string_literal_derive(input: TokenStream) -> TokenStream {
249    let input = parse_macro_input!(input as DeriveInput);
250    let name = &input.ident;
251    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
252
253    let as_cstr_impl = match input.data {
254        Data::Enum(ref data_enum) => {
255            let enum_to_string_mapping = data_enum.variants.iter().map(|variant| {
256                let null_terminated = |s: &str| {
257                    quote! {
258                        // This code appends the null termination which cannot be confirmed at compile time,
259                        // thus the code is ensured safe.
260                        unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#s, "\0").as_bytes()) }
261                    }
262                };
263
264                let enum_name = &variant.ident;
265                let cstr_literal = variant
266                    .attrs
267                    .iter()
268                    .find(|attr| attr.path().is_ident("CStr"))
269                    .and_then(|attr| match attr.meta.require_name_value() {
270                        Ok(meta) => match &meta.value {
271                            Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) => Some(null_terminated(&lit.value())),
272                            _ => None,
273                        },
274                        _ => None,
275                    })
276                    .unwrap_or_else(|| {
277                        // No explicity `CStr` is provided.
278                        // Convert variant name from 'UpperCamelCase' to 'lowercase with spaces'.
279                        let enum_string = enum_name.to_string()
280                            .char_indices()
281                            .fold(String::new(), |mut acc, (i, c)| {
282                                if i > 0 && c.is_uppercase() {
283                                    acc.push(' ');
284                                }
285                                acc.push(c.to_ascii_lowercase());
286                                acc
287                            });
288                        null_terminated(&enum_string)
289                    });
290
291                let pattern = match &variant.fields {
292                    Fields::Unit => quote!(Self::#enum_name),
293                    Fields::Unnamed(_) => quote!(Self::#enum_name(..)),
294                    Fields::Named(_) => quote!(Self::#enum_name{..}),
295                };
296                quote! { #pattern => #cstr_literal }
297            });
298
299            quote! {
300                fn as_const_cstr(&self) -> &'static ::std::ffi::CStr {
301                    match self {
302                        #(#enum_to_string_mapping,)*
303                    }
304                }
305            }
306        }
307        _ => {
308            let err = syn::Error::new_spanned(&input, "AsCStrRepr can only be derived for enums");
309            return err.to_compile_error().into();
310        }
311    };
312
313    TokenStream::from(quote! {
314        impl #impl_generics AsCStr for #name #type_generics #where_clause {
315            #as_cstr_impl
316        }
317    })
318}