summaryrefslogtreecommitdiffstats
path: root/ffi-macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'ffi-macros/src')
-rw-r--r--ffi-macros/src/lib.rs266
1 files changed, 266 insertions, 0 deletions
diff --git a/ffi-macros/src/lib.rs b/ffi-macros/src/lib.rs
index 6a1cfe80..4c5df357 100644
--- a/ffi-macros/src/lib.rs
+++ b/ffi-macros/src/lib.rs
@@ -1,6 +1,11 @@
//! Common macros for Sequoia's FFI crates.
+use std::collections::HashMap;
+
+extern crate lazy_static;
+use lazy_static::lazy_static;
extern crate syn;
+use syn::spanned::Spanned;
extern crate quote;
extern crate proc_macro;
extern crate proc_macro2;
@@ -87,3 +92,264 @@ pub fn ffi_catch_abort(_attr: TokenStream, item: TokenStream) -> TokenStream {
expanded.into()
}
+
+/// Derives FFI functions for a wrapper type.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// /// Holds a fingerprint.
+/// #[::ffi_wrapper_type(prefix = "pgp_",
+/// derive = "Clone, Debug, Display, PartialEq, Hash")]
+/// pub struct Fingerprint(openpgp::Fingerprint);
+/// ```
+#[proc_macro_attribute]
+pub fn ffi_wrapper_type(args: TokenStream, input: TokenStream) -> TokenStream {
+ // Parse tokens into a function declaration.
+ let args = syn::parse_macro_input!(args as syn::AttributeArgs);
+ let st = syn::parse_macro_input!(input as syn::ItemStruct);
+
+ let mut name = None;
+ let mut prefix = None;
+ let mut derive = Vec::new();
+
+ for arg in args.iter() {
+ match arg {
+ syn::NestedMeta::Meta(syn::Meta::NameValue(ref mnv)) => {
+ let value = match mnv.lit {
+ syn::Lit::Str(ref s) => s.value(),
+ _ => unreachable!(),
+ };
+ match mnv.ident.to_string().as_ref() {
+ "name" => name = Some(value),
+ "prefix" => prefix = Some(value),
+ "derive" => {
+ for ident in value.split(",").map(|d| d.trim()
+ .to_string()) {
+ if let Some(f) = derive_functions().get::<str>(&ident) {
+ derive.push(f);
+ } else {
+ return syn::Error::new(
+ mnv.ident.span(),
+ format!("unknown derive: {}", ident))
+ .to_compile_error().into();
+ }
+ }
+ },
+ name => return
+ syn::Error::new(mnv.ident.span(),
+ format!("unexpected parameter: {}",
+ name))
+ .to_compile_error().into(),
+ }
+ },
+ _ => return syn::Error::new(arg.span(),
+ "expected key = \"value\" pair")
+ .to_compile_error().into(),
+ }
+ }
+
+ let name = name.unwrap_or(ident2c(&st.ident));
+ let prefix = prefix.unwrap_or("".into());
+
+ // Parse the wrapped type.
+ let wrapped_type = match &st.fields {
+ syn::Fields::Unnamed(fields) => {
+ if fields.unnamed.len() != 1 {
+ return
+ syn::Error::new(st.fields.span(),
+ "expected a single field")
+ .to_compile_error().into();
+ }
+ fields.unnamed.first().unwrap().value().ty.clone()
+ },
+ _ => return
+ syn::Error::new(st.fields.span(),
+ format!("expected tuple struct, try: {}(...)",
+ st.ident))
+ .to_compile_error().into(),
+ };
+
+ let default_derives: &[DeriveFn] = &[
+ derive_free,
+ ];
+ let mut impls = TokenStream2::new();
+ for dfn in derive.into_iter().chain(default_derives.iter()) {
+ impls.extend(dfn(st.span(), &prefix, &name, &wrapped_type));
+ }
+
+ let expanded = quote! {
+ #st
+
+ // The derived functions.
+ #impls
+ };
+
+ // To debug problems with the generated code, just eprintln it:
+ //
+ // eprintln!("{}", expanded);
+
+ expanded.into()
+}
+
+/// Derives the C type from the Rust type.
+fn ident2c(ident: &syn::Ident) -> String {
+ let mut s = String::new();
+ for (i, c) in ident.to_string().chars().enumerate() {
+ if c.is_uppercase() {
+ if i > 0 {
+ s += "_";
+ }
+ s += &c.to_lowercase().to_string();
+ } else {
+ s += &c.to_string();
+ }
+ }
+ s
+}
+
+#[test]
+fn ident2c_tests() {
+ let span = proc_macro2::Span::call_site();
+ assert_eq!(&ident2c(&syn::Ident::new("Fingerprint", span)), "fingerprint");
+ assert_eq!(&ident2c(&syn::Ident::new("PacketPile", span)), "packet_pile");
+}
+
+/// Describes our custom derive functions.
+type DeriveFn = fn(proc_macro2::Span, &str, &str, &syn::Type) -> TokenStream2;
+
+/// Maps trait names to our generator functions.
+fn derive_functions() -> &'static HashMap<&'static str, DeriveFn>
+{
+ lazy_static! {
+ static ref MAP: HashMap<&'static str, DeriveFn> = {
+ let mut h = HashMap::new();
+ h.insert("Clone", derive_clone as DeriveFn);
+ h.insert("PartialEq", derive_equal as DeriveFn);
+ h.insert("Hash", derive_hash as DeriveFn);
+ h.insert("Display", derive_to_string as DeriveFn);
+ h.insert("Debug", derive_debug as DeriveFn);
+ h
+ };
+ }
+ &MAP
+}
+
+/// Derives prefix_name_free.
+fn derive_free(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_free", prefix, name),
+ span);
+ quote! {
+ /// Frees this object.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (this: Option<&mut #ty>) {
+ if let Some(ptr) = this {
+ unsafe {
+ drop(Box::from_raw(ptr))
+ }
+ }
+ }
+ }
+}
+
+/// Derives prefix_name_clone.
+fn derive_clone(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_clone", prefix, name),
+ span);
+ quote! {
+ /// Clones this object.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (this: *const #ty)
+ -> *mut #ty {
+ let this = ffi_param_ref!(this);
+ box_raw!(this.clone())
+ }
+ }
+}
+
+/// Derives prefix_name_equal.
+fn derive_equal(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_equal", prefix, name),
+ span);
+ quote! {
+ /// Compares objects.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (a: *const #ty,
+ b: *const #ty)
+ -> bool {
+ let a = ffi_param_ref!(a);
+ let b = ffi_param_ref!(b);
+ a == b
+ }
+ }
+}
+
+
+/// Derives prefix_name_to_string.
+fn derive_to_string(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_to_string", prefix, name),
+ span);
+ quote! {
+ /// Returns a human readable description of this object
+ /// intended for communication with end users.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (this: *const #ty)
+ -> *mut ::libc::c_char {
+ let this = ffi_param_ref!(this);
+ ffi_return_string!(format!("{}", this))
+ }
+ }
+}
+
+/// Derives prefix_name_debug.
+fn derive_debug(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_debug", prefix, name),
+ span);
+ quote! {
+ /// Returns a human readable description of this object
+ /// suitable for debugging.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (this: *const #ty)
+ -> *mut ::libc::c_char {
+ let this = ffi_param_ref!(this);
+ ffi_return_string!(format!("{:?}", this))
+ }
+ }
+}
+
+/// Derives prefix_name_hash.
+fn derive_hash(span: proc_macro2::Span, prefix: &str, name: &str,
+ ty: &syn::Type)
+ -> TokenStream2
+{
+ let ident = syn::Ident::new(&format!("{}{}_hash", prefix, name),
+ span);
+ quote! {
+ /// Hashes this object.
+ #[::ffi_catch_abort] #[no_mangle]
+ pub extern "system" fn #ident (this: *const #ty)
+ -> ::libc::uint64_t {
+ use ::std::hash::{Hash, Hasher};
+
+ let this = ffi_param_ref!(this);
+ let mut hasher = ::build_hasher();
+ this.hash(&mut hasher);
+ hasher.finish()
+ }
+ }
+}