ffi_destruct/
lib.rs

1//! # FFI Destruct
2//! Generates destructors for structures that contain raw pointers in the FFI.
3//!
4//! ## Example
5//! Provides a structure with several raw pointers that need to be dropped manually.
6//! ```
7//! use ffi_destruct::{extern_c_destructor, Destruct};
8//! use std::ffi::*;
9//!
10//! #[derive(Destruct)]
11//! pub struct MyStruct {
12//!     field: *mut std::ffi::c_char,
13//! }
14//!
15//! pub struct AnyOther(u32, u32);
16//!
17//! // Struct definition here, with deriving Destruct and nullable attributes.
18//! #[derive(Destruct)]
19//! pub struct Structure {
20//!     // Default is non-null.
21//!     c_string: *const c_char,
22//!     #[nullable]
23//!     c_string_nullable: *mut c_char,
24//!
25//!     other: *mut MyStruct,
26//!     #[nullable]
27//!     other_nullable: *mut MyStruct,
28//!
29//!     // Do not drop this field.
30//!     #[no_drop]
31//!     not_dropped: *const AnyOther,
32//!
33//!     // Raw pointer for any other things
34//!     any: *mut AnyOther,
35//!
36//!     // Non-pointer types are still available, and will not be added to drop().
37//!     pub normal_int: u32,
38//!     pub normal_string: String,
39//! }
40//!
41//! // (Optional) The macro here generates the destructor: destruct_structure()
42//! extern_c_destructor!(Structure);
43//!
44//! fn test() {
45//!     // Some resources manually managed
46//!     let tmp = AnyOther(1, 1);
47//!     let tmp_ptr = Box::into_raw(Box::new(tmp));
48//!
49//!     let my_struct = Structure {
50//!         c_string: CString::new("Hello").unwrap().into_raw(),
51//!         c_string_nullable: std::ptr::null_mut(),
52//!         other: Box::into_raw(Box::new(MyStruct {
53//!             field: CString::new("Hello").unwrap().into_raw(),
54//!         })),
55//!         other_nullable: std::ptr::null_mut(),
56//!         not_dropped: tmp_ptr,
57//!         any: Box::into_raw(Box::new(AnyOther(1, 1))),
58//!         normal_int: 114514,
59//!         normal_string: "Hello".to_string(),
60//!     };
61//!
62//!     let my_struct_ptr = Box::into_raw(Box::new(my_struct));
63//!     // FFI calling
64//!     unsafe {
65//!         destruct_structure(my_struct_ptr);
66//!     }
67//!
68//!     // Drop the manually managed resources
69//!     unsafe {
70//!         let _ = Box::from_raw(tmp_ptr);
71//!     }
72//! }
73//! ```
74//!
75//! After expanding the macros:
76//! ```ignore
77//! #[macro_use]
78//! #![feature(prelude_import)]
79//! #![allow(dead_code)]
80//! #[prelude_import]
81//! use std::prelude::rust_2021::*;
82//! #[macro_use]
83//! extern crate std;
84//! use ffi_destruct::{extern_c_destructor, Destruct};
85//! use std::ffi::*;
86//! pub struct MyStruct {
87//!     field: *mut std::ffi::c_char,
88//! }
89//! impl ::std::ops::Drop for MyStruct {
90//!     fn drop(&mut self) {
91//!         unsafe {
92//!             let _ = ::std::ffi::CString::from_raw(self.field as *mut ::std::ffi::c_char);
93//!         }
94//!     }
95//! }
96//! pub struct AnyOther(u32, u32);
97//! pub struct Structure {
98//!     c_string: *const c_char,
99//!     #[nullable]
100//!     c_string_nullable: *mut c_char,
101//!     other: *mut MyStruct,
102//!     #[nullable]
103//!     other_nullable: *mut MyStruct,
104//!     #[no_drop]
105//!     not_dropped: *const AnyOther,
106//!     any: *mut AnyOther,
107//!     pub normal_int: u32,
108//!     pub normal_string: String,
109//! }
110//! impl ::std::ops::Drop for Structure {
111//!     fn drop(&mut self) {
112//!         unsafe {
113//!             let _ = ::std::ffi::CString::from_raw(
114//!                 self.c_string as *mut ::std::ffi::c_char,
115//!             );
116//!             if !self.c_string_nullable.is_null() {
117//!                 let _ = ::std::ffi::CString::from_raw(
118//!                     self.c_string_nullable as *mut ::std::ffi::c_char,
119//!                 );
120//!             }
121//!             let _ = ::std::boxed::Box::from_raw(self.other as *mut MyStruct);
122//!             if !self.other_nullable.is_null() {
123//!                 let _ = ::std::boxed::Box::from_raw(
124//!                     self.other_nullable as *mut MyStruct,
125//!                 );
126//!             }
127//!             let _ = ::std::boxed::Box::from_raw(self.any as *mut AnyOther);
128//!         }
129//!     }
130//! }
131//! #[no_mangle]
132//! pub unsafe extern "C" fn destruct_structure(ptr: *mut Structure) {
133//!     if ptr.is_null() {
134//!         return;
135//!     }
136//!     let _ = ::std::boxed::Box::from_raw(ptr);
137//! }
138//! fn test() {
139//!     let tmp = AnyOther(1, 1);
140//!     let tmp_ptr = Box::into_raw(Box::new(tmp));
141//!     let my_struct = Structure {
142//!         c_string: CString::new("Hello").unwrap().into_raw(),
143//!         c_string_nullable: std::ptr::null_mut(),
144//!         other: Box::into_raw(
145//!             Box::new(MyStruct {
146//!                 field: CString::new("Hello").unwrap().into_raw(),
147//!             }),
148//!         ),
149//!         other_nullable: std::ptr::null_mut(),
150//!         not_dropped: tmp_ptr,
151//!         any: Box::into_raw(Box::new(AnyOther(1, 1))),
152//!         normal_int: 114514,
153//!         normal_string: "Hello".to_string(),
154//!     };
155//!     let my_struct_ptr = Box::into_raw(Box::new(my_struct));
156//!     unsafe {
157//!         destruct_structure(my_struct_ptr);
158//!     }
159//!     unsafe {
160//!         let _ = Box::from_raw(tmp_ptr);
161//!     }
162//! }
163//! ```
164
165mod destruct;
166mod utils;
167
168use convert_case::{Case, Casing};
169use proc_macro2::{Ident, TokenStream};
170use quote::{quote, quote_spanned, ToTokens};
171use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
172
173/// The [`Destruct`] derive macro.
174///
175/// Generate a destructor for the structure.
176///
177/// ## Field Attributes
178/// - `#[nullable]` - The field is nullable, the destructor will check if the pointer is null before
179/// - `#[no_drop]` - The field will not be added to the destructor
180#[proc_macro_derive(Destruct, attributes(nullable, no_drop))]
181pub fn destruct_macro_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182    let input = parse_macro_input!(input as DeriveInput);
183
184    let expand = destruct::impl_destruct_macro(&input);
185
186    proc_macro::TokenStream::from(expand)
187}
188
189/// Generate extern "C" destructor for provide type
190///
191/// Provide the function name: "destruct_" + snake_case name of the type.
192///
193/// ## Usage
194///
195/// ```
196/// // Definition of struct here
197/// # use ffi_destruct::{Destruct, extern_c_destructor};
198/// #[derive(Destruct)]
199/// pub struct MyStruct {
200///     field: *mut std::ffi::c_char,
201/// }
202/// // destructor macro here
203/// extern_c_destructor!(MyStruct);
204/// ```
205/// The macro will be expanded to:
206/// ```
207/// # use ffi_destruct::Destruct;
208/// # #[derive(Destruct)]
209/// # pub struct MyStruct {
210/// #    field: *mut std::ffi::c_char,
211/// # }
212/// #[no_mangle]
213/// pub unsafe extern "C" fn destruct_my_struct(ptr: *mut MyStruct) {
214///     if ptr.is_null() {
215///         return;
216///     }
217///     let _ = ::std::boxed::Box::from_raw(ptr);
218/// }
219/// ```
220#[proc_macro]
221pub fn extern_c_destructor(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
222    let ty: syn::Type = parse_macro_input!(input);
223    match ty {
224        syn::Type::Path(v) => {
225            let ident = v.path.get_ident().expect("Only support single ident.");
226            let mut name = ident.to_string().to_case(Case::Snake);
227            name.insert_str(0, "destruct_");
228            let fn_ident = Ident::new(&name, ident.span());
229            quote! {
230                #[no_mangle]
231                pub unsafe extern "C" fn #fn_ident(ptr: *mut #ident) {
232                    if ptr.is_null() {
233                        return;
234                    }
235                    let _ = ::std::boxed::Box::from_raw(ptr);
236                }
237            }
238            .into()
239        }
240        _ => panic!("Not supported type"),
241    }
242}