1// (C) Copyright 2016 Jethro G. Beekman
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8extern crate cexpr;
9extern crate clang_sys;
10
11use std::collections::HashMap;
12use std::io::Write;
13use std::str::{self, FromStr};
14use std::{char, ffi, mem, ptr, slice};
15
16use cexpr::assert_full_parse;
17use cexpr::expr::{fn_macro_declaration, EvalResult, IdentifierParser};
18use cexpr::literal::CChar;
19use cexpr::token::Token;
20use clang_sys::*;
21
22// main testing routine
23fn test_definition(
24    ident: Vec<u8>,
25    tokens: &[Token],
26    idents: &mut HashMap<Vec<u8>, EvalResult>,
27) -> bool {
28    fn bytes_to_int(value: &[u8]) -> Option<EvalResult> {
29        str::from_utf8(value)
30            .ok()
31            .map(|s| s.replace("n", "-"))
32            .map(|s| s.replace("_", ""))
33            .and_then(|v| i64::from_str(&v).ok())
34            .map(::std::num::Wrapping)
35            .map(Int)
36    }
37
38    use cexpr::expr::EvalResult::*;
39
40    let display_name = String::from_utf8_lossy(&ident).into_owned();
41
42    let functional;
43    let test = {
44        // Split name such as Str_test_string into (Str,test_string)
45        let pos = ident
46            .iter()
47            .position(|c| *c == b'_')
48            .expect(&format!("Invalid definition in testcase: {}", display_name));
49        let mut expected = &ident[..pos];
50        let mut value = &ident[(pos + 1)..];
51
52        functional = expected == b"Fn";
53
54        if functional {
55            let ident = value;
56            let pos = ident
57                .iter()
58                .position(|c| *c == b'_')
59                .expect(&format!("Invalid definition in testcase: {}", display_name));
60            expected = &ident[..pos];
61            value = &ident[(pos + 1)..];
62        }
63
64        if expected == b"Str" {
65            let mut splits = value.split(|c| *c == b'U');
66            let mut s = Vec::with_capacity(value.len());
67            s.extend_from_slice(splits.next().unwrap());
68            for split in splits {
69                let (chr, rest) = split.split_at(6);
70                let chr = u32::from_str_radix(str::from_utf8(chr).unwrap(), 16).unwrap();
71                write!(s, "{}", char::from_u32(chr).unwrap()).unwrap();
72                s.extend_from_slice(rest);
73            }
74            Some(Str(s))
75        } else if expected == b"Int" {
76            bytes_to_int(value)
77        } else if expected == b"Float" {
78            str::from_utf8(value)
79                .ok()
80                .map(|s| s.replace("n", "-").replace("p", "."))
81                .and_then(|v| f64::from_str(&v).ok())
82                .map(Float)
83        } else if expected == b"CharRaw" {
84            str::from_utf8(value)
85                .ok()
86                .and_then(|v| u64::from_str(v).ok())
87                .map(CChar::Raw)
88                .map(Char)
89        } else if expected == b"CharChar" {
90            str::from_utf8(value)
91                .ok()
92                .and_then(|v| u32::from_str(v).ok())
93                .and_then(char::from_u32)
94                .map(CChar::Char)
95                .map(Char)
96        } else {
97            Some(Invalid)
98        }
99        .expect(&format!("Invalid definition in testcase: {}", display_name))
100    };
101
102    let result = if functional {
103        let mut fnidents;
104        let expr_tokens;
105        match fn_macro_declaration(&tokens) {
106            Ok((rest, (_, args))) => {
107                fnidents = idents.clone();
108                expr_tokens = rest;
109                for arg in args {
110                    let val = match test {
111                        Int(_) => bytes_to_int(&arg),
112                        Str(_) => Some(Str(arg.to_owned())),
113                        _ => unimplemented!(),
114                    }
115                    .expect(&format!(
116                        "Invalid argument in functional macro testcase: {}",
117                        display_name
118                    ));
119                    fnidents.insert(arg.to_owned(), val);
120                }
121            }
122            e => {
123                println!(
124                    "Failed test for {}, unable to parse functional macro declaration: {:?}",
125                    display_name, e
126                );
127                return false;
128            }
129        }
130        assert_full_parse(IdentifierParser::new(&fnidents).expr(&expr_tokens))
131    } else {
132        IdentifierParser::new(idents)
133            .macro_definition(&tokens)
134            .map(|(i, (_, val))| (i, val))
135    };
136
137    match result {
138        Ok((_, val)) => {
139            if val == test {
140                if let Some(_) = idents.insert(ident, val) {
141                    panic!("Duplicate definition for testcase: {}", display_name);
142                }
143                true
144            } else {
145                println!(
146                    "Failed test for {}, expected {:?}, got {:?}",
147                    display_name, test, val
148                );
149                false
150            }
151        }
152        e => {
153            if test == Invalid {
154                true
155            } else {
156                println!(
157                    "Failed test for {}, expected {:?}, got {:?}",
158                    display_name, test, e
159                );
160                false
161            }
162        }
163    }
164}
165
166// support code for the clang lexer
167unsafe fn clang_str_to_vec(s: CXString) -> Vec<u8> {
168    let vec = ffi::CStr::from_ptr(clang_getCString(s))
169        .to_bytes()
170        .to_owned();
171    clang_disposeString(s);
172    vec
173}
174
175#[allow(non_upper_case_globals)]
176unsafe fn token_clang_to_cexpr(tu: CXTranslationUnit, orig: &CXToken) -> Token {
177    Token {
178        kind: match clang_getTokenKind(*orig) {
179            CXToken_Comment => cexpr::token::Kind::Comment,
180            CXToken_Identifier => cexpr::token::Kind::Identifier,
181            CXToken_Keyword => cexpr::token::Kind::Keyword,
182            CXToken_Literal => cexpr::token::Kind::Literal,
183            CXToken_Punctuation => cexpr::token::Kind::Punctuation,
184            _ => panic!("invalid token kind: {:?}", *orig),
185        },
186        raw: clang_str_to_vec(clang_getTokenSpelling(tu, *orig)).into_boxed_slice(),
187    }
188}
189
190extern "C" fn visit_children_thunk<F>(
191    cur: CXCursor,
192    parent: CXCursor,
193    closure: CXClientData,
194) -> CXChildVisitResult
195where
196    F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,
197{
198    unsafe { (&mut *(closure as *mut F))(cur, parent) }
199}
200
201unsafe fn visit_children<F>(cursor: CXCursor, mut f: F)
202where
203    F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,
204{
205    clang_visitChildren(
206        cursor,
207        visit_children_thunk::<F> as _,
208        &mut f as *mut F as CXClientData,
209    );
210}
211
212unsafe fn location_in_scope(r: CXSourceRange) -> bool {
213    let start = clang_getRangeStart(r);
214    let mut file = ptr::null_mut();
215    clang_getSpellingLocation(
216        start,
217        &mut file,
218        ptr::null_mut(),
219        ptr::null_mut(),
220        ptr::null_mut(),
221    );
222    clang_Location_isFromMainFile(start) != 0
223        && clang_Location_isInSystemHeader(start) == 0
224        && file != ptr::null_mut()
225}
226
227/// tokenize_range_adjust can be used to work around LLVM bug 9069
228/// https://bugs.llvm.org//show_bug.cgi?id=9069
229fn file_visit_macros<F: FnMut(Vec<u8>, Vec<Token>)>(
230    file: &str,
231    tokenize_range_adjust: bool,
232    mut visitor: F,
233) {
234    unsafe {
235        let tu = {
236            let index = clang_createIndex(true as _, false as _);
237            let cfile = ffi::CString::new(file).unwrap();
238            let mut tu = mem::MaybeUninit::uninit();
239            assert!(
240                clang_parseTranslationUnit2(
241                    index,
242                    cfile.as_ptr(),
243                    [b"-std=c11\0".as_ptr() as *const ::std::os::raw::c_char].as_ptr(),
244                    1,
245                    ptr::null_mut(),
246                    0,
247                    CXTranslationUnit_DetailedPreprocessingRecord,
248                    &mut *tu.as_mut_ptr()
249                ) == CXError_Success,
250                "Failure reading test case {}",
251                file
252            );
253            tu.assume_init()
254        };
255        visit_children(clang_getTranslationUnitCursor(tu), |cur, _parent| {
256            if cur.kind == CXCursor_MacroDefinition {
257                let mut range = clang_getCursorExtent(cur);
258                if !location_in_scope(range) {
259                    return CXChildVisit_Continue;
260                }
261                range.end_int_data -= if tokenize_range_adjust { 1 } else { 0 };
262                let mut token_ptr = ptr::null_mut();
263                let mut num = 0;
264                clang_tokenize(tu, range, &mut token_ptr, &mut num);
265                if token_ptr != ptr::null_mut() {
266                    let tokens = slice::from_raw_parts(token_ptr, num as usize);
267                    let tokens: Vec<_> = tokens
268                        .iter()
269                        .filter_map(|t| {
270                            if clang_getTokenKind(*t) != CXToken_Comment {
271                                Some(token_clang_to_cexpr(tu, t))
272                            } else {
273                                None
274                            }
275                        })
276                        .collect();
277                    clang_disposeTokens(tu, token_ptr, num);
278                    visitor(clang_str_to_vec(clang_getCursorSpelling(cur)), tokens)
279                }
280            }
281            CXChildVisit_Continue
282        });
283        clang_disposeTranslationUnit(tu);
284    };
285}
286
287fn test_file(file: &str) -> bool {
288    let mut idents = HashMap::new();
289    let mut all_succeeded = true;
290    file_visit_macros(file, fix_bug_9069(), |ident, tokens| {
291        all_succeeded &= test_definition(ident, &tokens, &mut idents)
292    });
293    all_succeeded
294}
295
296fn fix_bug_9069() -> bool {
297    fn check_bug_9069() -> bool {
298        let mut token_sets = vec![];
299        file_visit_macros(
300            "tests/input/test_llvm_bug_9069.h",
301            false,
302            |ident, tokens| {
303                assert_eq!(&ident, b"A");
304                token_sets.push(tokens);
305            },
306        );
307        assert_eq!(token_sets.len(), 2);
308        token_sets[0] != token_sets[1]
309    }
310
311    use std::sync::atomic::{AtomicBool, Ordering};
312    use std::sync::Once;
313
314    static CHECK_FIX: Once = Once::new();
315    static FIX: AtomicBool = AtomicBool::new(false);
316
317    CHECK_FIX.call_once(|| FIX.store(check_bug_9069(), Ordering::SeqCst));
318
319    FIX.load(Ordering::SeqCst)
320}
321
322macro_rules! test_file {
323    ($f:ident) => {
324        #[test]
325        fn $f() {
326            assert!(
327                test_file(concat!("tests/input/", stringify!($f), ".h")),
328                "test_file"
329            )
330        }
331    };
332}
333
334test_file!(floats);
335test_file!(chars);
336test_file!(strings);
337test_file!(int_signed);
338test_file!(int_unsigned);
339test_file!(fail);
340