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}