1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//! This crate is the `cpp` procedural macro implementation. It is useless
//! without the companion crates `cpp`, and `cpp_build`.
//!
//! For more information, see the [`cpp` crate module level
//! documentation](https://docs.rs/cpp).

extern crate cpp_synom as synom;

extern crate cpp_syn as syn;

#[macro_use]
extern crate quote;

extern crate cpp_common;

extern crate proc_macro;

#[macro_use]
extern crate lazy_static;

extern crate aho_corasick;

extern crate byteorder;

use std::env;
use std::path::PathBuf;
use std::collections::HashMap;
use proc_macro::TokenStream;
use cpp_common::{parsing, VERSION, LIB_NAME, MSVC_LIB_NAME};
use syn::Ident;

use std::io::{self, Read, Seek, SeekFrom, BufReader};
use std::fs::File;
use aho_corasick::{AcAutomaton, Automaton};
use byteorder::{LittleEndian, ReadBytesExt};

lazy_static! {
    static ref OUT_DIR: PathBuf =
        PathBuf::from(env::var("OUT_DIR").expect(r#"
-- rust-cpp fatal error --

The OUT_DIR environment variable was not set.
NOTE: rustc must be run by Cargo."#));

    static ref METADATA: HashMap<u64, Vec<(usize, usize)>> = {
        let file = open_lib_file().expect(r#"
-- rust-cpp fatal error --

Failed to open the target library file.
NOTE: Did you make sure to add the rust-cpp build script?"#);

        read_metadata(file)
            .expect(r#"
-- rust-cpp fatal error --

I/O error while reading metadata from target library file."#)
    };
}

/// NOTE: This panics when it can produce a better error message
fn read_metadata(file: File) -> io::Result<HashMap<u64, Vec<(usize, usize)>>> {
    let mut file = BufReader::new(file);
    let end = {
        const AUTO_KEYWORD: &'static [&'static [u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC];
        let aut = AcAutomaton::new(AUTO_KEYWORD);
        let found = aut.stream_find(&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))?;

    // Read & convert the version buffer into a string & compare with our
    // version.
    let mut version_buf = [0; 16];
    file.read(&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 size_data = 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;

        size_data
            .entry(hash)
            .or_insert(Vec::new())
            .push((size, align));
    }
    Ok(size_data)
}

/// Try to open a file handle to the lib file. This is used to scan it for
/// metadata. We check both MSVC_LIB_NAME and LIB_NAME, in case we are on
/// or are targeting windows.
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))
    }
}

/// Strip tokens from the prefix and suffix of the source string, extracting the
/// original argument to the cpp! macro.
fn macro_text(mut source: &str) -> &str {
    #[cfg_attr(rustfmt, rustfmt_skip)]
    const PREFIX: &'static [&'static str] = &[
        "#", "[", "allow", "(", "unused", ")", "]",
        "enum", "CppClosureInput", "{",
        "Input", "=", "(", "stringify", "!", "("
    ];

    #[cfg_attr(rustfmt, rustfmt_skip)]
    const SUFFIX: &'static [&'static str] = &[
        ")", ",", "0", ")", ".", "1", ",", "}"
    ];

    source = source.trim();

    for token in PREFIX {
        assert!(source.starts_with(token),
                "expected prefix token {}, got {}",
                token,
                source);
        source = &source[token.len()..].trim();
    }

    for token in SUFFIX.iter().rev() {
        assert!(source.ends_with(token),
                "expected suffix token {}, got {}",
                token,
                source);
        source = &source[..source.len() - token.len()].trim();
    }

    source
}

#[proc_macro_derive(__cpp_internal_closure)]
pub fn expand_internal(input: TokenStream) -> TokenStream {
    assert_eq!(env!("CARGO_PKG_VERSION"),
               VERSION,
               "Internal Error: mismatched cpp_common and cpp_macros versions");

    // Parse the macro input
    let source = input.to_string();
    let tokens = macro_text(&source);

    let closure = parsing::cpp_closure(synom::ParseState::new(tokens))
        .expect("cpp! macro")
        .sig;

    // Get the size data compiled by the build macro
    let size_data = METADATA
        .get(&closure.name_hash())
        .expect(r#"
-- rust-cpp fatal error --

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."#);

    let mut extern_params = Vec::new();
    let mut tt_args = Vec::new();
    let mut call_args = Vec::new();
    for (i, capture) in closure.captures.iter().enumerate() {
        let written_name = &capture.name;
        let mac_name: Ident = format!("$var_{}", written_name).into();
        let mac_cty: Ident = format!("$cty_{}", written_name).into();

        // Generate the assertion to check that the size and align of the types
        // match before calling.
        let (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!{
            // Perform a compile time check that the sizes match. This should be
            // a no-op.
            ::std::mem::forget(
                ::std::mem::transmute::<_, [u8; #size]>(
                    ::std::ptr::read(&#mac_name)));

            // NOTE: Both of these calls should be dead code in opt builds.
            assert!(::std::mem::size_of_val(&#mac_name) == #size,
                    #sizeof_msg);
            assert!(::std::mem::align_of_val(&#mac_name) == #align,
                    #alignof_msg);
        };

        let mb_mut = if capture.mutable {
            quote!(mut)
        } else {
            quote!()
        };
        let ptr = if capture.mutable {
            quote!(*mut)
        } else {
            quote!(*const)
        };

        extern_params.push(quote!(#written_name : #ptr u8));

        tt_args.push(quote!(#mb_mut #mac_name : ident as #mac_cty : tt));

        call_args.push(quote!({
            #assertion
            &#mb_mut #mac_name as #ptr _ as #ptr u8
        }));
    }

    let extern_name = closure.extern_name();
    let ret_ty = &closure.ret;
    let (ret_size, ret_align) = size_data[0];
    let result = quote! {
        extern "C" {
            fn #extern_name(#(#extern_params),*) -> #ret_ty;
        }

        macro_rules! __cpp_closure_impl {
            (#(#tt_args),*) => {
                {
                    // Perform a compile time check that the sizes match.
                    ::std::mem::forget(
                        ::std::mem::transmute::<_, [u8; #ret_size]>(
                            ::std::mem::uninitialized::<#ret_ty>()));

                    // Perform a runtime check that the sizes match.
                    assert!(::std::mem::size_of::<#ret_ty>() == #ret_size);
                    assert!(::std::mem::align_of::<#ret_ty>() == #ret_align);

                    #extern_name(#(#call_args),*)
                }
            }
        }
    };

    result.to_string().parse().unwrap()
}