1//! This crate is the `cpp` procedural macro implementation. It is useless
2//! without the companion crates `cpp`, and `cpp_build`.
3//!
4//! For more information, see the [`cpp` crate module level
5//! documentation](https://docs.rs/cpp).
6#![recursion_limit = "128"]
7
8#[macro_use]
9extern crate syn;
10extern crate proc_macro;
11use proc_macro2::Span;
12
13use cpp_common::{flags, kw, RustInvocation, FILE_HASH, LIB_NAME, MSVC_LIB_NAME, OUT_DIR, VERSION};
14use std::collections::HashMap;
15use std::iter::FromIterator;
16use syn::parse::Parser;
17use syn::Ident;
18
19use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
20use lazy_static::lazy_static;
21use quote::{quote, quote_spanned};
22use std::fs::File;
23use std::io::{self, BufReader, Read, Seek, SeekFrom};
24
25struct MetaData {
26 size: usize,
27 align: usize,
28 flags: u64,
29}
30impl MetaData {
31 fn has_flag(&self, f: u32) -> bool {
32 self.flags & (1 << f) != 0
33 }
34}
35
36lazy_static! {
37 static ref METADATA: HashMap<u64, Vec<MetaData>> = {
38 let file = match open_lib_file() {
39 Ok(x) => x,
40 Err(e) => {
41 #[cfg(not(feature = "docs-only"))]
42 panic!(
43 r#"
44-- rust-cpp fatal error --
45
46Failed to open the target library file.
47NOTE: Did you make sure to add the rust-cpp build script?
48{}"#,
49 e
50 );
51 #[cfg(feature = "docs-only")]
52 {
53 eprintln!("Error while opening target library: {}", e);
54 return Default::default();
55 };
56 }
57 };
58
59 read_metadata(file).expect(
60 r#"
61-- rust-cpp fatal error --
62
63I/O error while reading metadata from target library file."#,
64 )
65 };
66}
67
68/// NOTE: This panics when it can produce a better error message
69fn read_metadata(file: File) -> io::Result<HashMap<u64, Vec<MetaData>>> {
70 let mut file = BufReader::new(file);
71 let end = {
72 const AUTO_KEYWORD: &[&[u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC];
73 let aut = aho_corasick::AhoCorasick::new(AUTO_KEYWORD).unwrap();
74 let found = aut.stream_find_iter(&mut file).next().expect(
75 r#"
76-- rust-cpp fatal error --
77
78Struct metadata not present in target library file.
79NOTE: Double-check that the version of cpp_build and cpp_macros match"#,
80 )?;
81 found.end()
82 };
83 file.seek(SeekFrom::Start(end as u64))?;
84
85 // Read & convert the version buffer into a string & compare with our
86 // version.
87 let mut version_buf = [0; 16];
88 file.read_exact(&mut version_buf)?;
89 let version =
90 version_buf.iter().take_while(|b| **b != b'\0').map(|b| *b as char).collect::<String>();
91
92 assert_eq!(
93 version, VERSION,
94 r#"
95-- rust-cpp fatal error --
96
97Version mismatch between cpp_macros and cpp_build for same crate."#
98 );
99 let endianness_check = file.read_u64::<LittleEndian>()?;
100 if endianness_check == 0xffef {
101 read_metadata_rest::<LittleEndian>(file)
102 } else if endianness_check == 0xefff000000000000 {
103 read_metadata_rest::<BigEndian>(file)
104 } else {
105 panic!("Endianness check value matches neither little nor big endian.");
106 }
107}
108
109fn read_metadata_rest<E: ByteOrder>(
110 mut file: BufReader<File>,
111) -> io::Result<HashMap<u64, Vec<MetaData>>> {
112 let length: u64 = file.read_u64::<E>()?;
113 let mut metadata: HashMap> = HashMap::new();
114 for _ in 0..length {
115 let hash: u64 = file.read_u64::<E>()?;
116 let size: usize = file.read_u64::<E>()? as usize;
117 let align: usize = file.read_u64::<E>()? as usize;
118 let flags: u64 = file.read_u64::<E>()?;
119
120 metadata.entry(hash).or_insert_with(default:Vec::new).push(MetaData { size, align, flags });
121 }
122 Ok(metadata)
123}
124
125/// Try to open a file handle to the lib file. This is used to scan it for
126/// metadata. We check both `MSVC_LIB_NAME` and `LIB_NAME`, in case we are on
127/// or are targeting Windows.
128fn open_lib_file() -> io::Result<File> {
129 if let Ok(file: File) = File::open(path:OUT_DIR.join(MSVC_LIB_NAME)) {
130 Ok(file)
131 } else {
132 File::open(path:OUT_DIR.join(LIB_NAME))
133 }
134}
135
136fn find_all_rust_macro(
137 input: syn::parse::ParseStream,
138) -> Result<Vec<RustInvocation>, syn::parse::Error> {
139 let mut r = Vec::<RustInvocation>::new();
140 while !input.is_empty() {
141 if input.peek(kw::rust) {
142 if let Ok(ri) = input.parse::<RustInvocation>() {
143 r.push(ri);
144 }
145 } else if input.peek(syn::token::Brace) {
146 let c;
147 braced!(c in input);
148 r.extend(find_all_rust_macro(&c)?);
149 } else if input.peek(syn::token::Paren) {
150 let c;
151 parenthesized!(c in input);
152 r.extend(find_all_rust_macro(&c)?);
153 } else if input.peek(syn::token::Bracket) {
154 let c;
155 bracketed!(c in input);
156 r.extend(find_all_rust_macro(&c)?);
157 } else {
158 input.parse::<proc_macro2::TokenTree>()?;
159 }
160 }
161 Ok(r)
162}
163
164/// Find the occurrence of the `stringify!` macro within the macro derive
165fn extract_original_macro(input: &syn::DeriveInput) -> Option<proc_macro2::TokenStream> {
166 #[derive(Default)]
167 struct Finder(Option<proc_macro2::TokenStream>);
168 impl<'ast> syn::visit::Visit<'ast> for Finder {
169 fn visit_macro(&mut self, mac: &'ast syn::Macro) {
170 if mac.path.segments.len() == 1 && mac.path.segments[0].ident == "stringify" {
171 self.0 = Some(mac.tokens.clone());
172 }
173 }
174 }
175 let mut f: Finder = Finder::default();
176 syn::visit::visit_derive_input(&mut f, node:input);
177 f.0
178}
179
180#[proc_macro_derive(__cpp_internal_closure)]
181#[allow(clippy::cognitive_complexity)]
182pub fn expand_internal(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
183 assert_eq!(
184 env!("CARGO_PKG_VERSION"),
185 VERSION,
186 "Internal Error: mismatched cpp_common and cpp_macros versions"
187 );
188
189 // Parse the macro input
190 let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap();
191
192 let closure = match syn::parse2::<cpp_common::Closure>(input) {
193 Ok(x) => x,
194 Err(err) => return err.to_compile_error().into(),
195 };
196
197 // Get the size data compiled by the build macro
198 let size_data = match METADATA.get(&closure.sig.name_hash()) {
199 Some(x) => x,
200 None => {
201 #[cfg(not(feature = "docs-only"))]
202 return quote!(compile_error! {
203r#"This cpp! macro is not found in the library's rust-cpp metadata.
204NOTE: Only cpp! macros found directly in the program source will be parsed -
205NOTE: They cannot be generated by macro expansion."#})
206 .into();
207 #[cfg(feature = "docs-only")]
208 {
209 return quote! {
210 macro_rules! __cpp_closure_impl {
211 ($($x:tt)*) => { panic!("docs-only"); }
212 }
213 }
214 .into();
215 };
216 }
217 };
218
219 let mut extern_params = Vec::new();
220 let mut tt_args = Vec::new();
221 let mut call_args = Vec::new();
222 for (i, capture) in closure.sig.captures.iter().enumerate() {
223 let written_name = &capture.name;
224 let span = written_name.span();
225 let mac_name = Ident::new(&format!("var_{}", written_name), span);
226 let mac_cty = Ident::new(&format!("cty_{}", written_name), span);
227
228 // Generate the assertion to check that the size and align of the types
229 // match before calling.
230 let MetaData { size, align, .. } = size_data[i + 1];
231 let sizeof_msg = format!(
232 "size_of for argument `{}` does not match between c++ and \
233 rust",
234 &capture.name
235 );
236 let alignof_msg = format!(
237 "align_of for argument `{}` does not match between c++ and \
238 rust",
239 &capture.name
240 );
241 let assertion = quote_spanned! {span=>
242 // Perform a compile time check that the sizes match. This should be
243 // a no-op.
244 if false {
245 #[allow(clippy::transmute_num_to_bytes)]
246 ::core::mem::transmute::<_, [u8; #size]>(
247 ::core::ptr::read(&$#mac_name));
248 }
249
250 // NOTE: Both of these calls should be dead code in opt builds.
251 #[allow(clippy::size_of_ref)] { assert!(::core::mem::size_of_val(&$#mac_name) == #size, #sizeof_msg); };
252 assert!(::core::mem::align_of_val(&$#mac_name) == #align,
253 #alignof_msg);
254 };
255
256 let mb_mut = if capture.mutable { quote_spanned!(span=> mut) } else { quote!() };
257 let ptr = if capture.mutable {
258 quote_spanned!(span=> *mut)
259 } else {
260 quote_spanned!(span=> *const)
261 };
262
263 let arg_name = Ident::new(&format!("arg_{}", written_name), span);
264
265 extern_params.push(quote_spanned!(span=> #arg_name : #ptr u8));
266
267 tt_args.push(quote_spanned!(span=> #mb_mut $#mac_name : ident as $#mac_cty : tt));
268
269 call_args.push(quote_spanned!(span=> {
270 #assertion
271 &#mb_mut $#mac_name as #ptr _ as #ptr u8
272 }));
273 }
274
275 let extern_name = closure.sig.extern_name();
276 let ret_ty = &closure.sig.ret;
277 let MetaData { size: ret_size, align: ret_align, flags } = size_data[0];
278 let is_void = closure.sig.cpp == "void";
279
280 let decl = if is_void {
281 quote! {
282 fn #extern_name(#(#extern_params),*);
283 }
284 } else {
285 quote! {
286 fn #extern_name(#(#extern_params,)* _result: *mut #ret_ty);
287 }
288 };
289
290 let call = if is_void {
291 assert!(ret_size == 0, "`void` should have a size of 0!");
292 quote! {
293 #extern_name(#(#call_args),*);
294 #[allow(clippy::useless_transmute)]
295 ::core::mem::transmute::<(), (#ret_ty)>(())
296 }
297 } else {
298 // static assert that the size and alignement are the same
299 let assert_size = quote! {
300 if false {
301 const _assert_size: [(); #ret_size] = [(); ::core::mem::size_of::<#ret_ty>()];
302 const _assert_align: [(); #ret_align] = [(); ::core::mem::align_of::<#ret_ty>()];
303 }
304 };
305 quote!(
306 #assert_size
307 let mut result = ::core::mem::MaybeUninit::<#ret_ty>::uninit();
308 #extern_name(#(#call_args,)* result.as_mut_ptr());
309 result.assume_init()
310 )
311 };
312
313 let input = proc_macro2::TokenStream::from_iter([closure.body].iter().cloned());
314 let rust_invocations = find_all_rust_macro.parse2(input).expect("rust! macro");
315 let init_callbacks = if !rust_invocations.is_empty() {
316 let rust_cpp_callbacks =
317 Ident::new(&format!("rust_cpp_callbacks{}", *FILE_HASH), Span::call_site());
318 let offset = (flags >> 32) as isize;
319 let callbacks: Vec<Ident> = rust_invocations.iter().map(|x| x.id.clone()).collect();
320 quote! {
321 use ::std::sync::Once;
322 static INIT_INVOCATIONS: Once = Once::new();
323 INIT_INVOCATIONS.call_once(|| {
324 // #rust_cpp_callbacks is in fact an array. Since we cannot represent it in rust,
325 // we just are gonna take the pointer to it can offset from that.
326 extern "C" {
327 #[no_mangle]
328 static mut #rust_cpp_callbacks: *const ::std::os::raw::c_void;
329 }
330 let callbacks_array : *mut *const ::std::os::raw::c_void = &mut #rust_cpp_callbacks;
331 let mut offset = #offset;
332 #(
333 offset += 1;
334 *callbacks_array.offset(offset - 1) = #callbacks as *const ::std::os::raw::c_void;
335 )*
336 });
337 }
338 } else {
339 quote!()
340 };
341
342 let result = quote! {
343 extern "C" {
344 #decl
345 }
346
347 macro_rules! __cpp_closure_impl {
348 (#(#tt_args),*) => {
349 {
350 #init_callbacks
351 #call
352 }
353 }
354 }
355 };
356 result.into()
357}
358
359#[proc_macro_derive(__cpp_internal_class)]
360pub fn expand_wrap_class(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
361 // Parse the macro input
362 let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap();
363
364 let class = match ::syn::parse2::<cpp_common::Class>(input) {
365 Ok(x) => x,
366 Err(err) => return err.to_compile_error().into(),
367 };
368
369 let hash = class.name_hash();
370 let class_name = class.name.clone();
371
372 // Get the size data compiled by the build macro
373 let size_data = match METADATA.get(&hash) {
374 Some(x) => x,
375 None => {
376 #[cfg(not(feature = "docs-only"))]
377 return quote!(compile_error! {
378r#"This cpp_class! macro is not found in the library's rust-cpp metadata.
379NOTE: Only cpp_class! macros found directly in the program source will be parsed -
380NOTE: They cannot be generated by macro expansion."#})
381 .into();
382 #[cfg(feature = "docs-only")]
383 {
384 let mut result = quote! {
385 #[doc(hidden)]
386 impl ::cpp::CppTrait for #class_name {
387 type BaseType = usize;
388 const ARRAY_SIZE: usize = 1;
389 const CPP_TYPE: &'static str = stringify!(#class_name);
390 }
391 #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is trivially copyable"]
392 impl ::core::marker::Copy for #class_name { }
393 #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is copyable"]
394 impl ::core::clone::Clone for #class_name { fn clone(&self) -> Self { panic!("docs-only") } }
395 #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is default constructible"]
396 impl ::core::default::Default for #class_name { fn default() -> Self { panic!("docs-only") } }
397 };
398 if class.derives("PartialEq") {
399 result = quote! { #result
400 impl ::core::cmp::PartialEq for #class_name {
401 fn eq(&self, other: &#class_name) -> bool { panic!("docs-only") }
402 }
403 };
404 }
405 if class.derives("PartialOrd") {
406 result = quote! { #result
407 impl ::core::cmp::PartialOrd for #class_name {
408 fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> {
409 panic!("docs-only")
410 }
411 }
412 };
413 }
414 if class.derives("Ord") {
415 result = quote! { #result
416 impl ::core::cmp::Ord for #class_name {
417 fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering {
418 panic!("docs-only")
419 }
420 }
421 };
422 }
423 return result.into();
424 };
425 }
426 };
427
428 let (size, align) = (size_data[0].size, size_data[0].align);
429
430 let base_type = match align {
431 1 => quote!(u8),
432 2 => quote!(u16),
433 4 => quote!(u32),
434 8 => quote!(u64),
435 _ => panic!("unsupported alignment"),
436 };
437
438 let destructor_name = Ident::new(&format!("__cpp_destructor_{}", hash), Span::call_site());
439 let copyctr_name = Ident::new(&format!("__cpp_copy_{}", hash), Span::call_site());
440 let defaultctr_name = Ident::new(&format!("__cpp_default_{}", hash), Span::call_site());
441
442 let mut result = quote! {
443 #[doc(hidden)]
444 impl ::cpp::CppTrait for #class_name {
445 type BaseType = #base_type;
446 const ARRAY_SIZE: usize = #size / #align;
447 const CPP_TYPE: &'static str = stringify!(#class_name);
448 }
449 };
450 if !size_data[0].has_flag(flags::IS_TRIVIALLY_DESTRUCTIBLE) {
451 result = quote! { #result
452 impl ::core::ops::Drop for #class_name {
453 fn drop(&mut self) {
454 unsafe {
455 extern "C" { fn #destructor_name(_: *mut #class_name); }
456 #destructor_name(&mut *self);
457 }
458 }
459 }
460 };
461 };
462
463 if size_data[0].has_flag(flags::IS_COPY_CONSTRUCTIBLE) {
464 if !size_data[0].has_flag(flags::IS_TRIVIALLY_COPYABLE) && !class.derives("Copy") {
465 let call_construct = quote!(
466 let mut result = ::core::mem::MaybeUninit::<Self>::uninit();
467 #copyctr_name(& *self, result.as_mut_ptr());
468 result.assume_init()
469 );
470 result = quote! { #result
471 impl ::core::clone::Clone for #class_name {
472 fn clone(&self) -> Self {
473 unsafe {
474 extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); }
475 #call_construct
476 }
477 }
478 }
479 };
480 } else {
481 result = quote! { #result
482 impl ::core::marker::Copy for #class_name { }
483 impl ::core::clone::Clone for #class_name {
484 fn clone(&self) -> Self { *self }
485 }
486 };
487 };
488 } else if class.derives("Clone") {
489 panic!("C++ class is not copyable");
490 }
491
492 if size_data[0].has_flag(flags::IS_DEFAULT_CONSTRUCTIBLE) {
493 let call_construct = quote!(
494 let mut result = ::core::mem::MaybeUninit::<Self>::uninit();
495 #defaultctr_name(result.as_mut_ptr());
496 result.assume_init()
497 );
498 result = quote! { #result
499 impl ::core::default::Default for #class_name {
500 fn default() -> Self {
501 unsafe {
502 extern "C" { fn #defaultctr_name(dst: *mut #class_name); }
503 #call_construct
504 }
505 }
506 }
507 };
508 } else if class.derives("Default") {
509 panic!("C++ class is not default constructible");
510 }
511
512 if class.derives("PartialEq") {
513 let equal_name = Ident::new(&format!("__cpp_equal_{}", hash), Span::call_site());
514 result = quote! { #result
515 impl ::core::cmp::PartialEq for #class_name {
516 fn eq(&self, other: &#class_name) -> bool {
517 unsafe {
518 extern "C" { fn #equal_name(a: *const #class_name, b: *const #class_name) -> bool; }
519 #equal_name(& *self, other)
520 }
521 }
522 }
523 };
524 }
525 if class.derives("PartialOrd") {
526 let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site());
527 let f = |func, cmp| {
528 quote! {
529 fn #func(&self, other: &#class_name) -> bool {
530 unsafe {
531 extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
532 #compare_name(& *self, other, #cmp) != 0
533 }
534 }
535 }
536 };
537 let lt = f(quote! {lt}, -2);
538 let gt = f(quote! {gt}, 2);
539 let le = f(quote! {le}, -1);
540 let ge = f(quote! {ge}, 1);
541 result = quote! { #result
542 impl ::core::cmp::PartialOrd for #class_name {
543 #lt #gt #le #ge
544
545 fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> {
546 use ::core::cmp::Ordering;
547 unsafe {
548 extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
549 ::core::option::Option::Some(match #compare_name(& *self, other, 0) {
550 -1 => Ordering::Less,
551 0 => Ordering::Equal,
552 1 => Ordering::Greater,
553 _ => panic!()
554 })
555 }
556 }
557 }
558 };
559 }
560 if class.derives("Ord") {
561 let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site());
562 result = quote! { #result
563 impl ::core::cmp::Ord for #class_name {
564 fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering {
565 unsafe {
566 use ::core::cmp::Ordering;
567 extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; }
568 match #compare_name(& *self, other, 0) {
569 -1 => Ordering::Less,
570 0 => Ordering::Equal,
571 1 => Ordering::Greater,
572 _ => panic!()
573 }
574 }
575 }
576 }
577 };
578 }
579
580 if class.derives("Hash") {
581 panic!("Deriving from Hash is not implemented")
582 };
583 if class.derives("Debug") {
584 panic!("Deriving from Debug is not implemented")
585 };
586
587 result.into()
588}
589