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