blob: b2484f0778288b96999d0654b2e3e2772ee085c5 [file] [log] [blame]
// (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);