summaryrefslogtreecommitdiffstats
path: root/ffi-macros
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-01-16 13:43:07 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-01-16 13:52:53 +0100
commit83873fca4714be6ceb264d6d1ca7d56b4fda8150 (patch)
treef3088db6dc878b3413e4657fc828d998784eb1be /ffi-macros
parent977fae00f57ca87fa08568a3ce0f55a5382af13a (diff)
ffi-macros: New crate.
- This crate contains macros for Sequoia's FFI crate(s). Having it in a separate crate means that we can share it when we split the FFI crate into two. - More importantly, we need a separate crate if we want to create procedural macros. - As first macro, this patch adds ffi_catch_abort that wraps a function's body in a catch_unwind block, aborting on panics.
Diffstat (limited to 'ffi-macros')
-rw-r--r--ffi-macros/Cargo.toml29
-rw-r--r--ffi-macros/README.md1
-rw-r--r--ffi-macros/src/lib.rs87
3 files changed, 117 insertions, 0 deletions
diff --git a/ffi-macros/Cargo.toml b/ffi-macros/Cargo.toml
new file mode 100644
index 00000000..152ced79
--- /dev/null
+++ b/ffi-macros/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "sequoia-ffi-macros"
+description = "Macros for writing the C API for Sequoia"
+version = "0.3.0"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+ "Kai Michaelis <kai@sequoia-pgp.org>",
+ "Neal H. Walfield <neal@sequoia-pgp.org>",
+]
+documentation = "https://docs.sequoia-pgp.org/sequoia_ffi"
+homepage = "https://sequoia-pgp.org/"
+repository = "https://gitlab.com/sequoia-pgp/sequoia"
+readme = "README.md"
+license = "GPL-3.0"
+
+[badges]
+gitlab = { repository = "sequoia-pgp/sequoia" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+proc-macro2 = "0.4"
+quote = "0.6"
+
+[dependencies.syn]
+version = "0.15"
+features = ["full"]
+
+[lib]
+proc-macro = true
diff --git a/ffi-macros/README.md b/ffi-macros/README.md
new file mode 100644
index 00000000..f7f818a2
--- /dev/null
+++ b/ffi-macros/README.md
@@ -0,0 +1 @@
+This is a set of macros to help with the FFI glue for Sequoia.
diff --git a/ffi-macros/src/lib.rs b/ffi-macros/src/lib.rs
new file mode 100644
index 00000000..255c56c8
--- /dev/null
+++ b/ffi-macros/src/lib.rs
@@ -0,0 +1,87 @@
+//! Common macros for Sequoia's FFI crates.
+
+extern crate syn;
+extern crate quote;
+extern crate proc_macro;
+extern crate proc_macro2;
+
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+
+use quote::{quote, ToTokens};
+
+/// Wraps a function's body in a catch_unwind block, aborting on
+/// panics.
+///
+/// Unwinding the stack across the FFI boundary is [undefined
+/// behavior]. We therefore need to wrap every FFI function's body
+/// with [catch_unwind]. This macro does that in an unobtrusive
+/// manner.
+///
+/// [undefined behavior]: https://doc.rust-lang.org/nomicon/unwinding.html
+/// [catch_unwind]: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
+///
+/// # Example
+///
+/// ```rust,ignore
+/// #[ffi_catch_abort]
+/// #[no_mangle]
+/// pub extern "system" fn sahnetorte() {
+/// assert_eq!(2 * 3, 4); // See what happens...
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn ffi_catch_abort(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ // Parse tokens into a function declaration.
+ let fun = syn::parse_macro_input!(item as syn::ItemFn);
+
+ // Extract all information from the parsed function that we need
+ // to compose the new function.
+ let attrs = fun.attrs.iter()
+ .fold(TokenStream2::new(),
+ |mut acc, attr| {
+ acc.extend(attr.clone().into_token_stream());
+ acc
+ });
+ let vis = &fun.vis;
+ let constness = &fun.constness;
+ let unsafety = &fun.unsafety;
+ let asyncness = &fun.asyncness;
+ let abi = &fun.abi;
+ let ident = &fun.ident;
+
+ let decl = &fun.decl;
+ let fn_token = &decl.fn_token;
+ let fn_generics = &decl.generics;
+ let fn_out = &decl.output;
+ let fn_in = &decl.inputs;
+
+ let block = &fun.block;
+
+ // We wrap the functions body into an catch_unwind, asserting that
+ // all variables captured by the closure are unwind safe. This is
+ // safe because we terminate the process on panics, therefore no
+ // inconsistencies can be observed.
+ let expanded = quote! {
+ #attrs #vis #constness #unsafety #asyncness #abi
+ #fn_token #ident #fn_generics ( #fn_in ) #fn_out
+ {
+ match ::std::panic::catch_unwind(
+ ::std::panic::AssertUnwindSafe(|| #fn_out #block))
+ {
+ Ok(v) => v,
+ Err(p) => {
+ unsafe {
+ ::libc::abort();
+ }
+ },
+ }
+ }
+ };
+
+ // To debug problems with the generated code, just eprintln it:
+ //
+ // eprintln!("{}", expanded);
+
+ expanded.into()
+}