summaryrefslogtreecommitdiffstats
path: root/ffi-macros
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-01-22 13:08:12 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-01-22 17:46:40 +0100
commit20e89f09aa9cae3dd56ffff1b45d88ddbf3d0acd (patch)
treec5a84b4a2d5fe0debb5e5deb545906634a9265a6 /ffi-macros
parent0cb819a1c3cc127c05c03a7220b5aa11a3ef1d44 (diff)
ffi-macros: Add ffi_wrapper_type that derives functions.
- Note that the wrapper type is not actually used for now, the functions still operate on the original type. But, this lets us derive functions, and for that we merge it now. - See #166.
Diffstat (limited to 'ffi-macros')
-rw-r--r--ffi-macros/Cargo.toml1
-rw-r--r--ffi-macros/src/lib.rs266
2 files changed, 267 insertions, 0 deletions
diff --git a/ffi-macros/Cargo.toml b/ffi-macros/Cargo.toml
index 152ced79..e323b452 100644
--- a/ffi-macros/Cargo.toml
+++ b/ffi-macros/Cargo.toml
@@ -18,6 +18,7 @@ gitlab = { repository = "sequoia-pgp/sequoia" }
maintenance = { status = "actively-developed" }
[dependencies]
+lazy_static = "1.0.0"
proc-macro2 = "0.4"
quote = "0.6"
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()
+ }
+ }
+}