summaryrefslogtreecommitdiff
path: root/tests/clang.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/clang.rs')
-rw-r--r--tests/clang.rs339
1 files changed, 339 insertions, 0 deletions
diff --git a/tests/clang.rs b/tests/clang.rs
new file mode 100644
index 0000000..b2484f0
--- /dev/null
+++ b/tests/clang.rs
@@ -0,0 +1,339 @@
+// (C) Copyright 2016 Jethro G. Beekman
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<u8>,
+ tokens: &[Token],
+ idents: &mut HashMap<Vec<u8>, EvalResult>,
+) -> bool {
+ fn bytes_to_int(value: &[u8]) -> Option<EvalResult> {
+ 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<u8> {
+ 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<F>(
+ 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<F>(cursor: CXCursor, mut f: F)
+where
+ F: FnMut(CXCursor, CXCursor) -> CXChildVisitResult,
+{
+ clang_visitChildren(
+ cursor,
+ visit_children_thunk::<F> 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<F: FnMut(Vec<u8>, Vec<Token>)>(
+ 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);