Skip to content

Commit

Permalink
Workaround for expansion of function-like macros
Browse files Browse the repository at this point in the history
This commit resolves an issue where macros that evaluate to a constant
but have a function like macro in the macro body would not be properly
expanded by cexpr.

This adds an opt-in option to use Clang on intermediary files to
evaluate the macros one by one. This is opt-in largely because of the
compile time implications.
  • Loading branch information
jbaublitz committed Mar 19, 2024
1 parent 3b5ce9c commit b71bb6f
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 71 deletions.
16 changes: 16 additions & 0 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ struct BindgenCommand {
/// Wrap unsafe operations in unsafe blocks.
#[arg(long)]
wrap_unsafe_ops: bool,
/// Enable fallback for clang macro parsing.
#[arg(long)]
clang_macro_fallback: bool,
/// Set path for temporary files generated by fallback for clang macro parsing.
#[arg(long)]
clang_macro_fallback_build_dir: Option<PathBuf>,
/// Derive custom traits on any kind of type. The CUSTOM value must be of the shape REGEX=DERIVE where DERIVE is a coma-separated list of derive macros.
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)]
with_derive_custom: Vec<(Vec<String>, String)>,
Expand Down Expand Up @@ -554,6 +560,8 @@ where
merge_extern_blocks,
override_abi,
wrap_unsafe_ops,
clang_macro_fallback,
clang_macro_fallback_build_dir,
with_derive_custom,
with_derive_custom_struct,
with_derive_custom_enum,
Expand Down Expand Up @@ -1023,6 +1031,14 @@ where
builder = builder.wrap_unsafe_ops(true);
}

if clang_macro_fallback {
builder = builder.clang_macro_fallback();
}

if let Some(path) = clang_macro_fallback_build_dir {
builder = builder.clang_macro_fallback_build_dir(path);
}

#[derive(Debug)]
struct CustomDeriveCallback {
derives: Vec<String>,
Expand Down
4 changes: 4 additions & 0 deletions bindgen-tests/tests/expectations/tests/issue-753.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bindgen-tests/tests/headers/issue-753.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// bindgen-flags: --clang-macro-fallback

#define UINT32_C(c) c ## U

#define CONST UINT32_C(5)
#define OTHER_CONST UINT32_C(6)
#define LARGE_CONST UINT32_C(6 << 8)
96 changes: 96 additions & 0 deletions bindgen/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::cmp;

use std::ffi::{CStr, CString};
use std::fmt;
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};
Expand Down Expand Up @@ -1868,6 +1869,27 @@ impl TranslationUnit {
}
}

/// Save a translation unit to the given file.
pub(crate) fn save(&mut self, file: &str) -> Result<(), CXSaveError> {
let file = if let Ok(cstring) = CString::new(file) {
cstring
} else {
return Err(CXSaveError_Unknown);
};
let ret = unsafe {
clang_saveTranslationUnit(
self.x,
file.as_ptr(),
clang_defaultSaveOptions(self.x),
)
};
if ret != 0 {
Err(ret)
} else {
Ok(())
}
}

/// Is this the null translation unit?
pub(crate) fn is_null(&self) -> bool {
self.x.is_null()
Expand All @@ -1882,6 +1904,80 @@ impl Drop for TranslationUnit {
}
}

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

impl fmt::Debug for FallbackTranslationUnit {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "FallbackTranslationUnit {{ }}")
}
}

impl FallbackTranslationUnit {
/// Create a new fallback translation unit
pub(crate) fn new(file: String, c_args: &[Box<str>]) -> Option<Self> {
// Create empty file
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file)
.ok()?;

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

/// Get reference to underlying translation unit.
pub(crate) fn translation_unit(&self) -> &TranslationUnit {
&self.tu
}

/// Reparse a translation unit.
pub(crate) fn reparse(
&mut self,
unsaved_contents: &str,
) -> Result<(), CXErrorCode> {
let unsaved = &[UnsavedFile::new(&self.file_path, unsaved_contents)];
let mut c_unsaved: Vec<CXUnsavedFile> =
unsaved.iter().map(|f| f.x).collect();
let ret = unsafe {
clang_reparseTranslationUnit(
self.tu.x,
unsaved.len() as c_uint,
c_unsaved.as_mut_ptr(),
clang_defaultReparseOptions(self.tu.x),
)
};
if ret != 0 {
Err(ret)
} else {
Ok(())
}
}
}

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

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

0 comments on commit b71bb6f

Please sign in to comment.