// (C) Copyright 2016 Jethro G. Beekman // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. extern crate cexpr; extern crate clang_sys; use std::collections::HashMap; use std::io::Write; use std::str::{self, FromStr}; use std::{char, ffi, mem, ptr, slice}; use cexpr::assert_full_parse; use cexpr::expr::{fn_macro_declaration, EvalResult, IdentifierParser}; use cexpr::literal::CChar; use cexpr::token::Token; use clang_sys::*; // main testing routine fn test_definition( ident: Vec, tokens: &[Token], idents: &mut HashMap, EvalResult>, ) -> bool { fn bytes_to_int(value: &[u8]) -> Option { str::from_utf8(value) .ok() .map(|s| s.replace("n", "-")) .map(|s| s.replace("_", "")) .and_then(|v| i64::from_str(&v).ok()) .map(::std::num::Wrapping) .map(Int) } use cexpr::expr::EvalResult::*; let display_name = String::from_utf8_lossy(&ident).into_owned(); let functional; let test = { // Split name such as Str_test_string into (Str,test_string) let pos = ident .iter() .position(|c| *c == b'_') .expect(&format!("Invalid definition in testcase: {}", display_name)); let mut expected = &ident[..pos]; let mut value = &ident[(pos + 1)..]; functional = expected == b"Fn"; if functional { let ident = value; let pos = ident .iter() .position(|c| *c == b'_') .expect(&format!("Invalid definition in testcase: {}", display_name)); expected = &ident[..pos]; value = &ident[(pos + 1)..]; } if expected == b"Str" { let mut splits = value.split(|c| *c == b'U'); let mut s = Vec::with_capacity(value.len()); s.extend_from_slice(splits.next().unwrap()); for split in splits { let (chr, rest) = split.split_at(6); let chr = u32::from_str_radix(str::from_utf8(chr).unwrap(), 16).unwrap(); write!(s, "{}", char::from_u32(chr).unwrap()).unwrap(); s.extend_from_slice(rest); } Some(Str(s)) } else if expected == b"Int" { bytes_to_int(value) } else if expected == b"Float" { str::from_utf8(value) .ok() .map(|s| s.replace("n", "-").replace("p", ".")) .and_then(|v| f64::from_str(&v).ok()) .map(Float) } else if expected == b"CharRaw" { str::from_utf8(value) .ok() .and_then(|v| u64::from_str(v).ok()) .map(CChar::Raw) .map(Char) } else if expected == b"CharChar" { str::from_utf8(value) .ok() .and_then(|v| u32::from_str(v).ok()) .and_then(char::from_u32) .map(CChar::Char) .map(Char) } else { Some(Invalid) } .expect(&format!("Invalid definition in testcase: {}", display_name)) }; let result = if functional { let mut fnidents; let expr_tokens; match fn_macro_declaration(&tokens) { Ok((rest, (_, args))) => { fnidents = idents.clone(); expr_tokens = rest; for arg in args { let val = match test { Int(_) => bytes_to_int(&arg), Str(_) => Some(Str(arg.to_owned())), _ => unimplemented!(), } .expect(&format!( "Invalid argument in functional macro testcase: {}", display_name )); fnidents.insert(arg.to_owned(), val); } } e => { println!( "Failed test for {}, unable to parse functional macro declaration: {:?}", display_name, e ); return false; } } assert_full_parse(IdentifierParser::new(&fnidents).expr(&expr_tokens)) } else { IdentifierParser::new(idents) .macro_definition(&tokens) .map(|(i, (_, val))| (i, val)) }; match result { Ok((_, val)) => { if val == test { if let Some(_) = idents.insert(ident, val) { panic!("Duplicate definition for testcase: {}", display_name); } true } else { println!( "Failed test for {}, expected {:?}, got {:?}", display_name, test, val ); false } } e => { if test == Invalid { true } else { println!( "Failed test for {}, expected {:?}, got {:?}", display_name, test, e ); false } } } } // support code for the clang lexer unsafe fn clang_str_to_vec(s: CXString) -> Vec { let vec = ffi::CStr::from_ptr(clang_getCString(s)) .to_bytes() .to_owned(); clang_disposeString(s); vec } #[allow(non_upper_case_globals)] unsafe fn token_clang_to_cexpr(tu: CXTranslationUnit, orig: &CXToken) -> Token { Token { kind: match clang_getTokenKind(*orig) { CXToken_Comment => cexpr::token::Kind::Comment, CXToken_Identifier => cexpr::token::Kind::Identifier, CXToken_Keyword => cexpr::token::Kind::Keyword, CXToken_Literal => cexpr::token::Kind::Literal, CXToken_Punctuation => cexpr::token::Kind::Punctuation, _ => panic!("invalid token kind: {:?}", *orig), }, raw: clang_str_to_vec(clang_getTokenSpelling(tu, *orig)).into_boxed_slice(), } } extern "C" fn visit_children_thunk( cur: CXCursor, parent: CXCursor, closure: CXClientData, ) -> CXChildVisitResult where F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult, { unsafe { (&mut *(closure as *mut F))(cur, parent) } } unsafe fn visit_children(cursor: CXCursor, mut f: F) where F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult, { clang_visitChildren( cursor, visit_children_thunk:: as _, &mut f as *mut F as CXClientData, ); } unsafe fn location_in_scope(r: CXSourceRange) -> bool { let start = clang_getRangeStart(r); let mut file = ptr::null_mut(); clang_getSpellingLocation( start, &mut file, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ); clang_Location_isFromMainFile(start) != 0 && clang_Location_isInSystemHeader(start) == 0 && file != ptr::null_mut() } /// tokenize_range_adjust can be used to work around LLVM bug 9069 /// https://bugs.llvm.org//show_bug.cgi?id=9069 fn file_visit_macros, Vec)>( file: &str, tokenize_range_adjust: bool, mut visitor: F, ) { unsafe { let tu = { let index = clang_createIndex(true as _, false as _); let cfile = ffi::CString::new(file).unwrap(); let mut tu = mem::MaybeUninit::uninit(); assert!( clang_parseTranslationUnit2( index, cfile.as_ptr(), [b"-std=c11\0".as_ptr() as *const ::std::os::raw::c_char].as_ptr(), 1, ptr::null_mut(), 0, CXTranslationUnit_DetailedPreprocessingRecord, &mut *tu.as_mut_ptr() ) == CXError_Success, "Failure reading test case {}", file ); tu.assume_init() }; visit_children(clang_getTranslationUnitCursor(tu), |cur, _parent| { if cur.kind == CXCursor_MacroDefinition { let mut range = clang_getCursorExtent(cur); if !location_in_scope(range) { return CXChildVisit_Continue; } range.end_int_data -= if tokenize_range_adjust { 1 } else { 0 }; let mut token_ptr = ptr::null_mut(); let mut num = 0; clang_tokenize(tu, range, &mut token_ptr, &mut num); if token_ptr != ptr::null_mut() { let tokens = slice::from_raw_parts(token_ptr, num as usize); let tokens: Vec<_> = tokens .iter() .filter_map(|t| { if clang_getTokenKind(*t) != CXToken_Comment { Some(token_clang_to_cexpr(tu, t)) } else { None } }) .collect(); clang_disposeTokens(tu, token_ptr, num); visitor(clang_str_to_vec(clang_getCursorSpelling(cur)), tokens) } } CXChildVisit_Continue }); clang_disposeTranslationUnit(tu); }; } fn test_file(file: &str) -> bool { let mut idents = HashMap::new(); let mut all_succeeded = true; file_visit_macros(file, fix_bug_9069(), |ident, tokens| { all_succeeded &= test_definition(ident, &tokens, &mut idents) }); all_succeeded } fn fix_bug_9069() -> bool { fn check_bug_9069() -> bool { let mut token_sets = vec![]; file_visit_macros( "tests/input/test_llvm_bug_9069.h", false, |ident, tokens| { assert_eq!(&ident, b"A"); token_sets.push(tokens); }, ); assert_eq!(token_sets.len(), 2); token_sets[0] != token_sets[1] } use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Once; static CHECK_FIX: Once = Once::new(); static FIX: AtomicBool = AtomicBool::new(false); CHECK_FIX.call_once(|| FIX.store(check_bug_9069(), Ordering::SeqCst)); FIX.load(Ordering::SeqCst) } macro_rules! test_file { ($f:ident) => { #[test] fn $f() { assert!( test_file(concat!("tests/input/", stringify!($f), ".h")), "test_file" ) } }; } test_file!(floats); test_file!(chars); test_file!(strings); test_file!(int_signed); test_file!(int_unsigned); test_file!(fail);