use super::*;
fn eval(src: &[u8], defined: &[&[u8]]) -> Result<bool, CPreprocessorError> {
PreprocessorExprParser {
bytes: src,
index: 0,
base_offset: 0,
defined_macros: defined,
depth: 0,
}
.parse()
}
#[test]
fn deeply_nested_parens_fail_closed() {
let n = 50_000;
let mut src = Vec::new();
src.extend(std::iter::repeat(b'(').take(n));
src.push(b'1');
src.extend(std::iter::repeat(b')').take(n));
let r = eval(&src, &[]);
assert!(
r.is_err(),
"Fix: 50k-deep #if parens must be rejected with an error, not crash"
);
}
#[test]
fn deeply_nested_unary_not_fails_closed() {
let mut src = vec![b'!'; 50_000];
src.push(b'1');
let r = eval(&src, &[]);
assert!(
r.is_err(),
"Fix: 50k-deep #if `!` chain must fail closed, not crash"
);
}
#[test]
fn deeply_nested_unary_minus_fails_closed() {
let mut src = vec![b'-'; 50_000];
src.push(b'1');
let r = eval(&src, &[]);
assert!(
r.is_err(),
"Fix: 50k-deep #if `-` chain must fail closed, not crash"
);
}
#[test]
fn deeply_nested_bitnot_fails_closed() {
let mut src = vec![b'~'; 50_000];
src.push(b'1');
let r = eval(&src, &[]);
assert!(
r.is_err(),
"Fix: 50k-deep #if `~` chain must fail closed, not crash"
);
}
#[test]
fn deeply_nested_ternary_fails_closed() {
let n = 50_000;
let mut src = Vec::new();
for _ in 0..n {
src.extend_from_slice(b"1?1:");
}
src.push(b'1');
let r = eval(&src, &[]);
assert!(
r.is_err(),
"Fix: 50k-deep #if ternary chain must fail closed, not crash"
);
}
#[test]
fn reasonable_nesting_still_evaluates() {
let depth = 32;
let mut src = Vec::new();
src.extend(std::iter::repeat(b'(').take(depth));
src.extend_from_slice(b"1");
src.extend(std::iter::repeat(b')').take(depth));
let r = eval(&src, &[]);
assert_eq!(
r,
Ok(true),
"Fix: a 32-deep parenthesized #if expression is legal and must evaluate"
);
}
mod robustness {
use super::eval;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(4096))]
#[test]
fn arbitrary_bytes_never_panic(bytes in proptest::collection::vec(any::<u8>(), 0..512)) {
let first = eval(&bytes, &[]);
let second = eval(&bytes, &[]);
prop_assert_eq!(
first.is_ok(),
second.is_ok(),
"evaluator must be deterministic over identical input"
);
}
#[test]
fn expression_soup_never_panics(
tokens in proptest::collection::vec(
proptest::sample::select(
&b"0123456789()+-*/%!~&|^<>=?: \tabcdefABCDEF"[..]
),
0..256,
)
) {
let first = eval(&tokens, &[b"A".as_slice(), b"B".as_slice()]);
let second = eval(&tokens, &[b"A".as_slice(), b"B".as_slice()]);
prop_assert_eq!(
first.is_ok(),
second.is_ok(),
"evaluator must be deterministic over identical expression soup"
);
}
}
}
#[test]
fn has_attribute_returns_zero_and_consumes_argument() {
assert_eq!(eval(b"__has_attribute(visibility)", &[]), Ok(false));
assert_eq!(eval(b"__has_feature(c_static_assert)", &[]), Ok(false));
assert_eq!(eval(b"__has_extension(c_alignof)", &[]), Ok(false));
assert_eq!(eval(b"__has_warning(\"-Wfoo\")", &[]), Ok(false));
}
#[test]
fn has_builtin_uses_frontend_builtin_catalog() {
assert_eq!(eval(b"__has_builtin(__builtin_expect)", &[]), Ok(true));
assert_eq!(eval(b"__has_builtin(__builtin_popcount)", &[]), Ok(true));
assert_eq!(
eval(b"__has_constexpr_builtin(__builtin_bitreverse32)", &[]),
Ok(true)
);
assert_eq!(
eval(b"__has_builtin(__builtin_vyre_unknown)", &[]),
Ok(false)
);
assert_eq!(eval(b"__has_builtin(ordinary_identifier)", &[]), Ok(false));
}
#[test]
fn has_attribute_inside_or_chain() {
assert_eq!(
eval(
b"!__has_attribute(visibility) || defined(FALLBACK)",
&[b"FALLBACK"]
),
Ok(true)
);
assert_eq!(
eval(b"!__has_attribute(visibility) || defined(FALLBACK)", &[]),
Ok(true)
);
}
#[test]
fn has_c_attribute_with_scoped_name() {
assert_eq!(eval(b"__has_c_attribute(gnu::packed)", &[]), Ok(false));
assert_eq!(
eval(b"__has_cpp_attribute(clang::trivial_abi)", &[]),
Ok(false)
);
}
#[test]
fn is_identifier_matches_keyword_guard_semantics() {
assert_eq!(eval(b"__is_identifier(regular_name)", &[]), Ok(true));
assert_eq!(eval(b"__is_identifier(__int128)", &[]), Ok(false));
assert_eq!(eval(b"__is_identifier(typeof)", &[]), Ok(false));
assert_eq!(eval(b"__is_identifier(_Static_assert)", &[]), Ok(false));
}
#[test]
fn has_include_angle_and_quoted() {
assert_eq!(eval(b"__has_include(<threads.h>)", &[]), Ok(false));
assert_eq!(eval(b"__has_include(\"local.h\")", &[]), Ok(false));
assert_eq!(eval(b"__has_include_next(<stdio.h>)", &[]), Ok(false));
}
#[test]
fn has_embed_consumes_resource_and_parameters() {
assert_eq!(eval(b"__has_embed(<asset.bin>)", &[]), Ok(false));
assert_eq!(eval(b"__has_embed(\"asset.bin\" limit(4))", &[]), Ok(false));
assert_eq!(
eval(b"__has_embed(__FILE__ limit (4) vendor::attr(42))", &[]),
Ok(false)
);
}
#[test]
fn has_include_rejects_unterminated_header() {
let err = eval(b"__has_include(<threads.h", &[]).expect_err("unterminated header");
assert!(
err.to_string().contains("close __has_include header"),
"unterminated header error: {err}"
);
}
#[test]
fn has_embed_rejects_unterminated_operands() {
let err_angle = eval(b"__has_embed(<asset.bin", &[]).expect_err("unterminated angle resource");
assert!(
err_angle.to_string().contains("close __has_embed resource"),
"unterminated embed angle error: {err_angle}"
);
let err_paren =
eval(b"__has_embed(\"asset.bin\" limit(4)", &[]).expect_err("unterminated embed tail");
assert!(
err_paren.to_string().contains("close __has_embed operator"),
"unterminated embed paren error: {err_paren}"
);
}
#[test]
fn has_attribute_rejects_missing_paren() {
let err = eval(b"__has_attribute visibility", &[]).expect_err("missing paren");
assert!(
err.to_string().contains("parenthesized argument"),
"missing-paren __has_attribute error: {err}"
);
}
#[test]
fn has_attribute_does_not_collide_with_macro_lookup() {
assert_eq!(
eval(b"__has_attribute(visibility)", &[b"__has_attribute"]),
Ok(false)
);
}
#[test]
fn modern_integer_literals_consume_digit_separators_and_suffixes() {
assert_eq!(eval(b"1'024ULL == 1024", &[]), Ok(true));
assert_eq!(eval(b"0xFF'00z == 65280", &[]), Ok(true));
assert_eq!(eval(b"0b1010'0101WB == 165", &[]), Ok(true));
assert_eq!(eval(b"0755'1uL > 0", &[]), Ok(true));
}