Skip to content

Commit 3a52ac5

Browse files
committed
Workaround for expansion of funcion-like macros
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.
1 parent b5a6813 commit 3a52ac5

File tree

5 files changed

+212
-2
lines changed

5 files changed

+212
-2
lines changed

bindgen-cli/options.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,9 @@ struct BindgenCommand {
400400
/// Wrap unsafe operations in unsafe blocks.
401401
#[arg(long)]
402402
wrap_unsafe_ops: bool,
403+
/// Enable fallback for clang macro parsing.
404+
#[arg(long)]
405+
clang_macro_fallback: bool,
403406
/// 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.
404407
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)]
405408
with_derive_custom: Vec<(Vec<String>, String)>,
@@ -554,6 +557,7 @@ where
554557
merge_extern_blocks,
555558
override_abi,
556559
wrap_unsafe_ops,
560+
clang_macro_fallback,
557561
with_derive_custom,
558562
with_derive_custom_struct,
559563
with_derive_custom_enum,
@@ -1023,6 +1027,10 @@ where
10231027
builder = builder.wrap_unsafe_ops(true);
10241028
}
10251029

1030+
if clang_macro_fallback {
1031+
builder = builder.clang_macro_fallback();
1032+
}
1033+
10261034
#[derive(Debug)]
10271035
struct CustomDeriveCallback {
10281036
derives: Vec<String>,

bindgen/clang.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::cmp;
1010

1111
use std::ffi::{CStr, CString};
1212
use std::fmt;
13+
use std::fs::OpenOptions;
1314
use std::hash::Hash;
1415
use std::hash::Hasher;
1516
use std::os::raw::{c_char, c_int, c_longlong, c_uint, c_ulong, c_ulonglong};
@@ -1868,6 +1869,27 @@ impl TranslationUnit {
18681869
}
18691870
}
18701871

1872+
/// Save a translation unit to the given file.
1873+
pub(crate) fn save(&mut self, file: &str) -> Result<(), CXSaveError> {
1874+
let file = if let Ok(cstring) = CString::new(file) {
1875+
cstring
1876+
} else {
1877+
return Err(CXSaveError_Unknown);
1878+
};
1879+
let ret = unsafe {
1880+
clang_saveTranslationUnit(
1881+
self.x,
1882+
file.as_ptr(),
1883+
clang_defaultSaveOptions(self.x),
1884+
)
1885+
};
1886+
if ret != 0 {
1887+
Err(ret)
1888+
} else {
1889+
Ok(())
1890+
}
1891+
}
1892+
18711893
/// Is this the null translation unit?
18721894
pub(crate) fn is_null(&self) -> bool {
18731895
self.x.is_null()
@@ -1882,6 +1904,72 @@ impl Drop for TranslationUnit {
18821904
}
18831905
}
18841906

1907+
/// Translation unit used for macro fallback parsing
1908+
pub(crate) struct FallbackTranslationUnit {
1909+
file_path: &'static str,
1910+
idx: Box<Index>,
1911+
tu: TranslationUnit,
1912+
}
1913+
1914+
impl fmt::Debug for FallbackTranslationUnit {
1915+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1916+
write!(fmt, "FallbackTranslationUnit {{ }}")
1917+
}
1918+
}
1919+
1920+
impl FallbackTranslationUnit {
1921+
/// Create a new fallback translation unit
1922+
pub(crate) fn new(file: &'static str, c_args: &[Box<str>]) -> Option<Self> {
1923+
// Create empty file
1924+
OpenOptions::new()
1925+
.write(true)
1926+
.create(true)
1927+
.truncate(true)
1928+
.open(file)
1929+
.ok()?;
1930+
1931+
let f_index = Box::new(Index::new(true, false));
1932+
let f_translation_unit = TranslationUnit::parse(
1933+
&f_index,
1934+
file,
1935+
c_args,
1936+
&[],
1937+
CXTranslationUnit_None,
1938+
)?;
1939+
Some(FallbackTranslationUnit {
1940+
file_path: file,
1941+
tu: f_translation_unit,
1942+
idx: f_index,
1943+
})
1944+
}
1945+
1946+
/// Get reference to underlying translation unit.
1947+
pub(crate) fn translation_unit(&self) -> &TranslationUnit {
1948+
&self.tu
1949+
}
1950+
1951+
/// Reparse a translation unit.
1952+
pub(crate) fn reparse(&mut self, unsaved: &[UnsavedFile]) -> bool {
1953+
let mut c_unsaved: Vec<CXUnsavedFile> =
1954+
unsaved.iter().map(|f| f.x).collect();
1955+
let ret = unsafe {
1956+
clang_reparseTranslationUnit(
1957+
self.tu.x,
1958+
unsaved.len() as c_uint,
1959+
c_unsaved.as_mut_ptr(),
1960+
clang_defaultReparseOptions(self.tu.x),
1961+
)
1962+
};
1963+
ret == 0
1964+
}
1965+
}
1966+
1967+
impl Drop for FallbackTranslationUnit {
1968+
fn drop(&mut self) {
1969+
let _ = std::fs::remove_file(&self.file_path);
1970+
}
1971+
}
1972+
18851973
/// A diagnostic message generated while parsing a translation unit.
18861974
pub(crate) struct Diagnostic {
18871975
x: CXDiagnostic,

bindgen/ir/context.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use std::borrow::Cow;
3030
use std::cell::{Cell, RefCell};
3131
use std::collections::{BTreeSet, HashMap as StdHashMap};
3232
use std::mem;
33+
use std::path::Path;
3334

3435
/// An identifier for some kind of IR item.
3536
#[derive(Debug, Copy, Clone, Eq, PartialOrd, Ord, Hash)]
@@ -376,6 +377,9 @@ pub(crate) struct BindgenContext {
376377
/// The translation unit for parsing.
377378
translation_unit: clang::TranslationUnit,
378379

380+
/// The translation unit for macro fallback parsing.
381+
fallback_tu: Option<clang::FallbackTranslationUnit>,
382+
379383
/// Target information that can be useful for some stuff.
380384
target_info: clang::TargetInfo,
381385

@@ -584,6 +588,7 @@ If you encounter an error missing from this list, please file an issue or a PR!"
584588
collected_typerefs: false,
585589
in_codegen: false,
586590
translation_unit,
591+
fallback_tu: None,
587592
target_info,
588593
options,
589594
generated_bindgen_complex: Cell::new(false),
@@ -2060,6 +2065,65 @@ If you encounter an error missing from this list, please file an issue or a PR!"
20602065
&self.translation_unit
20612066
}
20622067

2068+
/// Initialize fallback translation unit if it does not exist and
2069+
/// then return a mutable reference to the fallback translation unit.
2070+
pub(crate) fn try_ensure_fallback_translation_unit(
2071+
&mut self,
2072+
file: &'static str,
2073+
) -> Option<&mut clang::FallbackTranslationUnit> {
2074+
if self.fallback_tu.is_none() {
2075+
let index = clang::Index::new(false, false);
2076+
2077+
let mut c_args = Vec::new();
2078+
for input_header in self.options().input_headers.iter() {
2079+
let path = Path::new(input_header.as_ref());
2080+
let header_name = path
2081+
.file_name()
2082+
.and_then(|hn| hn.to_str())
2083+
.map(|s| s.to_owned());
2084+
let header_path = path
2085+
.parent()
2086+
.and_then(|hp| hp.to_str())
2087+
.map(|s| s.to_owned());
2088+
2089+
if let (Some(hp), Some(hn)) = (header_path, header_name) {
2090+
let header_path = if hp.is_empty() {
2091+
Path::new(".")
2092+
.canonicalize()
2093+
.map(|p| p.display().to_string())
2094+
.unwrap_or_else(|_| ".".to_string())
2095+
} else {
2096+
hp.clone()
2097+
};
2098+
let pch = format!("{header_path}/{hn}.pch");
2099+
2100+
if !Path::new(&pch).exists() {
2101+
let header = format!("{header_path}/{hn}");
2102+
let mut tu = clang::TranslationUnit::parse(
2103+
&index,
2104+
&header,
2105+
&[
2106+
"-x".to_owned().into_boxed_str(),
2107+
"c-header".to_owned().into_boxed_str(),
2108+
],
2109+
&[],
2110+
clang_sys::CXTranslationUnit_ForSerialization,
2111+
)?;
2112+
tu.save(&pch).ok()?;
2113+
}
2114+
2115+
c_args.push("-include-pch".to_string().into_boxed_str());
2116+
c_args.push(pch.into_boxed_str());
2117+
}
2118+
}
2119+
2120+
self.fallback_tu =
2121+
Some(clang::FallbackTranslationUnit::new(file, &c_args)?);
2122+
}
2123+
2124+
self.fallback_tu.as_mut()
2125+
}
2126+
20632127
/// Have we parsed the macro named `macro_name` already?
20642128
pub(crate) fn parsed_macro(&self, macro_name: &[u8]) -> bool {
20652129
self.parsed_macros.contains_key(macro_name)

bindgen/ir/var.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,44 @@ impl ClangSubItemParser for Var {
389389
}
390390
}
391391

392+
fn parse_macro_clang_fallback(
393+
ctx: &mut BindgenContext,
394+
cursor: &clang::Cursor,
395+
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
396+
if !ctx.options().clang_macro_fallback {
397+
return None;
398+
}
399+
400+
let file = ".macro_eval.c";
401+
let contents = format!("int main() {{ {}; }}", cursor.spelling(),);
402+
403+
let ftu = ctx.try_ensure_fallback_translation_unit(file)?;
404+
ftu.reparse(&[clang::UnsavedFile::new(file, &contents)]);
405+
// Children of root node of AST
406+
let root_children = ftu.translation_unit().cursor().collect_children();
407+
// Last child in root is function declaration
408+
// Should be FunctionDecl
409+
let main_func = root_children.last()?;
410+
// Children should all be statements in function declaration
411+
let all_stmts = main_func.collect_children();
412+
// First child in all_stmts should be the statement containing the macro to evaluate
413+
// Should be CompoundStmt
414+
let macro_stmt = all_stmts.first()?;
415+
// Children should all be expressions from the compound statement
416+
let paren_exprs = macro_stmt.collect_children();
417+
// First child in all_exprs is the expression utilizing the given macro to be evaluated
418+
// Should be ParenExpr
419+
let paren = paren_exprs.first()?;
420+
421+
Some((
422+
cursor.spelling().into_bytes(),
423+
cexpr::expr::EvalResult::Int(Wrapping(paren.evaluate()?.as_int()?)),
424+
))
425+
}
426+
392427
/// Try and parse a macro using all the macros parsed until now.
393428
fn parse_macro(
394-
ctx: &BindgenContext,
429+
ctx: &mut BindgenContext,
395430
cursor: &clang::Cursor,
396431
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
397432
use cexpr::expr;
@@ -402,7 +437,7 @@ fn parse_macro(
402437

403438
match parser.macro_definition(&cexpr_tokens) {
404439
Ok((_, (id, val))) => Some((id.into(), val)),
405-
_ => None,
440+
_ => parse_macro_clang_fallback(ctx, cursor),
406441
}
407442
}
408443

bindgen/options/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,5 +2104,20 @@ options! {
21042104
}
21052105
},
21062106
as_args: "--emit-diagnostics",
2107+
},
2108+
/// Whether to use Clang evaluation on temporary files as a fallback for macros that fail to
2109+
/// parse.
2110+
clang_macro_fallback: bool {
2111+
methods: {
2112+
/// Use Clang as a fallback for macros that fail to parse using `CExpr`.
2113+
///
2114+
/// This uses a workaround to evaluate each macro in a temporary file. Because this
2115+
/// results in slower compilation, this option is opt-in.
2116+
pub fn clang_macro_fallback(mut self) -> Self {
2117+
self.options.clang_macro_fallback = true;
2118+
self
2119+
}
2120+
},
2121+
as_args: "--clang-macro-fallback",
21072122
}
21082123
}

0 commit comments

Comments
 (0)