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.
8 extern crate cexpr;
9 extern crate clang_sys;
10 
11 use std::collections::HashMap;
12 use std::io::Write;
13 use std::str::{self, FromStr};
14 use std::{char, ffi, mem, ptr, slice};
15 
16 use cexpr::assert_full_parse;
17 use cexpr::expr::{fn_macro_declaration, EvalResult, IdentifierParser};
18 use cexpr::literal::CChar;
19 use cexpr::token::Token;
20 use clang_sys::*;
21 
22 // main testing routine
test_definition( ident: Vec<u8>, tokens: &[Token], idents: &mut HashMap<Vec<u8>, EvalResult>, ) -> bool23 fn 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
clang_str_to_vec(s: CXString) -> Vec<u8>167 unsafe 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)]
token_clang_to_cexpr(tu: CXTranslationUnit, orig: &CXToken) -> Token176 unsafe 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 
visit_children_thunk<F>( cur: CXCursor, parent: CXCursor, closure: CXClientData, ) -> CXChildVisitResult where F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,190 extern "C" fn visit_children_thunk<F>(
191     cur: CXCursor,
192     parent: CXCursor,
193     closure: CXClientData,
194 ) -> CXChildVisitResult
195 where
196     F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,
197 {
198     unsafe { (&mut *(closure as *mut F))(cur, parent) }
199 }
200 
visit_children<F>(cursor: CXCursor, mut f: F) where F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,201 unsafe fn visit_children<F>(cursor: CXCursor, mut f: F)
202 where
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 
location_in_scope(r: CXSourceRange) -> bool212 unsafe 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
file_visit_macros<F: FnMut(Vec<u8>, Vec<Token>)>( file: &str, tokenize_range_adjust: bool, mut visitor: F, )229 fn 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 
test_file(file: &str) -> bool287 fn 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 
fix_bug_9069() -> bool296 fn 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 
322 macro_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 
334 test_file!(floats);
335 test_file!(chars);
336 test_file!(strings);
337 test_file!(int_signed);
338 test_file!(int_unsigned);
339 test_file!(fail);
340