summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ffi-macros/Cargo.toml1
-rw-r--r--ffi-macros/src/lib.rs117
-rw-r--r--openpgp-ffi/examples/.gitignore1
-rw-r--r--openpgp-ffi/examples/Makefile2
-rw-r--r--openpgp-ffi/examples/type-safety-demo.c28
5 files changed, 134 insertions, 15 deletions
diff --git a/ffi-macros/Cargo.toml b/ffi-macros/Cargo.toml
index e323b452..e37cab34 100644
--- a/ffi-macros/Cargo.toml
+++ b/ffi-macros/Cargo.toml
@@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
lazy_static = "1.0.0"
+nettle = "2.0"
proc-macro2 = "0.4"
quote = "0.6"
diff --git a/ffi-macros/src/lib.rs b/ffi-macros/src/lib.rs
index 1f57275b..0d659687 100644
--- a/ffi-macros/src/lib.rs
+++ b/ffi-macros/src/lib.rs
@@ -3,9 +3,12 @@
#![recursion_limit="256"]
use std::collections::HashMap;
+use std::io::Write;
extern crate lazy_static;
use lazy_static::lazy_static;
+extern crate nettle;
+use nettle::hash::Hash;
extern crate syn;
use syn::spanned::Spanned;
extern crate quote;
@@ -109,7 +112,7 @@ pub fn ffi_catch_abort(_attr: TokenStream, item: TokenStream) -> TokenStream {
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 st = syn::parse_macro_input!(input as syn::ItemStruct);
let mut name = None;
let mut prefix = None;
@@ -154,29 +157,37 @@ pub fn ffi_wrapper_type(args: TokenStream, input: TokenStream) -> TokenStream {
let name = name.unwrap_or(ident2c(&st.ident));
let prefix = prefix.unwrap_or("".into());
- // Parse the wrapped type.
+ // Parse the type of the wrapped object.
+ let argument_span = st.fields.span();
let wrapped_type = match &st.fields {
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() != 1 {
return
- syn::Error::new(st.fields.span(),
+ syn::Error::new(argument_span,
"expected a single field")
.to_compile_error().into();
}
fields.unnamed.first().unwrap().value().ty.clone()
},
_ => return
- syn::Error::new(st.fields.span(),
+ syn::Error::new(argument_span,
format!("expected tuple struct, try: {}(...)",
- st.ident))
+ st.ident))
.to_compile_error().into(),
};
+ // We now assemble the derived functions.
+ let mut impls = TokenStream2::new();
+
+ // First, we derive the conversion functions. As a side-effect,
+ // this function injects fields into the struct definition.
+ impls.extend(
+ derive_conversion_functions(&mut st, &prefix, &name, &wrapped_type));
+
+ // Now, we derive both the default and the requested functions.
let default_derives: &[DeriveFn] = &[
derive_free,
- derive_conversion_traits,
];
- let mut impls = TokenStream2::new();
for dfn in derive.into_iter().chain(default_derives.iter()) {
impls.extend(dfn(st.span(), &prefix, &name,
&st.ident, &wrapped_type));
@@ -240,36 +251,114 @@ fn derive_functions() -> &'static HashMap<&'static str, DeriveFn>
&MAP
}
+/// Produces a deterministic hash of the given identifier.
+fn hash_ident(i: &syn::Ident) -> u64 {
+ let mut hash = ::nettle::hash::Sha256::default();
+ write!(hash, "{}", i).unwrap();
+
+ let mut buf = [0; 8];
+ hash.digest(&mut buf);
+
+ buf.iter().fold(0, |acc, b| (acc << 8) + (*b as u64))
+}
+
/// Derives prefix_name_conversion_trait.
-fn derive_conversion_traits(_: proc_macro2::Span, _: &str, _: &str,
- wrapper: &syn::Ident, wrapped: &syn::Type)
- -> TokenStream2
+fn derive_conversion_functions(st: &mut syn::ItemStruct, _: &str, _: &str,
+ wrapped: &syn::Type)
+ -> TokenStream2
{
+ let wrapper = st.ident.clone();
+
+ // We now inject a field into the struct definition. This tag
+ // uniquely identifies this wrapper at runtime.
+
+ // We use a word sized unsigned type to avoid alignment issues.
+ let tag_type = syn::parse_quote!(u64);
+
+ // The value is a compile-time constant.
+ let magic_value = hash_ident(&wrapper);
+
+ // Inject the tag.
+ let argument_span = st.fields.span();
+ match &mut st.fields {
+ syn::Fields::Unnamed(fields) => {
+ if fields.unnamed.len() != 1 {
+ return
+ syn::Error::new(argument_span,
+ "expected a single field")
+ .to_compile_error().into();
+ }
+ fields.unnamed.push(
+ syn::Field {
+ attrs: vec![],
+ vis: syn::Visibility::Inherited,
+ ident: None,
+ colon_token: None,
+ ty: tag_type,
+ }
+ );
+ },
+ _ => return
+ syn::Error::new(argument_span,
+ format!("expected tuple struct, try: {}(...)",
+ st.ident))
+ .to_compile_error().into(),
+ };
+
quote! {
+ impl #wrapper {
+ fn assert_tag(&self) {
+ if self.1 != #magic_value {
+ panic!("FFI contract violation: Wrong parameter type");
+ }
+ }
+ }
+
use MoveFromRaw;
impl MoveFromRaw<#wrapped> for *mut #wrapper {
fn move_from_raw(self) -> #wrapped {
- ffi_param_move!(self).0
+ if self.is_null() {
+ panic!("FFI contract violation: Parameter is NULL");
+ }
+ let wrapper = unsafe {
+ Box::from_raw(self)
+ };
+ wrapper.assert_tag();
+ wrapper.0
}
}
use RefRaw;
impl RefRaw<#wrapped> for *const #wrapper {
fn ref_raw(self) -> &'static #wrapped {
- &ffi_param_ref!(self).0
+ if self.is_null() {
+ panic!("FFI contract violation: Parameter is NULL");
+ }
+ let wrapper = unsafe {
+ &(*self)
+ };
+ wrapper.assert_tag();
+ &wrapper.0
}
}
use RefMutRaw;
impl RefMutRaw<#wrapped> for *mut #wrapper {
fn ref_mut_raw(self) -> &'static mut #wrapped {
- &mut ffi_param_ref_mut!(self).0
+ if self.is_null() {
+ panic!("FFI contract violation: Parameter is NULL");
+ }
+ let wrapper = unsafe {
+ &mut (*self)
+ };
+ wrapper.assert_tag();
+ &mut wrapper.0
}
}
impl #wrapper {
fn wrap(obj: #wrapped) -> *mut #wrapper {
- Box::into_raw(Box::new(#wrapper(obj)))
+ Box::into_raw(Box::new(#wrapper(obj, #magic_value)))
}
}
diff --git a/openpgp-ffi/examples/.gitignore b/openpgp-ffi/examples/.gitignore
index 6a47c497..c1833f90 100644
--- a/openpgp-ffi/examples/.gitignore
+++ b/openpgp-ffi/examples/.gitignore
@@ -3,3 +3,4 @@ encrypt-for
example
parser
reader
+type-safety-demo
diff --git a/openpgp-ffi/examples/Makefile b/openpgp-ffi/examples/Makefile
index 29408634..d935ea52 100644
--- a/openpgp-ffi/examples/Makefile
+++ b/openpgp-ffi/examples/Makefile
@@ -5,7 +5,7 @@ CARGO_TARGET_DIR ?= $(shell pwd)/../../target
# We currently only support absolute paths.
CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR))
-TARGETS = example reader parser encrypt-for armor
+TARGETS = example reader parser encrypt-for armor type-safety-demo
CFLAGS = -I../include -O0 -g -Wall -Werror
LDFLAGS = -L$(CARGO_TARGET_DIR)/debug -lsequoia_openpgp_ffi
diff --git a/openpgp-ffi/examples/type-safety-demo.c b/openpgp-ffi/examples/type-safety-demo.c
new file mode 100644
index 00000000..ad4870e2
--- /dev/null
+++ b/openpgp-ffi/examples/type-safety-demo.c
@@ -0,0 +1,28 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sequoia/openpgp.h>
+
+int
+main (int argc, char **argv)
+{
+ pgp_keyid_t keyid = pgp_keyid_from_hex ("BBBBBBBBBBBBBBBB");
+
+ // Let's violate The Rules and forge a reference!
+ pgp_fingerprint_t fingerprint = (pgp_fingerprint_t) keyid;
+
+ // And use it!
+ printf ("%s", pgp_fingerprint_to_string (fingerprint));
+
+ // Always clean up though.
+ pgp_keyid_free (keyid);
+ return 0;
+}