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