Skip to content

Commit

Permalink
feat: extern_c_destructor macro
Browse files Browse the repository at this point in the history
  • Loading branch information
I-Info committed Oct 17, 2022
1 parent 94240ab commit b83c9ff
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 10 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,4 @@ proc-macro = true
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"

[dev-dependencies]
trybuild = "1.0"
convert_case = "0.6"
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ Supported types of raw pointers:
## Example
Provides a structure with several raw pointers that need to be dropped manually.
```rust
use ffi_destruct::{extern_c_destructor, Destruct};

// Struct definition here, with deriving Destruct and nullable attributes.
#[derive(Destruct)]
struct Structure {
pub struct Structure {
c_string: *const c_char,
#[nullable]
c_string_nullable: *mut c_char,
Expand All @@ -23,9 +26,12 @@ struct Structure {
#[nullable]
other_nullable: *mut TestA,
}

// destructor macro here (optional)
extern_c_destructor!(Structure);
```

The macros will be expanded:
The macros will be expanded to:
```rust
struct Structure {
c_string: *const c_char,
Expand All @@ -36,6 +42,7 @@ struct Structure {
other_nullable: *mut TestA,
}

// derive(Destruct)
impl ::std::ops::Drop for Structure {
fn drop(&mut self) {
unsafe {
Expand All @@ -54,4 +61,13 @@ impl ::std::ops::Drop for Structure {
}
}
}

// extern_c_destructor, with snake_case naming
#[no_mangle]
pub unsafe extern "C" fn destruct_structure(ptr: *mut Structure) {
if ptr.is_null() {
return;
}
let _ = ::std::boxed::Box::from_raw(ptr);
}
```
64 changes: 63 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
//! # FFI Destruct
//! Macros generate destructors for structures containing raw pointers.
use convert_case::{Case, Casing};
use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};

/// The `Destruct` derive macro.
/// The [`Destruct`] derive macro.
///
/// Generate a destructor for the structure.
///
/// ## Field Attributes
/// - `#[nullable]` - The field is nullable, the destructor will check if the pointer is null before
#[proc_macro_derive(Destruct, attributes(nullable))]
pub fn destruct_macro_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand Down Expand Up @@ -74,6 +80,7 @@ fn field_destructors(data: &Data) -> TokenStream {
}
}

/// Check if the field is nullable.
fn get_attribute_nullable(attrs: &Vec<syn::Attribute>) -> bool {
let mut nullable = false;
for attr in attrs {
Expand Down Expand Up @@ -110,3 +117,58 @@ fn destruct_type_ptr(name: &Ident, ty: &syn::TypePtr) -> TokenStream {
_ => panic!("Only single level raw pointers are supported"),
}
}

/// Generate extern "C" destructor for provide type
///
/// Provide the function name: "destruct_" + snake_case name of the type.
///
/// ## Usage
///
/// ```
/// // Definition of struct here
/// # use ffi_destruct::{Destruct, extern_c_destructor};
/// #[derive(Destruct)]
/// pub struct MyStruct {
/// field: *mut std::ffi::c_char,
/// }
/// // destructor macro here
/// extern_c_destructor!(MyStruct);
/// ```
/// The macro will be expanded to:
/// ```
/// # use ffi_destruct::Destruct;
/// # #[derive(Destruct)]
/// # pub struct MyStruct {
/// # field: *mut std::ffi::c_char,
/// # }
/// #[no_mangle]
/// pub unsafe extern "C" fn destruct_my_struct(ptr: *mut MyStruct) {
/// if ptr.is_null() {
/// return;
/// }
/// let _ = ::std::boxed::Box::from_raw(ptr);
/// }
/// ```
#[proc_macro]
pub fn extern_c_destructor(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ty: syn::Type = parse_macro_input!(input);
match ty {
syn::Type::Path(v) => {
let ident = v.path.get_ident().expect("Only support single ident.");
let mut name = ident.to_string().to_case(Case::Snake);
name.insert_str(0, "destruct_");
let fn_ident = Ident::new(&name, ident.span());
quote! {
#[no_mangle]
pub unsafe extern "C" fn #fn_ident(ptr: *mut #ident) {
if ptr.is_null() {
return;
}
let _ = ::std::boxed::Box::from_raw(ptr);
}
}
.into()
}
_ => panic!("Not supported type"),
}
}
29 changes: 25 additions & 4 deletions tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
#![allow(dead_code, unused)]

use ffi_destruct::Destruct;
use ffi_destruct::{extern_c_destructor, Destruct};
use std::ffi::*;

#[derive(Destruct)]
struct TestA {
pub struct TestA {
a: *const std::ffi::c_char,
b: *const std::os::raw::c_char,
c: *mut c_char,
}

extern_c_destructor!(TestA);

#[derive(Destruct)]
pub struct MyStruct {
field: *mut std::ffi::c_char,
}

extern_c_destructor!(MyStruct);

#[derive(Destruct)]
struct TestB {
a: String,
Expand Down Expand Up @@ -38,14 +47,16 @@ impl Drop for TestE {
}

#[derive(Destruct)]
struct TestF {
pub struct TestF {
a: *mut TestE,
#[nullable]
b: *mut TestE,
}

extern_c_destructor!(TestF);

#[derive(Destruct)]
struct Structure {
pub struct Structure {
c_string: *const c_char,
#[nullable]
c_string_nullable: *mut c_char,
Expand All @@ -55,13 +66,19 @@ struct Structure {
other_nullable: *mut TestA,
}

extern_c_destructor!(Structure);

#[test]
fn test() {
let a = TestA {
a: CString::into_raw(CString::new("123123").unwrap()),
b: CString::into_raw(CString::new("123123").unwrap()),
c: CString::into_raw(CString::new("123123").unwrap()),
};
let ptr_a = Box::into_raw(Box::new(a));
unsafe {
destruct_test_a(ptr_a);
}
let b = TestB {
a: String::from("test"),
b: Box::into_raw(Box::new(String::from("test"))),
Expand All @@ -78,4 +95,8 @@ fn test() {
a: Box::into_raw(Box::new(e)),
b: std::ptr::null_mut(),
};
let ptr_f = Box::into_raw(Box::new(f));
unsafe {
destruct_test_f(ptr_f);
}
}

0 comments on commit b83c9ff

Please sign in to comment.