#![recursion_limit = "128"]
#[macro_use]
extern crate syn;
extern crate proc_macro;
use proc_macro2::Span;
use cpp_common::{flags, kw, RustInvocation, FILE_HASH, LIB_NAME, MSVC_LIB_NAME, OUT_DIR, VERSION};
use std::collections::HashMap;
use std::iter::FromIterator;
use syn::parse::Parser;
use syn::Ident;
use byteorder::{LittleEndian, ReadBytesExt};
use if_rust_version::if_rust_version;
use lazy_static::lazy_static;
use quote::{quote, quote_spanned};
use std::fs::File;
use std::io::{self, BufReader, Read, Seek, SeekFrom};
struct MetaData {
size: usize,
align: usize,
flags: u64,
}
impl MetaData {
fn has_flag(&self, f: u32) -> bool {
self.flags & (1 << f) != 0
}
}
lazy_static! {
static ref METADATA: HashMap<u64, Vec<MetaData>> = {
let file = match open_lib_file() {
Ok(x) => x,
Err(e) => {
#[cfg(not(feature = "docs-only"))]
panic!(
r#"
-- rust-cpp fatal error --
Failed to open the target library file.
NOTE: Did you make sure to add the rust-cpp build script?
{}"#,
e
);
#[cfg(feature = "docs-only")]
{
eprintln!("Error while opening target library: {}", e);
return Default::default();
};
}
};
read_metadata(file).expect(
r#"
-- rust-cpp fatal error --
I/O error while reading metadata from target library file."#,
)
};
}
fn read_metadata(file: File) -> io::Result<HashMap<u64, Vec<MetaData>>> {
let mut file = BufReader::new(file);
let end = {
const AUTO_KEYWORD: &[&[u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC];
let aut = aho_corasick::AhoCorasick::new(AUTO_KEYWORD);
let found = aut.stream_find_iter(&mut file).next().expect(
r#"
-- rust-cpp fatal error --
Struct metadata not present in target library file.
NOTE: Double-check that the version of cpp_build and cpp_macros match"#,
)?;
found.end()
};
file.seek(SeekFrom::Start(end as u64))?;
let mut version_buf = [0; 16];
file.read_exact(&mut version_buf)?;
let version = version_buf
.iter()
.take_while(|b| **b != b'\0')
.map(|b| *b as char)
.collect::<String>();
assert_eq!(
version, VERSION,
r#"
-- rust-cpp fatal error --
Version mismatch between cpp_macros and cpp_build for same crate."#
);
let length = file.read_u64::<LittleEndian>()?;
let mut metadata = HashMap::new();
for _ in 0..length {
let hash = file.read_u64::<LittleEndian>()?;
let size = file.read_u64::<LittleEndian>()? as usize;
let align = file.read_u64::<LittleEndian>()? as usize;
let flags = file.read_u64::<LittleEndian>()? as u64;
metadata
.entry(hash)
.or_insert_with(Vec::new)
.push(MetaData { size, align, flags });
}
Ok(metadata)
}
fn open_lib_file() -> io::Result<File> {
if let Ok(file) = File::open(OUT_DIR.join(MSVC_LIB_NAME)) {
Ok(file)
} else {
File::open(OUT_DIR.join(LIB_NAME))
}
}
fn find_all_rust_macro(
input: syn::parse::ParseStream,
) -> Result<Vec<RustInvocation>, syn::parse::Error> {
let mut r = Vec::<RustInvocation>::new();
while !input.is_empty() {
if input.peek(kw::rust) {
if let Ok(ri) = input.parse::<RustInvocation>() {
r.push(ri);
}
} else if input.peek(syn::token::Brace) {
let c;
braced!(c in input);
r.extend(find_all_rust_macro(&c)?);
} else if input.peek(syn::token::Paren) {
let c;
parenthesized!(c in input);
r.extend(find_all_rust_macro(&c)?);
} else if input.peek(syn::token::Bracket) {
let c;
bracketed!(c in input);
r.extend(find_all_rust_macro(&c)?);
} else {
input.parse::<proc_macro2::TokenTree>()?;
}
}
Ok(r)
}
fn extract_original_macro(input: &syn::DeriveInput) -> Option<proc_macro2::TokenStream> {
#[derive(Default)]
struct Finder(Option<proc_macro2::TokenStream>);
impl<'ast> syn::visit::Visit<'ast> for Finder {
fn visit_macro(&mut self, mac: &'ast syn::Macro) {
if mac.path.segments.len() == 1 && mac.path.segments[0].ident == "stringify" {
self.0 = Some(mac.tokens.clone());
}
}
}
let mut f = Finder::default();
syn::visit::visit_derive_input(&mut f, &input);
f.0
}
#[proc_macro_derive(__cpp_internal_closure)]
pub fn expand_internal(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
assert_eq!(
env!("CARGO_PKG_VERSION"),
VERSION,
"Internal Error: mismatched cpp_common and cpp_macros versions"
);
let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap();
let closure = match syn::parse2::<cpp_common::Closure>(input) {
Ok(x) => x,
Err(err) => return err.to_compile_error().into(),
};
let size_data = match METADATA.get(&closure.sig.name_hash()) {
Some(x) => x,
None => {
#[cfg(not(feature = "docs-only"))]
return quote!(compile_error! {
r#"This cpp! macro is not found in the library's rust-cpp metadata.
NOTE: Only cpp! macros found directly in the program source will be parsed -
NOTE: They cannot be generated by macro expansion."#})
.into();
#[cfg(feature = "docs-only")]
{
return quote! {
macro_rules! __cpp_closure_impl {
($($x:tt)*) => { panic!("docs-only"); }
}
}
.into();
};
}
};
let mut extern_params = Vec::new();
let mut tt_args = Vec::new();
let mut call_args = Vec::new();
for (i, capture) in closure.sig.captures.iter().enumerate() {
let written_name = &capture.name;
let span = written_name.span();
let mac_name = Ident::new(&format!("var_{}", written_name), span);
let mac_cty = Ident::new(&format!("cty_{}", written_name), span);
let MetaData { size, align, .. } = size_data[i + 1];
let sizeof_msg = format!(
"size_of for argument `{}` does not match between c++ and \
rust",
&capture.name
);
let alignof_msg = format!(
"align_of for argument `{}` does not match between c++ and \
rust",
&capture.name
);
let assertion = quote_spanned! {span=>
if false {
::core::mem::transmute::<_, [u8; #size]>(
::core::ptr::read(&$#mac_name));
}
assert!(::core::mem::size_of_val(&$#mac_name) == #size,
#sizeof_msg);
assert!(::core::mem::align_of_val(&$#mac_name) == #align,
#alignof_msg);
};
let mb_mut = if capture.mutable {
quote_spanned!(span=> mut)
} else {
quote!()
};
let ptr = if capture.mutable {
quote_spanned!(span=> *mut)
} else {
quote_spanned!(span=> *const)
};
let arg_name = Ident::new(&format!("arg_{}", written_name), span);
extern_params.push(quote_spanned!(span=> #arg_name : #ptr u8));
tt_args.push(quote_spanned!(span=> #mb_mut $#mac_name : ident as $#mac_cty : tt));
call_args.push(quote_spanned!(span=> {
#assertion
&#mb_mut $#mac_name as #ptr _ as #ptr u8
}));
}
let extern_name = closure.sig.extern_name();
let ret_ty = &closure.sig.ret;
let MetaData {
size: ret_size,
align: ret_align,
flags,
} = size_data[0];
let is_void = closure.sig.cpp == "void";
let decl = if is_void {
quote! {
fn #extern_name(#(#extern_params),*);
}
} else {
quote! {
fn #extern_name(#(#extern_params,)* _result: *mut #ret_ty);
}
};
let call = if is_void {
assert!(ret_size == 0, "`void` should have a size of 0!");
quote! {
#extern_name(#(#call_args),*);
#[cfg_attr(feature = "cargo-clippy", allow(useless_transmute))]
::core::mem::transmute::<(), (#ret_ty)>(())
}
} else {
let assert_size = quote! {
if false {
const _assert_size: [(); #ret_size] = [(); ::core::mem::size_of::<#ret_ty>()];
const _assert_align: [(); #ret_align] = [(); ::core::mem::align_of::<#ret_ty>()];
}
};
if_rust_version!(>= 1.36 { quote!(
#assert_size
let mut result = ::core::mem::MaybeUninit::<#ret_ty>::uninit();
#extern_name(#(#call_args,)* result.as_mut_ptr());
result.assume_init()
)} else { quote!(
#assert_size
let mut result: #ret_ty = ::core::mem::uninitialized();
#extern_name(#(#call_args,)* &mut result);
result
)})
};
let input = proc_macro2::TokenStream::from_iter([closure.body].iter().cloned());
let rust_invocations = find_all_rust_macro.parse2(input).expect("rust! macro");
let init_callbacks = if !rust_invocations.is_empty() {
let rust_cpp_callbacks = Ident::new(
&format!("rust_cpp_callbacks{}", *FILE_HASH),
Span::call_site(),
);
let offset = (flags >> 32) as isize;
let callbacks: Vec<Ident> = rust_invocations.iter().map(|x| x.id.clone()).collect();
quote! {
use ::std::sync::Once;
static INIT_INVOCATIONS: Once = Once::new();
INIT_INVOCATIONS.call_once(|| {
extern "C" {
#[no_mangle]
static mut #rust_cpp_callbacks: *const ::std::os::raw::c_void;
}
let callbacks_array : *mut *const ::std::os::raw::c_void = &mut #rust_cpp_callbacks;
let mut offset = #offset;
#(
offset += 1;
*callbacks_array.offset(offset - 1) = #callbacks as *const ::std::os::raw::c_void;
)*
});
}
} else {
quote!()
};
let result = quote! {
extern "C" {
#decl
}
macro_rules! __cpp_closure_impl {
(#(#tt_args),*) => {
{
#init_callbacks
#call
}
}
}
};
result.into()
}
#[proc_macro_derive(__cpp_internal_class)]
pub fn expand_wrap_class(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap();
let class = match ::syn::parse2::<cpp_common::Class>(input) {
Ok(x) => x,
Err(err) => return err.to_compile_error().into(),
};
let hash = class.name_hash();
let class_name = class.name.clone();
let size_data = match METADATA.get(&hash) {
Some(x) => x,
None => {
#[cfg(not(feature = "docs-only"))]
return quote!(compile_error! {
r#"This cpp_class! macro is not found in the library's rust-cpp metadata.
NOTE: Only cpp_class! macros found directly in the program source will be parsed -
NOTE: They cannot be generated by macro expansion."#})
.into();
#[cfg(feature = "docs-only")]
{
let mut result = quote! {
#[doc(hidden)]
impl ::cpp::CppTrait for #class_name {
type BaseType = usize;
const ARRAY_SIZE: usize = 1;
const CPP_TYPE: &'static str = stringify!(#class_name);
}
#[doc = "NOTE: this trait will only be enabled if the C++ underlying type is trivially copyable"]
impl ::core::marker::Copy for #class_name { }
#[doc = "NOTE: this trait will only be enabled if the C++ underlying type is copyable"]
impl ::core::clone::Clone for #class_name { fn clone(&self) -> Self { panic!("docs-only") } }
#[doc = "NOTE: this trait will only be enabled if the C++ underlying type is default constructible"]
impl ::core::default::Default for #class_name { fn default() -> Self { panic!("docs-only") } }
};
if class.derives("PartialEq") {
result = quote! { #result
impl ::core::cmp::PartialEq for #class_name {
fn eq(&self, other: &#class_name) -> bool { panic!("docs-only") }
}
};
}
if class.derives("PartialOrd") {
result = quote! { #result
impl ::core::cmp::PartialOrd for #class_name {
fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> {
panic!("docs-only")
}
}
};
}
if class.derives("Ord") {
result = quote! { #result
impl ::core::cmp::Ord for #class_name {
fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering {
panic!("docs-only")
}
}
};
}
return result.into();
};
}
};
let (size, align) = (size_data[0].size, size_data[0].align);
let base_type = match align {
1 => quote!(u8),
2 => quote!(u16),
4 => quote!(u32),
8 => quote!(u64),
_ => panic!("unsupported alignment"),
};
let destructor_name = Ident::new(&format!("__cpp_destructor_{}", hash), Span::call_site());
let copyctr_name = Ident::new(&format!("__cpp_copy_{}", hash), Span::call_site());
let defaultctr_name = Ident::new(&format!("__cpp_default_{}", hash), Span::call_site());
let mut result = quote! {
#[doc(hidden)]
impl ::cpp::CppTrait for #class_name {
type BaseType = #base_type;
const ARRAY_SIZE: usize = #size / #align;
const CPP_TYPE: &'static str = stringify!(#class_name);
}
};
if !size_data[0].has_flag(flags::IS_TRIVIALLY_DESTRUCTIBLE) {
result = quote! { #result
impl ::core::ops::Drop for #class_name {
fn drop(&mut self) {
unsafe {
extern "C" { fn #destructor_name(_: *mut #class_name); }
#destructor_name(&mut *self);
}
}
}
};
};
if size_data[0].has_flag(flags::IS_COPY_CONSTRUCTIBLE) {
if !size_data[0].has_flag(flags::IS_TRIVIALLY_COPYABLE) && !class.derives("Copy") {
let call_construct = if_rust_version!(>= 1.36 { quote!(
let mut result = ::core::mem::MaybeUninit::<Self>::uninit();
#copyctr_name(& *self, result.as_mut_ptr());
result.assume_init()
)} else { quote!(
let mut result : Self = ::core::mem::uninitialized();
#copyctr_name(& *self, &mut result);
result
)});
result = quote! { #result
impl ::core::clone::Clone for #class_name {
fn clone(&self) -> Self {
unsafe {
extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); }
#call_construct
}
}
}
};
} else {
result = quote! { #result
impl ::core::marker::Copy for #class_name { }
impl ::core::clone::Clone for #class_name {
fn clone(&self) -> Self { *self }
}
};
};
} else if class.derives("Clone") {
panic!("C++ class is not copyable");
}
if size_data[0].has_flag(flags::IS_DEFAULT_CONSTRUCTIBLE) {
let call_construct = if_rust_version!(>= 1.36 { quote!(
let mut result = ::core::mem::MaybeUninit::<Self>::uninit();
#defaultctr_name(result.as_mut_ptr());
result.assume_init()
)} else { quote!(
let mut result : Self = ::core::mem::uninitialized();
#defaultctr_name(&mut result);
result
)});
result = quote! { #result
impl ::core::default::Default for #class_name {
fn default() -> Self {
unsafe {
extern "C" { fn #defaultctr_name(dst: *mut #class_name); }
#call_construct
}
}
}
};
} else if class.derives("Default") {
panic!("C++ class is not default constructible");
}
if class.derives("PartialEq") {
let equal_name = Ident::new(&format!("__cpp_equal_{}", hash), Span::call_site());
result = quote! { #result
impl ::core::cmp::PartialEq for #class_name {
fn eq(&self, other: &#class_name) -> bool {
unsafe {
extern "C" { fn #equal_name(a: *const #class_name, b: *const #class_name) -> bool; }
#equal_name(& *self, other)
}
}
}
};
}
if class.derives("PartialOrd") {
let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site());
let f = |func, cmp| {
quote! {
fn #func(&self, other: &#class_name) -> bool {
unsafe {
extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
#compare_name(& *self, other, #cmp) != 0
}
}
}
};
let lt = f(quote! {lt}, -2);
let gt = f(quote! {gt}, 2);
let le = f(quote! {le}, -1);
let ge = f(quote! {ge}, 1);
result = quote! { #result
impl ::core::cmp::PartialOrd for #class_name {
#lt #gt #le #ge
fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> {
use ::core::cmp::Ordering;
unsafe {
extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
::core::option::Option::Some(match #compare_name(& *self, other, 0) {
-1 => Ordering::Less,
0 => Ordering::Equal,
1 => Ordering::Greater,
_ => panic!()
})
}
}
}
};
}
if class.derives("Ord") {
let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site());
result = quote! { #result
impl ::core::cmp::Ord for #class_name {
fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering {
unsafe {
use ::core::cmp::Ordering;
extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
match #compare_name(& *self, other, 0) {
-1 => Ordering::Less,
0 => Ordering::Equal,
1 => Ordering::Greater,
_ => panic!()
}
}
}
}
};
}
if class.derives("Hash") {
panic!("Deriving from Hash is not implemented")
};
if class.derives("Debug") {
panic!("Deriving from Debug is not implemented")
};
result.into()
}