Skip to content

Commit 3990e2f

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 3990e2f

File tree

5 files changed

+210
-2
lines changed

5 files changed

+210
-2
lines changed

bindgen-cli/options.rs

+8
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

+75
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,23 @@ impl TranslationUnit {
18681868
}
18691869
}
18701870

1871+
/// Save a translation unit to the given file.
1872+
pub(crate) fn save(&mut self, file: &str) -> bool {
1873+
let file = if let Ok(cstring) = CString::new(file) {
1874+
cstring
1875+
} else {
1876+
return false;
1877+
};
1878+
let ret = unsafe {
1879+
clang_saveTranslationUnit(
1880+
self.x,
1881+
file.as_ptr(),
1882+
clang_defaultSaveOptions(self.x),
1883+
)
1884+
};
1885+
ret == 0
1886+
}
1887+
18711888
/// Is this the null translation unit?
18721889
pub(crate) fn is_null(&self) -> bool {
18731890
self.x.is_null()
@@ -1882,6 +1899,64 @@ impl Drop for TranslationUnit {
18821899
}
18831900
}
18841901

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

bindgen/ir/context.rs

+19
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+
/// Initialize 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

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

403491
match parser.macro_definition(&cexpr_tokens) {
404492
Ok((_, (id, val))) => Some((id.into(), val)),
493+
_ if ctx.options().clang_macro_fallback => {
494+
parse_macro_clang_fallback(ctx, cursor)
495+
}
405496
_ => None,
406497
}
407498
}

bindgen/options/mod.rs

+15
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)