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}