Skip to content

Commit 104bcfe

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 104bcfe

File tree

5 files changed

+216
-3
lines changed

5 files changed

+216
-3
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: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,27 @@ impl TranslationUnit {
18681868
}
18691869
}
18701870

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

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

bindgen/ir/context.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ pub(crate) struct BindgenContext {
376376
/// The translation unit for parsing.
377377
translation_unit: clang::TranslationUnit,
378378

379+
/// The translation unit for macro fallback parsing.
380+
fallback_tu: Option<clang::FallbackTranslationUnit>,
381+
379382
/// Target information that can be useful for some stuff.
380383
target_info: clang::TargetInfo,
381384

@@ -584,6 +587,7 @@ If you encounter an error missing from this list, please file an issue or a PR!"
584587
collected_typerefs: false,
585588
in_codegen: false,
586589
translation_unit,
590+
fallback_tu: None,
587591
target_info,
588592
options,
589593
generated_bindgen_complex: Cell::new(false),
@@ -2060,6 +2064,21 @@ If you encounter an error missing from this list, please file an issue or a PR!"
20602064
&self.translation_unit
20612065
}
20622066

2067+
/// Initialize fallback translation unit
2068+
pub(crate) fn init_fallback_translation_unit(
2069+
&mut self,
2070+
fallback_tu: clang::FallbackTranslationUnit,
2071+
) {
2072+
self.fallback_tu = Some(fallback_tu);
2073+
}
2074+
2075+
/// Get a mutable reference to fallback translation unit
2076+
pub(crate) fn fallback_translation_unit_mut(
2077+
&mut self,
2078+
) -> Option<&mut clang::FallbackTranslationUnit> {
2079+
self.fallback_tu.as_mut()
2080+
}
2081+
20632082
/// Have we parsed the macro named `macro_name` already?
20642083
pub(crate) fn parsed_macro(&self, macro_name: &[u8]) -> bool {
20652084
self.parsed_macros.contains_key(macro_name)

bindgen/ir/var.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ use super::int::IntKind;
88
use super::item::Item;
99
use super::ty::{FloatKind, TypeKind};
1010
use crate::callbacks::{ItemInfo, ItemKind, MacroParsingBehavior};
11-
use crate::clang;
1211
use crate::clang::ClangToken;
12+
use crate::clang::{self, FallbackTranslationUnit, TranslationUnit};
1313
use crate::parse::{ClangSubItemParser, ParseError, ParseResult};
1414

15+
use std::fs::OpenOptions;
1516
use std::io;
17+
use std::io::Write;
1618
use std::num::Wrapping;
19+
use std::path::Path;
1720

1821
/// The type for a constant variable.
1922
#[derive(Debug)]
@@ -389,9 +392,98 @@ impl ClangSubItemParser for Var {
389392
}
390393
}
391394

395+
fn parse_macro_clang_fallback(
396+
ctx: &mut BindgenContext,
397+
cursor: &clang::Cursor,
398+
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
399+
if !ctx.options().clang_macro_fallback {
400+
return None;
401+
}
402+
403+
let file = ".macro_eval.c";
404+
let contents = format!("int main() {{ {}; }}", cursor.spelling(),);
405+
406+
let root = if let Some(ftu) = ctx.fallback_translation_unit_mut() {
407+
ftu.reparse(&[clang::UnsavedFile::new(file, &contents)]);
408+
ftu.translation_unit().cursor().collect_children()
409+
} else {
410+
let index = clang::Index::new(false, false);
411+
412+
let mut c_args = Vec::new();
413+
for input_header in ctx.options().input_headers.iter() {
414+
let path = Path::new(input_header.as_ref());
415+
let header_name = path
416+
.file_name()
417+
.and_then(|hn| hn.to_str())
418+
.map(|s| s.to_owned());
419+
let header_path = path
420+
.parent()
421+
.and_then(|hp| hp.to_str())
422+
.map(|s| s.to_owned());
423+
424+
if let (Some(hp), Some(hn)) = (header_path, header_name) {
425+
let header_path = if hp.is_empty() {
426+
Path::new(".")
427+
.canonicalize()
428+
.map(|p| p.display().to_string())
429+
.unwrap_or_else(|_| ".".to_string())
430+
} else {
431+
hp.clone()
432+
};
433+
let pch = format!("{header_path}/{hn}.pch");
434+
435+
if !Path::new(&pch).exists() {
436+
let header = format!("{header_path}/{hn}");
437+
let mut tu = TranslationUnit::parse(
438+
&index,
439+
&header,
440+
&[
441+
"-x".to_owned().into_boxed_str(),
442+
"c-header".to_owned().into_boxed_str(),
443+
],
444+
&[],
445+
clang_sys::CXTranslationUnit_ForSerialization,
446+
)?;
447+
tu.save(&pch).ok()?;
448+
}
449+
450+
c_args.push("-include-pch".to_string().into_boxed_str());
451+
c_args.push(pch.into_boxed_str());
452+
}
453+
}
454+
455+
OpenOptions::new()
456+
.write(true)
457+
.create(true)
458+
.truncate(true)
459+
.open(file)
460+
.ok()?
461+
.write_all(contents.as_bytes())
462+
.ok()?;
463+
464+
let f_translation_unit = FallbackTranslationUnit::new(file, &c_args)?;
465+
let root = f_translation_unit
466+
.translation_unit()
467+
.cursor()
468+
.collect_children();
469+
ctx.init_fallback_translation_unit(f_translation_unit);
470+
root
471+
};
472+
473+
let all_exprs = root.last()?.collect_children();
474+
let paren = all_exprs.first()?.collect_children();
475+
476+
Some((
477+
cursor.spelling().into_bytes(),
478+
cexpr::expr::EvalResult::Int(Wrapping(
479+
paren.first()?.evaluate()?.as_int()?,
480+
)),
481+
))
482+
}
483+
392484
/// Try and parse a macro using all the macros parsed until now.
393485
fn parse_macro(
394-
ctx: &BindgenContext,
486+
ctx: &mut BindgenContext,
395487
cursor: &clang::Cursor,
396488
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
397489
use cexpr::expr;
@@ -402,7 +494,7 @@ fn parse_macro(
402494

403495
match parser.macro_definition(&cexpr_tokens) {
404496
Ok((_, (id, val))) => Some((id.into(), val)),
405-
_ => None,
497+
_ => parse_macro_clang_fallback(ctx, cursor),
406498
}
407499
}
408500

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)