use std::{
collections::HashSet,
io::{BufWriter, Write},
path::PathBuf,
process::{Command, Stdio},
};
use quote::{format_ident, quote, ToTokens};
use syn::{parse2, parse_str, File, FnArg, ForeignItem, Ident, Item, ReturnType, Signature, Type};
pub fn c_closure_header_include_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
const SPECIAL_FN_SUFFIX: &str = "_closure_call";
const SPECIAL_RELEASE_FN_SUFFIX: &str = "_release_rust_return_value";
struct ClosureDefinition {
name: String,
signature: Signature,
}
pub fn enhance_closure_bindings(rust_code: &str) -> String {
let mut tree = parse_str::<File>(rust_code).unwrap();
let mut new_items = vec![];
let mut return_types = HashSet::new();
for item in tree.items.iter_mut() {
let output = call_recurse(item, &mut |item| {
let mut enhance = vec![];
let mut should_omit = false;
if let Item::ForeignMod(foreigners) = item {
let mut new_items = vec![];
for foreign_item in &mut foreigners.items {
if let ForeignItem::Fn(function) = foreign_item {
let function_name = function.sig.ident.to_string();
if function_name.ends_with(SPECIAL_FN_SUFFIX) {
let closure_name = (&function_name
[0..(function_name.len() - SPECIAL_FN_SUFFIX.len())])
.to_string();
enhance.push(ClosureDefinition {
name: closure_name,
signature: function.sig.clone(),
});
new_items.push(foreign_item.clone());
} else if function_name.ends_with(SPECIAL_RELEASE_FN_SUFFIX) {
return_types.insert((
function.sig.ident.clone(),
function.sig.inputs[0].clone(),
));
} else {
new_items.push(foreign_item.clone());
}
}
}
should_omit = new_items.is_empty();
foreigners.items = new_items;
}
if should_omit {
None
} else {
Some(enhance.iter().flat_map(gen_closure_fns).collect())
}
});
if let Some(items) = output {
new_items.push(item.clone());
new_items.extend(items);
}
}
tree.items = new_items;
tree.items.extend(
return_types
.into_iter()
.map(|arg| match arg {
(name, FnArg::Typed(pat_type)) => (name, (*pat_type.ty).clone()),
_ => unreachable!("Functions passed into here should never have a self reference."),
})
.map(|(name, ty)| gen_drop_fns(name, ty)),
);
let tokenified_source = tree.to_token_stream().to_string();
if let Ok(mut rust_fmt_process) = Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
{
if let Some(mut input) = rust_fmt_process.stdin.as_mut().map(BufWriter::new) {
let _ = input.write_all(tokenified_source.as_bytes());
}
}
rust_fmt_process
.wait_with_output()
.map_err(|_| ())
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout).map_err(|_| ())
} else {
Err(())
}
})
.unwrap_or(tokenified_source)
} else {
tokenified_source
}
}
fn call_recurse<F: FnMut(&mut Item) -> Option<Vec<Item>>>(
item: &mut Item,
f: &mut F,
) -> Option<Vec<Item>> {
if let Item::Mod(mmod) = item {
if let Some(t) = mmod.content.as_mut() {
let new_items = t
.1
.iter_mut()
.filter_map(|item| {
call_recurse(item, f).map(|items| Some(item.clone()).into_iter().chain(items))
})
.flatten()
.collect::<Vec<_>>();
t.1 = new_items;
}
}
f(item)
}
fn type_from_output(output: &ReturnType) -> (bool, Type) {
match output {
ReturnType::Default => (false, Type::Verbatim(quote!(()))),
ReturnType::Type(_, ref ty) => (true, (**ty).clone()),
}
}
fn gen_closure_fns(
&ClosureDefinition {
ref name,
ref signature,
}: &ClosureDefinition,
) -> Vec<Item> {
let closure_name = format_ident!("{}Closure", name);
let release_name = format_ident!("{}_closure_release", name);
let args = signature
.inputs
.iter()
.skip(1)
.map(|arg| match arg {
FnArg::Typed(pat_type) => (*pat_type.ty).clone(),
_ => unreachable!("Functions passed into here should never have a self reference."),
})
.map(|a| a.to_token_stream())
.collect::<Vec<_>>();
let arg_idents = (0..args.len())
.map(|i| format_ident!("_p{}", i))
.collect::<Vec<_>>();
let arg_ident_pairs = args
.iter()
.zip(arg_idents.iter())
.map(|(arg, ident)| quote!(#ident: #arg))
.collect::<Vec<_>>();
let (has_return_value, return_type) = type_from_output(&signature.output);
let noop = if has_return_value {
quote!()
} else {
quote! {
pub fn new_noop() -> Self {
Self::fn_not_mut(|#(#arg_idents),*| ())
}
}
};
let return_block = if has_return_value {
quote!(-> #return_type)
} else {
quote!()
};
vec![
parse2(
quote! {
impl #closure_name {
unsafe extern "C" fn f_wrapper<F>(f: *mut ::std::ffi::c_void, #(#arg_ident_pairs),*) #return_block
where
F: FnMut(#(#args),*) #return_block,
{
match ::std::panic::catch_unwind(|| {
let f = &mut *(f as *mut F);
f(#(#arg_idents),*)
}) {
Ok(v) => v,
Err(e) => {
let _r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
eprintln!("c-closures-build: Internal closure panicked, this cannot be passed out the FFI boundary, aborting. Error: {:?}", e);
}));
::std::process::abort()
}
}
}
unsafe extern "C" fn drop_my_box<T>(t: *mut ::std::ffi::c_void) {
Self::drop_me(::std::boxed::Box::<T>::from_raw(t as *mut T));
}
unsafe extern "C" fn drop_me<T>(t: T) {
match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || ::std::mem::drop(t))) {
Ok(()) => (),
Err(e) => {
let _r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
eprintln!("c-closures-build: Internal drop panicked, this cannot be passed out the FFI boundary, aborting. Error: {:?}", e);
}));
::std::process::abort()
}
}
}
pub fn fn_mut<Function>(f: Function) -> Self
where
Function: FnMut(#(#args),*) #return_block,
{
Self {
data: ::std::boxed::Box::into_raw(::std::boxed::Box::new(f)) as *mut ::std::ffi::c_void,
function: Some(Self::f_wrapper::<Function>),
delete_data: Some(Self::drop_my_box::<Function>),
}
}
pub fn fn_not_mut<Function>(f: Function) -> Self
where
Function: Fn(#(#args),*) #return_block,
{
Self {
data: ::std::boxed::Box::into_raw(::std::boxed::Box::new(f)) as *mut ::std::ffi::c_void,
function: Some(Self::f_wrapper::<Function>),
delete_data: Some(Self::drop_my_box::<Function>),
}
}
pub fn fn_once<Function>(f: Function) -> Self
where
Function: FnOnce(#(#args),*) #return_block,
{
let mut f = Some(f);
Self::fn_mut(move |#(#arg_idents),*| match f.take() {
Some(f) => f(#(#arg_idents),*),
None => {
eprintln!("Function marked as single-use was called more than once, the closure will not be called as that would segfault. Aborting.");
::std::process::abort()
}
})
}
#noop
}
}
).unwrap(),
parse2(
quote! {
impl Drop for #closure_name {
fn drop(&mut self) {
unsafe {
#release_name(self)
}
}
}
}
).unwrap()
]
}
fn gen_drop_fns(function_name: Ident, ty: Type) -> Item {
parse2(quote! {
#[no_mangle]
pub extern "C" fn #function_name(_ret: #ty) {
}
})
.unwrap()
}