c_closures_build/
lib.rs

1//! # Purpose
2//!
3//! This crate is for producing Rust closures that can cross an FFI boundary.
4//! It provides support for any function signature, assuming all of the types
5//! in it have valid representations in C/C++ and Rust.
6//!
7//! [Here's an example.](https://github.com/Xaeroxe/c-closures-rs/tree/master/example)
8//!
9//! # Safety concerns
10//!
11//! Creating a `*Closure` by itself can not cause undefined behavior, however when the resulting
12//! structure is used in C/C++ it can still trigger undefined behavior. `*Closure` should never be
13//! an argument to a safe function, nor should it be a public member of any structures passed into a safe function.
14//! Please write your own safe wrappers that incorporate the `*Closure` types internally.
15//!
16//! # Usage in C/C++
17//!
18//! To use this with a C/C++ library you'll need to include the header provided in the repo,
19//! `rust_closures.h`. Then you can accept the relevant `*Closure` type anywhere that you need to
20//! accept arbitrary Rust code.
21//!
22//! # Limitations
23//!
24//! This cannot be used to transfer ownership of allocated memory across FFI boundaries, as this crate cannot reasonably guarantee
25//! both sides are using the same memory allocator, or dispose of the types in the same way. If such transfer
26//! is required, you should copy the data into a new allocation, on the side of the FFI boundary it needs to live
27//! on. The major exception to this is types with the `Copy` marker trait, which are trivially cloned and require
28//! no disposal instructions.
29
30use std::{
31    collections::HashSet,
32    io::{BufWriter, Write},
33    path::PathBuf,
34    process::{Command, Stdio},
35};
36
37use quote::{format_ident, quote, ToTokens};
38use syn::{parse2, parse_str, File, FnArg, ForeignItem, Ident, Item, ReturnType, Signature, Type};
39
40/// Provides the path containing `rust_closures.h`.
41/// You'll need to include this path to compile any C/C++ code making use of this crate's `Closure` types.
42pub fn c_closure_header_include_dir() -> PathBuf {
43    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
44}
45
46const SPECIAL_FN_SUFFIX: &str = "_closure_call";
47const SPECIAL_RELEASE_FN_SUFFIX: &str = "_release_rust_return_value";
48
49struct ClosureDefinition {
50    name: String,
51    signature: Signature,
52}
53
54/// Accepts a blob of auto generated rust code binding to a C/C++ library, probably from `bindgen`,
55/// analyzes it searching for instances of `Closure` definitions. When it finds them, it
56/// enhances the definition with additional functions that allow passing in a rust closure
57/// with a matching signature for the `Closure` definition. Outputs the initial blob,
58/// with the accompanying enhancements. This attempts to `rustfmt` the output, but if that fails
59/// will instead output rust code on a single line. That can make your error messages really ugly looking.
60pub fn enhance_closure_bindings(rust_code: &str) -> String {
61    let mut tree = parse_str::<File>(rust_code).unwrap();
62    let mut new_items = vec![];
63    let mut return_types = HashSet::new();
64    for item in tree.items.iter_mut() {
65        let output = call_recurse(item, &mut |item| {
66            let mut enhance = vec![];
67            let mut should_omit = false;
68            if let Item::ForeignMod(foreigners) = item {
69                let mut new_items = vec![];
70                for foreign_item in &mut foreigners.items {
71                    if let ForeignItem::Fn(function) = foreign_item {
72                        let function_name = function.sig.ident.to_string();
73                        if function_name.ends_with(SPECIAL_FN_SUFFIX) {
74                            let closure_name = (&function_name
75                                [0..(function_name.len() - SPECIAL_FN_SUFFIX.len())])
76                                .to_string();
77                            enhance.push(ClosureDefinition {
78                                name: closure_name,
79                                signature: function.sig.clone(),
80                            });
81                            new_items.push(foreign_item.clone());
82                        } else if function_name.ends_with(SPECIAL_RELEASE_FN_SUFFIX) {
83                            return_types.insert((
84                                function.sig.ident.clone(),
85                                function.sig.inputs[0].clone(),
86                            ));
87                        } else {
88                            new_items.push(foreign_item.clone());
89                        }
90                    }
91                }
92                should_omit = new_items.is_empty();
93                foreigners.items = new_items;
94            }
95            if should_omit {
96                None
97            } else {
98                Some(enhance.iter().flat_map(gen_closure_fns).collect())
99            }
100        });
101        if let Some(items) = output {
102            new_items.push(item.clone());
103            new_items.extend(items);
104        }
105    }
106    tree.items = new_items;
107    tree.items.extend(
108        return_types
109            .into_iter()
110            .map(|arg| match arg {
111                (name, FnArg::Typed(pat_type)) => (name, (*pat_type.ty).clone()),
112                _ => unreachable!("Functions passed into here should never have a self reference."),
113            })
114            .map(|(name, ty)| gen_drop_fns(name, ty)),
115    );
116    let tokenified_source = tree.to_token_stream().to_string();
117    if let Ok(mut rust_fmt_process) = Command::new("rustfmt")
118        .stdin(Stdio::piped())
119        .stdout(Stdio::piped())
120        .spawn()
121    {
122        {
123            if let Some(mut input) = rust_fmt_process.stdin.as_mut().map(BufWriter::new) {
124                let _ = input.write_all(tokenified_source.as_bytes());
125            }
126        }
127        rust_fmt_process
128            .wait_with_output()
129            .map_err(|_| ())
130            .and_then(|o| {
131                if o.status.success() {
132                    String::from_utf8(o.stdout).map_err(|_| ())
133                } else {
134                    Err(())
135                }
136            })
137            .unwrap_or(tokenified_source)
138    } else {
139        tokenified_source
140    }
141}
142
143// Calls a closure on a list of Rust items recursively for each module. If the function returns None that signals
144// to the upper layer that not only is there no enhancements for that item, but additionally that item should be removed
145// from the parent item list.
146fn call_recurse<F: FnMut(&mut Item) -> Option<Vec<Item>>>(
147    item: &mut Item,
148    f: &mut F,
149) -> Option<Vec<Item>> {
150    if let Item::Mod(mmod) = item {
151        if let Some(t) = mmod.content.as_mut() {
152            let new_items = t
153                .1
154                .iter_mut()
155                .filter_map(|item| {
156                    call_recurse(item, f).map(|items| Some(item.clone()).into_iter().chain(items))
157                })
158                .flatten()
159                .collect::<Vec<_>>();
160            t.1 = new_items;
161        }
162    }
163    f(item)
164}
165
166fn type_from_output(output: &ReturnType) -> (bool, Type) {
167    match output {
168        ReturnType::Default => (false, Type::Verbatim(quote!(()))),
169        ReturnType::Type(_, ref ty) => (true, (**ty).clone()),
170    }
171}
172
173fn gen_closure_fns(
174    &ClosureDefinition {
175        ref name,
176        ref signature,
177    }: &ClosureDefinition,
178) -> Vec<Item> {
179    let closure_name = format_ident!("{}Closure", name);
180    let release_name = format_ident!("{}_closure_release", name);
181    let args = signature
182        .inputs
183        .iter()
184        .skip(1)
185        .map(|arg| match arg {
186            FnArg::Typed(pat_type) => (*pat_type.ty).clone(),
187            _ => unreachable!("Functions passed into here should never have a self reference."),
188        })
189        .map(|a| a.to_token_stream())
190        .collect::<Vec<_>>();
191    let arg_idents = (0..args.len())
192        .map(|i| format_ident!("_p{}", i))
193        .collect::<Vec<_>>();
194    let arg_ident_pairs = args
195        .iter()
196        .zip(arg_idents.iter())
197        .map(|(arg, ident)| quote!(#ident: #arg))
198        .collect::<Vec<_>>();
199    let (has_return_value, return_type) = type_from_output(&signature.output);
200
201    let noop = if has_return_value {
202        quote!()
203    } else {
204        quote! {
205            /// Constructs a new instance of this class that when called does nothing.
206            pub fn new_noop() -> Self {
207                Self::fn_not_mut(|#(#arg_idents),*| ())
208            }
209        }
210    };
211    let return_block = if has_return_value {
212        quote!(-> #return_type)
213    } else {
214        quote!()
215    };
216    vec![
217        // primary fn block
218        parse2(
219            quote! {
220                impl #closure_name {
221
222                    unsafe extern "C" fn f_wrapper<F>(f: *mut ::std::ffi::c_void, #(#arg_ident_pairs),*) #return_block
223                    where
224                        F: FnMut(#(#args),*) #return_block,
225                    {
226                        match ::std::panic::catch_unwind(|| {
227                            let f = &mut *(f as *mut F);
228                            f(#(#arg_idents),*)
229                        }) {
230                            Ok(v) => v,
231                            Err(e) => {
232                                // This may also panic. Gotta catch that too.
233                                let _r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
234                                    eprintln!("c-closures-build: Internal closure panicked, this cannot be passed out the FFI boundary, aborting. Error: {:?}", e);
235                                }));
236                                ::std::process::abort()
237                            }
238                        }
239                    }
240
241                    unsafe extern "C" fn drop_my_box<T>(t: *mut ::std::ffi::c_void) {
242                        Self::drop_me(::std::boxed::Box::<T>::from_raw(t as *mut T));
243                    }
244
245                    unsafe extern "C" fn drop_me<T>(t: T) {
246                        match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || ::std::mem::drop(t))) {
247                            Ok(()) => (),
248                            Err(e) => {
249                                // This may also panic. Gotta catch that too.
250                                let _r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
251                                    eprintln!("c-closures-build: Internal drop panicked, this cannot be passed out the FFI boundary, aborting. Error: {:?}", e);
252                                }));
253                                ::std::process::abort()
254                            }
255                        }
256                    }
257
258                    /// Transform an FnMut Rust closure into a structure you can pass into a C/C++ library.
259                    ///
260                    /// This structure currently assumes it will never be called in multiple threads
261                    /// simultaneously. If that guarantee cannot be upheld, then you should instead use `fn_not_mut`.
262                    /// 
263                    /// If the internal closure panics the program will abort, unless the `no_std` feature is enabled.
264                    pub fn fn_mut<Function>(f: Function) -> Self
265                    where
266                        Function: FnMut(#(#args),*) #return_block,
267                    {
268                        Self {
269                            data: ::std::boxed::Box::into_raw(::std::boxed::Box::new(f)) as *mut ::std::ffi::c_void,
270                            function: Some(Self::f_wrapper::<Function>),
271                            delete_data: Some(Self::drop_my_box::<Function>),
272                        }
273                    }
274
275                    /// Transform an Fn Rust closure into a structure you can pass into a C/C++ library.
276                    ///
277                    /// This structure is safe to use in multiple threads simultaneously. If your usage is single
278                    /// threaded, consider `fn_mut` instead as it permits more robust closures.
279                    ///
280                    /// If the internal closure panics the program will abort, unless the `no_std` feature is enabled.
281                    pub fn fn_not_mut<Function>(f: Function) -> Self
282                    where
283                        Function: Fn(#(#args),*) #return_block,
284                    {
285                        Self {
286                            data: ::std::boxed::Box::into_raw(::std::boxed::Box::new(f)) as *mut ::std::ffi::c_void,
287                            function: Some(Self::f_wrapper::<Function>),
288                            delete_data: Some(Self::drop_my_box::<Function>),
289                        }
290                    }
291
292                    /// Transform an FnOnce Rust closure into a structure you can pass into a C/C++ library.
293                    ///
294                    /// This structure assumes it will only ever be called once. If you attempt to call it more than once
295                    /// the program will abort. If the `no_std` feature is enabled, instead you'll received zeroed memory.
296                    ///
297                    /// If the internal closure panics the program will abort, unless the `no_std` feature is enabled.
298                    pub fn fn_once<Function>(f: Function) -> Self
299                    where
300                        Function: FnOnce(#(#args),*) #return_block,
301                    {
302                        let mut f = Some(f);
303                        Self::fn_mut(move |#(#arg_idents),*| match f.take() {
304                            Some(f) => f(#(#arg_idents),*),
305                            None => {
306                                eprintln!("Function marked as single-use was called more than once, the closure will not be called as that would segfault. Aborting.");
307                                ::std::process::abort()
308                            }
309                        })
310                    }
311
312                    #noop
313                }
314            }
315        ).unwrap(),
316        // drop block
317        parse2(
318            quote! {
319                impl Drop for #closure_name {
320                    fn drop(&mut self) {
321                        unsafe {
322                            #release_name(self)
323                        }
324                    }
325                }
326            }
327        ).unwrap()
328    ]
329}
330
331fn gen_drop_fns(function_name: Ident, ty: Type) -> Item {
332    parse2(quote! {
333        #[no_mangle]
334        pub extern "C" fn #function_name(_ret: #ty) {
335            // Do nothing, drop is implicit.
336        }
337    })
338    .unwrap()
339}