Skip to content

Commit

Permalink
Workaround for expansion of funcion-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 11, 2024
1 parent b5a6813 commit 3990e2f
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 2 deletions.
8 changes: 8 additions & 0 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,9 @@ 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,
/// 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 +557,7 @@ where
merge_extern_blocks,
override_abi,
wrap_unsafe_ops,
clang_macro_fallback,
with_derive_custom,
with_derive_custom_struct,
with_derive_custom_enum,
Expand Down Expand Up @@ -1023,6 +1027,10 @@ where
builder = builder.wrap_unsafe_ops(true);
}

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

#[derive(Debug)]
struct CustomDeriveCallback {
derives: Vec<String>,
Expand Down
75 changes: 75 additions & 0 deletions bindgen/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,23 @@ impl TranslationUnit {
}
}

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

/// Is this the null translation unit?
pub(crate) fn is_null(&self) -> bool {
self.x.is_null()
Expand All @@ -1882,6 +1899,64 @@ 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: &str, c_args: &[Box<str>]) -> Option<Self> {
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.to_owned(),
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: &[UnsavedFile]) -> bool {
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(),
CXSaveTranslationUnit_None,
)
};
ret == 0
}
}

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
19 changes: 19 additions & 0 deletions bindgen/ir/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ pub(crate) struct BindgenContext {
/// The translation unit for parsing.
translation_unit: clang::TranslationUnit,

/// The translation unit for macro fallback parsing.
fallback_tu: Option<clang::FallbackTranslationUnit>,

/// Target information that can be useful for some stuff.
target_info: clang::TargetInfo,

Expand Down Expand Up @@ -584,6 +587,7 @@ If you encounter an error missing from this list, please file an issue or a PR!"
collected_typerefs: false,
in_codegen: false,
translation_unit,
fallback_tu: None,
target_info,
options,
generated_bindgen_complex: Cell::new(false),
Expand Down Expand Up @@ -2060,6 +2064,21 @@ If you encounter an error missing from this list, please file an issue or a PR!"
&self.translation_unit
}

/// Initialize fallback translation unit
pub(crate) fn init_fallback_translation_unit(
&mut self,
fallback_tu: clang::FallbackTranslationUnit,
) {
self.fallback_tu = Some(fallback_tu);
}

/// Initialize fallback translation unit
pub(crate) fn fallback_translation_unit_mut(
&mut self,
) -> Option<&mut clang::FallbackTranslationUnit> {
self.fallback_tu.as_mut()
}

/// Have we parsed the macro named `macro_name` already?
pub(crate) fn parsed_macro(&self, macro_name: &[u8]) -> bool {
self.parsed_macros.contains_key(macro_name)
Expand Down
95 changes: 93 additions & 2 deletions bindgen/ir/var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ use super::int::IntKind;
use super::item::Item;
use super::ty::{FloatKind, TypeKind};
use crate::callbacks::{ItemInfo, ItemKind, MacroParsingBehavior};
use crate::clang;
use crate::clang::ClangToken;
use crate::clang::{self, FallbackTranslationUnit, TranslationUnit};
use crate::parse::{ClangSubItemParser, ParseError, ParseResult};

use std::fs::OpenOptions;
use std::io;
use std::io::Write;
use std::num::Wrapping;
use std::path::Path;

/// The type for a constant variable.
#[derive(Debug)]
Expand Down Expand Up @@ -389,9 +392,94 @@ impl ClangSubItemParser for Var {
}
}

fn parse_macro_clang_fallback(
ctx: &mut BindgenContext,
cursor: &clang::Cursor,
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
let file = ".macro_eval.c".to_string();
let contents = format!("int main() {{ {}; }}", cursor.spelling(),);

let root = if let Some(ftu) = ctx.fallback_translation_unit_mut() {
ftu.reparse(&[clang::UnsavedFile::new(&file, &contents)]);
ftu.translation_unit().cursor().collect_children()
} else {
let index = clang::Index::new(false, false);

let mut c_args = Vec::new();
for input_header in ctx.options().input_headers.iter() {
let path = Path::new(input_header.as_ref());
let header_name = path
.file_name()
.and_then(|hn| hn.to_str())
.map(|s| s.to_owned());
let header_path = path
.parent()
.and_then(|hp| hp.to_str())
.map(|s| s.to_owned());

if let (Some(hp), Some(hn)) = (header_path, header_name) {
let header_path = if hp.is_empty() {
Path::new(".")
.canonicalize()
.map(|p| p.display().to_string())
.unwrap_or(".".to_string())
} else {
hp.clone()
};
let pch = format!("{header_path}/{hn}.pch");

if !Path::new(&pch).exists() {
let header = format!("{header_path}/{hn}");
let mut tu = TranslationUnit::parse(
&index,
&header,
&[
"-x".to_owned().into_boxed_str(),
"c-header".to_owned().into_boxed_str(),
],
&[],
clang_sys::CXTranslationUnit_ForSerialization,
)?;
tu.save(&pch);
}

c_args.push("-include-pch".to_string().into_boxed_str());
c_args.push(pch.into_boxed_str());
}
}

OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file)
.ok()?
.write_all(contents.as_bytes())
.ok()?;

let f_translation_unit = FallbackTranslationUnit::new(&file, &c_args)?;
let root = f_translation_unit
.translation_unit()
.cursor()
.collect_children();
ctx.init_fallback_translation_unit(f_translation_unit);
root
};

let all_exprs = root.last()?.collect_children();
let paren = all_exprs.first()?.collect_children();

Some((
cursor.spelling().into_bytes(),
cexpr::expr::EvalResult::Int(Wrapping(
paren.first()?.evaluate()?.as_int()?,
)),
))
}

/// Try and parse a macro using all the macros parsed until now.
fn parse_macro(
ctx: &BindgenContext,
ctx: &mut BindgenContext,
cursor: &clang::Cursor,
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
use cexpr::expr;
Expand All @@ -402,6 +490,9 @@ fn parse_macro(

match parser.macro_definition(&cexpr_tokens) {
Ok((_, (id, val))) => Some((id.into(), val)),
_ if ctx.options().clang_macro_fallback => {
parse_macro_clang_fallback(ctx, cursor)
}
_ => None,
}
}
Expand Down
15 changes: 15 additions & 0 deletions bindgen/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2104,5 +2104,20 @@ options! {
}
},
as_args: "--emit-diagnostics",
},
/// Whether to use Clang evaluation on temporary files as a fallback for macros that fail to
/// parse.
clang_macro_fallback: bool {
methods: {
/// Use Clang as a fallback for macros that fail to parse using CExpr.
///
/// This uses a workaround to evaluate each macro in a temporary file. Because this
/// results in slower compilation, this option is opt-in.
pub fn clang_macro_fallback(mut self) -> Self {
self.options.clang_macro_fallback = true;
self
}
},
as_args: "--clang-macro-fallback",
}
}

0 comments on commit 3990e2f

Please sign in to comment.