Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions bindgen-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,286 @@ fn test_macro_fallback_non_system_dir() {
}
}

/// clang 9's `clang_Cursor_Evaluate` returns `CXEval_UnExposed` instead
/// of `CXEval_Int` for macro-expanded expressions loaded through a PCH
/// built from materialized `header_contents()` files, so fallback
/// constant evaluation produces no results in that specific path.
fn clang9_fallback_header_contents_works() -> bool {
!matches!(clang_version().parsed, Some((9, _)))
}

#[test]
fn test_macro_fallback_header_contents() {
if !clang9_fallback_header_contents_works() {
return;
}
let tmpdir = tempfile::tempdir().unwrap();
let actual = builder()
.disable_header_comment()
.header_contents(
"test.h",
"#define UINT32_C(c) c ## U\n\
#define SIMPLE 42\n\
#define COMPOUND UINT32_C(69)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

let actual = format_code(actual).unwrap();

let expected = format_code(
"pub const SIMPLE: u32 = 42;\npub const COMPOUND: u32 = 69;\n",
)
.unwrap();

assert_eq!(expected, actual);
}

#[test]
fn test_macro_fallback_multiple_header_contents() {
if !clang9_fallback_header_contents_works() {
return;
}
let tmpdir = tempfile::tempdir().unwrap();
let actual = builder()
.disable_header_comment()
.header_contents("defs.h", "#define UINT32_C(c) c ## U\n")
.header_contents(
"test.h",
"#include \"defs.h\"\n\
#define MY_CONST UINT32_C(28)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

let actual = format_code(actual).unwrap();

// UINT32_C is a function-like macro, should not appear as a constant.
// MY_CONST should be evaluated by the fallback.
assert!(
actual.contains("pub const MY_CONST: u32 = 28;"),
"Expected MY_CONST constant in output:\n{actual}"
);
}

#[test]
fn test_macro_fallback_mixed_header_and_header_contents() {
if !clang9_fallback_header_contents_works() {
return;
}
let tmpdir = tempfile::tempdir().unwrap();
let actual = builder()
.disable_header_comment()
.header(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/headers/issue-753.h"
))
.header_contents(
"extra.h",
"#define UINT32_C(c) c ## U\n\
#define EXTRA_CONST UINT32_C(99)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.generate()
.unwrap()
.to_string();

let actual = format_code(actual).unwrap();

// Constants from the real header (issue-753.h defines UINT32_C and uses it)
assert!(
actual.contains("pub const CONST: u32 = 5;"),
"Expected CONST from real header in output:\n{actual}"
);
// Constants from the virtual header via fallback
assert!(
actual.contains("pub const EXTRA_CONST: u32 = 99;"),
"Expected EXTRA_CONST from header_contents in output:\n{actual}"
);
}

#[test]
fn test_macro_fallback_header_contents_duplicate_basename() {
if !clang9_fallback_header_contents_works() {
return;
}
let tmpdir = tempfile::tempdir().unwrap();
let actual = builder()
.disable_header_comment()
.header_contents(
"defs.h",
"#define UINT32_C(c) c ## U\n\
#define FROM_ROOT UINT32_C(11)\n",
)
.header_contents(
"sub/defs.h",
"#define UINT32_C(c) c ## U\n\
#define FROM_SUB UINT32_C(22)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

let actual = format_code(actual).unwrap();

assert!(
actual.contains("pub const FROM_ROOT: u32 = 11;"),
"Expected FROM_ROOT in output:\n{actual}"
);
assert!(
actual.contains("pub const FROM_SUB: u32 = 22;"),
"Expected FROM_SUB in output:\n{actual}"
);
}

#[test]
fn test_macro_fallback_header_contents_absolute_name() {
// Absolute names must not escape the build dir — they should be
// materialized safely under it, and the original file must not be
// clobbered or deleted.
let tmpdir = tempfile::tempdir().unwrap();
let victim = tmpdir.path().join("victim.h");
fs::write(&victim, "// original content\n").unwrap();

let abs_name = victim.to_str().unwrap();
let build_dir = tmpdir.path().join("fallback_build");
fs::create_dir_all(&build_dir).unwrap();

let actual = builder()
.disable_header_comment()
.header_contents(
abs_name,
"#define UINT32_C(c) c ## U\n\
#define ABS_CONST UINT32_C(55)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(&build_dir)
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

// The fallback-only constant should be evaluated (not on clang 9).
if clang9_fallback_header_contents_works() {
assert!(
actual.contains("pub const ABS_CONST: u32 = 55;"),
"Expected ABS_CONST in output:\n{actual}"
);
}

// The original file must not have been deleted by FallbackTU drop.
assert!(
victim.exists(),
"Original file at {abs_name} was deleted by fallback cleanup"
);
assert_eq!(
fs::read_to_string(&victim).unwrap(),
"// original content\n",
"Original file at {abs_name} was overwritten by materialization"
);
}

#[test]
fn test_macro_fallback_header_contents_parent_dir_escape() {
// Names with ".." must not escape the build dir or delete files
// outside it during FallbackTranslationUnit cleanup.
let tmpdir = tempfile::tempdir().unwrap();
let victim = tmpdir.path().join("victim.h");
fs::write(&victim, "// must survive\n").unwrap();

let build_dir = tmpdir.path().join("build");
fs::create_dir_all(&build_dir).unwrap();

let actual = builder()
.disable_header_comment()
.header_contents(
"../victim.h",
"#define UINT32_C(c) c ## U\n\
#define ESCAPE_CONST UINT32_C(77)\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(&build_dir)
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

if clang9_fallback_header_contents_works() {
assert!(
actual.contains("pub const ESCAPE_CONST: u32 = 77;"),
"Expected ESCAPE_CONST in output:\n{actual}"
);
}

// The file outside build_dir must not have been clobbered.
assert!(
victim.exists(),
"File outside build dir was deleted by fallback cleanup"
);
assert_eq!(fs::read_to_string(&victim).unwrap(), "// must survive\n",);
}

#[test]
fn test_macro_fallback_cross_target() {
if !clang9_fallback_header_contents_works() {
return;
}
// Subprocess-style test: setting TARGET as an env var in a parallel
// test is not robust, so we re-invoke the test binary as a child
// process with TARGET set to a non-host triple. The child runs the
// actual assertion; the parent just checks the exit status.
//
// __SIZEOF_POINTER__ is a clang builtin that equals the target's
// pointer width. On armv7 it's 4; on x86_64 it's 8. If the
// implicit --target isn't propagated to fallback_clang_args, the
// fallback TU evaluates with the host pointer size instead.
if env::var("__BINDGEN_CROSS_TARGET_INNER").is_ok() {
let tmpdir = tempfile::tempdir().unwrap();
let actual = builder()
.disable_header_comment()
.header_contents("test.h", "#define PTR_BYTES __SIZEOF_POINTER__\n")
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.generate()
.unwrap()
.to_string();
assert!(
actual.contains("pub const PTR_BYTES: u32 = 4;"),
"Expected 4-byte pointers for armv7 target, got:\n{actual}"
);
return;
}

let exe = env::current_exe().unwrap();
let output = std::process::Command::new(&exe)
.arg("test_macro_fallback_cross_target")
.arg("--exact")
.arg("--test-threads=1")
.env("TARGET", "armv7-unknown-linux-gnueabihf")
.env("__BINDGEN_CROSS_TARGET_INNER", "1")
.output()
.unwrap();

let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Cross-target fallback test failed.\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
}

#[test]
// Doesn't support executing sh file on Windows.
// We may want to implement it in Rust so that we support all systems.
Expand Down
8 changes: 8 additions & 0 deletions bindgen/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,9 @@ impl Drop for TranslationUnit {
pub(crate) struct FallbackTranslationUnit {
file_path: String,
pch_path: String,
/// Header files materialized from `header_contents()` that must remain on
/// disk while the PCH is in use (clang validates source file existence).
materialized_headers: Vec<String>,
idx: Box<Index>,
tu: TranslationUnit,
}
Expand All @@ -1931,6 +1934,7 @@ impl FallbackTranslationUnit {
file: String,
pch_path: String,
c_args: &[Box<str>],
materialized_headers: Vec<String>,
) -> Option<Self> {
// Create empty file
OpenOptions::new()
Expand All @@ -1951,6 +1955,7 @@ impl FallbackTranslationUnit {
Some(FallbackTranslationUnit {
file_path: file,
pch_path,
materialized_headers,
tu: f_translation_unit,
idx: f_index,
})
Expand Down Expand Up @@ -1989,6 +1994,9 @@ impl Drop for FallbackTranslationUnit {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.file_path);
let _ = std::fs::remove_file(&self.pch_path);
for path in &self.materialized_headers {
let _ = std::fs::remove_file(path);
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5364,7 +5364,8 @@ pub(crate) mod utils {
}

if !context.options().input_header_contents.is_empty() {
for (name, contents) in &context.options().input_header_contents {
for (name, contents, _) in &context.options().input_header_contents
{
writeln!(code, "// {name}\n{contents}")?;
}

Expand Down
Loading
Loading