Skip to content

Commit 9ded89d

Browse files
committed
Workaround for expansion of function-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 3b5ce9c commit 9ded89d

File tree

8 files changed

+287
-5
lines changed

8 files changed

+287
-5
lines changed

bindgen-cli/options.rs

+16
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ 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,
406+
/// Set path for temporary files generated by fallback for clang macro parsing.
407+
#[arg(long)]
408+
clang_macro_fallback_build_dir: Option<PathBuf>,
403409
/// 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.
404410
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)]
405411
with_derive_custom: Vec<(Vec<String>, String)>,
@@ -554,6 +560,8 @@ where
554560
merge_extern_blocks,
555561
override_abi,
556562
wrap_unsafe_ops,
563+
clang_macro_fallback,
564+
clang_macro_fallback_build_dir,
557565
with_derive_custom,
558566
with_derive_custom_struct,
559567
with_derive_custom_enum,
@@ -1023,6 +1031,14 @@ where
10231031
builder = builder.wrap_unsafe_ops(true);
10241032
}
10251033

1034+
if clang_macro_fallback {
1035+
builder = builder.clang_macro_fallback();
1036+
}
1037+
1038+
if let Some(path) = clang_macro_fallback_build_dir {
1039+
builder = builder.clang_macro_fallback_build_dir(path);
1040+
}
1041+
10261042
#[derive(Debug)]
10271043
struct CustomDeriveCallback {
10281044
derives: Vec<String>,

bindgen-tests/tests/expectations/tests/issue-753.rs

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-tests/tests/expectations/tests/libclang-9/issue-753.rs

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// bindgen-flags: --clang-macro-fallback
2+
3+
#define UINT32_C(c) c ## U
4+
5+
#define CONST UINT32_C(5)
6+
#define OTHER_CONST UINT32_C(6)
7+
#define LARGE_CONST UINT32_C(6 << 8)

bindgen/clang.rs

+105
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,89 @@ impl Drop for TranslationUnit {
18821904
}
18831905
}
18841906

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

bindgen/ir/context.rs

+76
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,77 @@ 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+
) -> Option<&mut clang::FallbackTranslationUnit> {
2073+
if self.fallback_tu.is_none() {
2074+
let file = format!(
2075+
"{}/.macro_eval.c",
2076+
match self.options().clang_macro_fallback_build_dir {
2077+
Some(ref path) => path.as_os_str().to_str()?,
2078+
None => ".",
2079+
}
2080+
);
2081+
2082+
let index = clang::Index::new(false, false);
2083+
2084+
let mut c_args = Vec::new();
2085+
let mut pch_paths = Vec::new();
2086+
for input_header in self.options().input_headers.iter() {
2087+
let path = Path::new(input_header.as_ref());
2088+
let header_name = path
2089+
.file_name()
2090+
.and_then(|hn| hn.to_str())
2091+
.map(|s| s.to_owned());
2092+
let header_path = path
2093+
.parent()
2094+
.and_then(|hp| hp.to_str())
2095+
.map(|s| s.to_owned());
2096+
2097+
let (header, pch) = if let (Some(ref hp), Some(hn)) =
2098+
(header_path, header_name)
2099+
{
2100+
let header_path = if hp.is_empty() { "." } else { hp };
2101+
let header = format!("{header_path}/{hn}");
2102+
let pch_path = if let Some(ref path) =
2103+
self.options().clang_macro_fallback_build_dir
2104+
{
2105+
path.as_os_str().to_str()?
2106+
} else {
2107+
header_path
2108+
};
2109+
(header, format!("{pch_path}/{hn}.pch"))
2110+
} else {
2111+
return None;
2112+
};
2113+
2114+
let mut tu = clang::TranslationUnit::parse(
2115+
&index,
2116+
&header,
2117+
&[
2118+
"-x".to_owned().into_boxed_str(),
2119+
"c-header".to_owned().into_boxed_str(),
2120+
],
2121+
&[],
2122+
clang_sys::CXTranslationUnit_ForSerialization,
2123+
)?;
2124+
tu.save(&pch).ok()?;
2125+
2126+
c_args.push("-include-pch".to_string().into_boxed_str());
2127+
c_args.push(pch.clone().into_boxed_str());
2128+
pch_paths.push(pch);
2129+
}
2130+
2131+
self.fallback_tu = Some(clang::FallbackTranslationUnit::new(
2132+
file, pch_paths, &c_args,
2133+
)?);
2134+
}
2135+
2136+
self.fallback_tu.as_mut()
2137+
}
2138+
20632139
/// Have we parsed the macro named `macro_name` already?
20642140
pub(crate) fn parsed_macro(&self, macro_name: &[u8]) -> bool {
20652141
self.parsed_macros.contains_key(macro_name)

bindgen/ir/var.rs

+44-2
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,51 @@ impl ClangSubItemParser for Var {
389389
}
390390
}
391391

392+
/// This function uses a [`FallbackTranslationUnit`][clang::FallbackTranslationUnit] to parse each
393+
/// macro that cannot be parsed by the normal bindgen process for `#define`s.
394+
///
395+
/// To construct the [`FallbackTranslationUnit`][clang::FallbackTranslationUnit], first precompiled
396+
/// headers are generated for all input headers. An empty temporary `.c` file is generated to pass
397+
/// to the translation unit. On the evaluation of each macro, a [`String`] is generated with the
398+
/// new contents of the empty file and passed in for reparsing. The precompiled headers and
399+
/// preservation of the [`FallbackTranslationUnit`][clang::FallbackTranslationUnit] across macro
400+
/// evaluations are both optimizations that have significantly improved the performance.
401+
fn parse_macro_clang_fallback(
402+
ctx: &mut BindgenContext,
403+
cursor: &clang::Cursor,
404+
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
405+
if !ctx.options().clang_macro_fallback {
406+
return None;
407+
}
408+
409+
let ftu = ctx.try_ensure_fallback_translation_unit()?;
410+
let contents = format!("int main() {{ {}; }}", cursor.spelling(),);
411+
ftu.reparse(&contents).ok()?;
412+
// Children of root node of AST
413+
let root_children = ftu.translation_unit().cursor().collect_children();
414+
// Last child in root is function declaration
415+
// Should be FunctionDecl
416+
let main_func = root_children.last()?;
417+
// Children should all be statements in function declaration
418+
let all_stmts = main_func.collect_children();
419+
// First child in all_stmts should be the statement containing the macro to evaluate
420+
// Should be CompoundStmt
421+
let macro_stmt = all_stmts.first()?;
422+
// Children should all be expressions from the compound statement
423+
let paren_exprs = macro_stmt.collect_children();
424+
// First child in all_exprs is the expression utilizing the given macro to be evaluated
425+
// Should be ParenExpr
426+
let paren = paren_exprs.first()?;
427+
428+
Some((
429+
cursor.spelling().into_bytes(),
430+
cexpr::expr::EvalResult::Int(Wrapping(paren.evaluate()?.as_int()?)),
431+
))
432+
}
433+
392434
/// Try and parse a macro using all the macros parsed until now.
393435
fn parse_macro(
394-
ctx: &BindgenContext,
436+
ctx: &mut BindgenContext,
395437
cursor: &clang::Cursor,
396438
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
397439
use cexpr::expr;
@@ -402,7 +444,7 @@ fn parse_macro(
402444

403445
match parser.macro_definition(&cexpr_tokens) {
404446
Ok((_, (id, val))) => Some((id.into(), val)),
405-
_ => None,
447+
_ => parse_macro_clang_fallback(ctx, cursor),
406448
}
407449
}
408450

bindgen/options/mod.rs

+34-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ use crate::HashMap;
2121
use crate::DEFAULT_ANON_FIELDS_PREFIX;
2222

2323
use std::env;
24-
#[cfg(feature = "experimental")]
25-
use std::path::Path;
26-
use std::path::PathBuf;
24+
use std::path::{Path, PathBuf};
2725
use std::rc::Rc;
2826

2927
use as_args::AsArgs;
@@ -2107,5 +2105,38 @@ options! {
21072105
}
21082106
},
21092107
as_args: "--emit-diagnostics",
2108+
},
2109+
/// Whether to use Clang evaluation on temporary files as a fallback for macros that fail to
2110+
/// parse.
2111+
clang_macro_fallback: bool {
2112+
methods: {
2113+
/// Use Clang as a fallback for macros that fail to parse using `CExpr`.
2114+
///
2115+
/// This uses a workaround to evaluate each macro in a temporary file. Because this
2116+
/// results in slower compilation, this option is opt-in.
2117+
pub fn clang_macro_fallback(mut self) -> Self {
2118+
self.options.clang_macro_fallback = true;
2119+
self
2120+
}
2121+
},
2122+
as_args: "--clang-macro-fallback",
2123+
}
2124+
/// Path to use for temporary files created by clang macro fallback code like precompiled
2125+
/// headers.
2126+
clang_macro_fallback_build_dir: Option<PathBuf> {
2127+
methods: {
2128+
/// Set a path to a directory to which `.c` and `.h.pch` files should be written for the
2129+
/// purpose of using clang to evaluate macros that can't be easily parsed.
2130+
///
2131+
/// The default location for `.h.pch` files is the directory that the corresponding
2132+
/// `.h` file is located in. The default for the temporary `.c` file used for clang
2133+
/// parsing is the current working directory. Both of these defaults are overridden
2134+
/// by this option.
2135+
pub fn clang_macro_fallback_build_dir<P: AsRef<Path>>(mut self, path: P) -> Self {
2136+
self.options.clang_macro_fallback_build_dir = Some(path.as_ref().to_owned());
2137+
self
2138+
}
2139+
},
2140+
as_args: "--clang-macro-fallback-build-dir",
21102141
}
21112142
}

0 commit comments

Comments
 (0)