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
180 changes: 180 additions & 0 deletions bindgen-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,186 @@ fn test_macro_fallback_non_system_dir() {
}
}

#[test]
fn test_macro_fallback_header_contents() {
if let Some((9, _)) = clang_version().parsed {
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_header_contents_preserves_build_dir_files() {
if let Some((9, _)) = clang_version().parsed {
return;
}

let tmpdir = tempfile::tempdir().unwrap();
let victim = tmpdir.path().join(".macro_eval_header_0.h");
fs::write(&victim, "must survive\n").unwrap();

let actual = builder()
.disable_header_comment()
.header_contents(
"test.h",
"#define UINT32_C(c) c ## U\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 COMPOUND: u32 = 69;\n").unwrap();

assert_eq!(expected, actual);
assert_eq!("must survive\n", fs::read_to_string(victim).unwrap());
}

#[test]
fn test_macro_fallback_header_contents_preserves_primary_header_order() {
if let Some((9, _)) = clang_version().parsed {
return;
}

let tmpdir = tempfile::tempdir().unwrap();
let header = tmpdir.path().join("primary.h");
fs::write(
&header,
"#undef ORDER_VALUE\n#define ORDER_VALUE UINT32_C(1)\n",
)
.unwrap();

let actual = builder()
.disable_header_comment()
.header(header.to_str().unwrap())
.header_contents(
"override.h",
"#define UINT32_C(c) c ## U\n\
#undef ORDER_VALUE\n\
#define ORDER_VALUE UINT32_C(2)\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 ORDER_VALUE: u32 = 1;\n").unwrap();

assert_eq!(expected, actual);
}

#[test]
fn test_macro_fallback_header_contents_preserves_include_identity() {
if let Some((9, _)) = clang_version().parsed {
return;
}

let tmpdir = tempfile::tempdir().unwrap();
let header = tmpdir.path().join("primary.h");
fs::write(
&header,
"#undef BINDGEN_VIRTUAL_FUNC_3353\n\
#include \"bindgen_virtual_dependency_3353.h\"\n\
#define FROM_INCLUDE BINDGEN_VIRTUAL_FUNC_3353(7)\n",
)
.unwrap();
let virtual_dir = tmpdir.path().join("virtual");
fs::create_dir(&virtual_dir).unwrap();
let virtual_header = virtual_dir.join("bindgen_virtual_dependency_3353.h");

let actual = builder()
.disable_header_comment()
.header(header.to_str().unwrap())
.header_contents(
virtual_header.to_str().unwrap(),
"#define BINDGEN_VIRTUAL_FUNC_3353(c) c ## U\n",
)
.clang_macro_fallback()
.clang_macro_fallback_build_dir(tmpdir.path())
.clang_arg(format!("-I{}", virtual_dir.display()))
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

let actual = format_code(actual).unwrap();
let expected = format_code("pub const FROM_INCLUDE: u32 = 7;\n").unwrap();

assert_eq!(expected, actual);
}

#[test]
fn test_macro_fallback_header_contents_preserves_full_include_identity() {
if let Some((9, _)) = clang_version().parsed {
return;
}

let tmpdir = tempfile::tempdir().unwrap();
let header = tmpdir.path().join("primary.h");
fs::write(&header, "#define FROM_COMMON COMMON_MACRO(9)\n").unwrap();

let virtual_dir = tmpdir.path().join("virtual");
let a_dir = virtual_dir.join("a");
let b_dir = virtual_dir.join("b");
fs::create_dir_all(&a_dir).unwrap();
fs::create_dir_all(&b_dir).unwrap();
let a_header = a_dir.join("common.h");
let b_header = b_dir.join("common.h");

let actual = builder()
.disable_header_comment()
.header(header.to_str().unwrap())
.header_contents(
a_header.to_str().unwrap(),
"#define COMMON_MACRO(c) 0\n",
)
.header_contents(
b_header.to_str().unwrap(),
"#undef COMMON_MACRO\n#define COMMON_MACRO(c) c ## U\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 FROM_COMMON: u32 = 9;\n").unwrap();

assert_eq!(expected, actual);
}

#[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
80 changes: 64 additions & 16 deletions bindgen/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ use std::fs::OpenOptions;
use std::hash::Hash;
use std::hash::Hasher;
use std::os::raw::{c_char, c_int, c_longlong, c_uint, c_ulong, c_ulonglong};
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::OnceLock;
use std::{mem, ptr, slice};

/// Counter used to create collision-free macro fallback scratch directories.
static NEXT_FALLBACK_ARTIFACT_DIR: AtomicUsize = AtomicUsize::new(0);

/// Type representing a clang attribute.
///
/// Values of this type can be used to check for different attributes using the `has_attrs`
Expand Down Expand Up @@ -1911,12 +1916,61 @@ impl Drop for TranslationUnit {
}
}

/// Translation unit used for macro fallback parsing
pub(crate) struct FallbackTranslationUnit {
/// Scratch files used by macro fallback parsing.
pub(crate) struct FallbackArtifacts {
file_path: String,
pch_path: String,
dir_path: String,
}

impl FallbackArtifacts {
/// Create a private artifact directory under `build_dir`.
pub(crate) fn new(build_dir: &str) -> Option<Self> {
let process_id = std::process::id();
let dir = loop {
let dir_index =
NEXT_FALLBACK_ARTIFACT_DIR.fetch_add(1, Ordering::Relaxed);
let path = Path::new(build_dir)
.join(format!(".macro_eval_{process_id}_{dir_index}"));
match std::fs::create_dir(&path) {
Ok(()) => break path,
Err(error)
if error.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return None,
}
};
let dir_path = dir.to_str()?.to_owned();
Some(FallbackArtifacts {
file_path: format!("{dir_path}/.macro_eval.c"),
pch_path: format!("{dir_path}/.macro_eval-precompile.h.pch"),
dir_path,
})
}

/// Path to the C file used for fallback macro parsing.
pub(crate) fn file_path(&self) -> &str {
&self.file_path
}

/// Path to the precompiled header used for fallback macro parsing.
pub(crate) fn pch_path(&self) -> &str {
&self.pch_path
}
}

impl Drop for FallbackArtifacts {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.file_path);
let _ = std::fs::remove_file(&self.pch_path);
let _ = std::fs::remove_dir(&self.dir_path);
}
}

/// Translation unit used for macro fallback parsing.
pub(crate) struct FallbackTranslationUnit {
idx: Box<Index>,
tu: TranslationUnit,
artifacts: FallbackArtifacts,
}

impl fmt::Debug for FallbackTranslationUnit {
Expand All @@ -1928,31 +1982,29 @@ impl fmt::Debug for FallbackTranslationUnit {
impl FallbackTranslationUnit {
/// Create a new fallback translation unit
pub(crate) fn new(
file: String,
pch_path: String,
artifacts: FallbackArtifacts,
c_args: &[Box<str>],
) -> Option<Self> {
// Create empty file
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file)
.open(artifacts.file_path())
.ok()?;

let f_index = Box::new(Index::new(true, false));
let f_translation_unit = TranslationUnit::parse(
&f_index,
&file,
artifacts.file_path(),
c_args,
&[],
CXTranslationUnit_None,
)?;
Some(FallbackTranslationUnit {
file_path: file,
pch_path,
tu: f_translation_unit,
idx: f_index,
artifacts,
})
}

Expand All @@ -1966,7 +2018,10 @@ impl FallbackTranslationUnit {
&mut self,
unsaved_contents: &str,
) -> Result<(), CXErrorCode> {
let unsaved = &[UnsavedFile::new(&self.file_path, unsaved_contents)];
let unsaved = &[UnsavedFile::new(
self.artifacts.file_path(),
unsaved_contents,
)];
let mut c_unsaved: Vec<CXUnsavedFile> =
unsaved.iter().map(|f| f.x).collect();
let ret = unsafe {
Expand All @@ -1985,13 +2040,6 @@ impl FallbackTranslationUnit {
}
}

impl Drop for FallbackTranslationUnit {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.file_path);
let _ = std::fs::remove_file(&self.pch_path);
}
}

/// A diagnostic message generated while parsing a translation unit.
pub(crate) struct Diagnostic {
x: CXDiagnostic,
Expand Down
Loading
Loading