Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions bindgen-tests/tests/headers/clang-macro-fallback-pointer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// bindgen-flags: --clang-macro-fallback

#define BEFORE_DECL ((struct later *)3UL)
#define CONST_PTR ((const struct later *)4UL)

struct later;

typedef struct later *later_ptr;
#define TYPEDEF_PTR ((later_ptr)5UL)
#define MAP_FAILED ((void *)-1)
#define MAP_FAILED_ALIAS MAP_FAILED
#define MAP_FAILED_EQUALS_ITSELF (MAP_FAILED == MAP_FAILED)
#define REDEFINED_FROM_INT 1
#undef REDEFINED_FROM_INT
#define REDEFINED_FROM_INT ((void *)2)
#define REDEFINED_ALIAS REDEFINED_FROM_INT
1 change: 1 addition & 0 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ impl CodeGenerator for Var {
}
VarType::Float(f) => helpers::ast_ty::float_expr(f).ok(),
VarType::Char(c) => Some(c.to_token_stream()),
VarType::Pointer(val) => Some(quote! { #val as usize as #ty }),
};

if let Some(mut val) = const_expr {
Expand Down
44 changes: 37 additions & 7 deletions bindgen/ir/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use super::module::{Module, ModuleKind};
use super::template::{TemplateInstantiation, TemplateParameters};
use super::traversal::{self, Edge, ItemTraversal};
use super::ty::{FloatKind, Type, TypeKind};
use super::var::PendingPointerMacro;
use crate::clang::{self, ABIKind, Cursor};
use crate::codegen::CodegenError;
use crate::ir::item::ItemCanonicalName;
Expand Down Expand Up @@ -350,13 +351,18 @@ pub(crate) struct BindgenContext {
/// potentially break that assumption.
currently_parsed_types: Vec<PartialType>,

/// A map with all the already parsed macro names. This is done to avoid
/// hard errors while parsing duplicated macros, as well to allow macro
/// expression parsing.
/// Parsed macro names whose current value cannot be provided to `cexpr`.
non_cexpr_macros: HashSet<Vec<u8>>,

/// Values from parsed macros that `cexpr` can use while parsing derived
/// macro expressions.
///
/// This needs to be an `std::HashMap` because the `cexpr` API requires it.
parsed_macros: StdHashMap<Vec<u8>, cexpr::expr::EvalResult>,

/// Pointer-valued macros whose type is materialized after macro parsing.
pending_pointer_macros: Vec<PendingPointerMacro>,

/// A map with all include locations.
///
/// This is needed so that items are created in the order they are defined in.
Expand Down Expand Up @@ -587,7 +593,9 @@ If you encounter an error missing from this list, please file an issue or a PR!"
current_module: root_module_id,
semantic_parents: Default::default(),
currently_parsed_types: vec![],
non_cexpr_macros: Default::default(),
parsed_macros: Default::default(),
pending_pointer_macros: vec![],
replacements: Default::default(),
collected_typerefs: false,
in_codegen: false,
Expand Down Expand Up @@ -2130,7 +2138,8 @@ If you encounter an error missing from this list, please file an issue or a PR!"

/// 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)
self.parsed_macros.contains_key(macro_name) ||
self.non_cexpr_macros.contains(macro_name)
}

/// Get the currently parsed macros.
Expand All @@ -2141,13 +2150,34 @@ If you encounter an error missing from this list, please file an issue or a PR!"
&self.parsed_macros
}

/// Mark the macro named `macro_name` as parsed.
/// Mark a macro as parsed and update any value usable by `cexpr`.
pub(crate) fn note_parsed_macro(
&mut self,
id: Vec<u8>,
value: cexpr::expr::EvalResult,
value: Option<cexpr::expr::EvalResult>,
) {
self.parsed_macros.insert(id, value);
if let Some(value) = value {
self.non_cexpr_macros.remove(&id);
self.parsed_macros.insert(id, value);
} else {
self.parsed_macros.remove(&id);
self.non_cexpr_macros.insert(id);
}
}

/// Defer materialization of a pointer-valued macro until parsing finishes.

@emilio emilio Jul 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate why we need this deferral thing?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deferral is about libclang cursor lifetimes, not just macro ordering.

The fallback translation unit is reparsed for each macro cexpr cannot evaluate. Materializing a pointer immediately can create an UnresolvedTypeRef holding a clang::Type and cursor from that translation unit. Those references are resolved only after parsing completes, but evaluating the next fallback macro invalidates them.

The regression fixture makes this concrete: BEFORE_DECL refers to struct later before its declaration has been visited, then CONST_PTR reparses the fallback translation unit. Without deferral, the first unresolved pointee would retain stale libclang handles.

Queueing pointer macros, preserving their item IDs, and materializing them together in one final reparse keeps those handles valid through type resolution. I do not see a simpler safe approach that preserves typed forward-declared pointees and the existing reusable fallback translation unit.

— tamirdex

pub(crate) fn note_pending_pointer_macro(
&mut self,
pointer_macro: PendingPointerMacro,
) {
self.pending_pointer_macros.push(pointer_macro);
}

/// Take all pointer-valued macros waiting for final materialization.
pub(crate) fn take_pending_pointer_macros(
&mut self,
) -> Vec<PendingPointerMacro> {
mem::take(&mut self.pending_pointer_macros)
}

/// Are we in the codegen phase?
Expand Down
180 changes: 166 additions & 14 deletions bindgen/ir/var.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Intermediate representation of variables.

use super::super::codegen::MacroTypeVariation;
use super::context::{BindgenContext, TypeId};
use super::annotations::Annotations;
use super::context::{BindgenContext, ItemId, TypeId};
use super::dot::DotAttributes;
use super::function::cursor_mangling;
use super::int::IntKind;
use super::item::Item;
use super::item_kind::ItemKind as IrItemKind;
use super::ty::{FloatKind, TypeKind};
use crate::callbacks::{ItemInfo, ItemKind, MacroParsingBehavior};
use crate::clang;
Expand All @@ -28,6 +30,27 @@ pub(crate) enum VarType {
Char(u8),
/// A string, not necessarily well-formed utf-8.
String(Vec<u8>),
/// A pointer represented as an integer constant.
Pointer(u64),
}

/// The value obtained when parsing a macro.
enum MacroValue {
/// A value parsed using `cexpr` or evaluated by Clang as an integer.
Expr(cexpr::expr::EvalResult),
/// A data pointer value recognized by Clang.
Pointer,
}

/// A pointer-valued macro waiting to have its type materialized.
#[derive(Debug)]
pub(crate) struct PendingPointerMacro {
/// The item ID reserved at the macro's source position.
id: ItemId,
/// The macro name.
name: String,
/// The original macro cursor in the primary translation unit.
cursor: clang::Cursor,

@emilio emilio Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess clang::Cursors are not invalidated when reparsing, but the types are? Is that documented anywhere?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the distinction is which translation unit is reparsed, not cursors versus types.

Clang explicitly documents that clang_reparseTranslationUnit invalidates all cursors and source locations referring into the translation unit being reparsed: https://github.com/llvm/llvm-project/blob/8e76c3f0d29a713c41f1b3d0f19d965a504481cf/clang/include/clang-c/Index.h#L1042-L1054.

The saved PendingPointerMacro cursor comes from bindgen’s original, primary translation unit, which is never reparsed:

// this point.
let name = String::from_utf8(id).unwrap();
let value =
match value {
MacroValue::Expr(value) => value,
MacroValue::Pointer => {
let id = ctx.next_item_id();
ctx.note_pending_pointer_macro(
PendingPointerMacro { id, name, cursor },
);
return Err(ParseError::Continue);
. It is the separate fallback translation unit that is reparsed for each expression:
fn parse_macro_clang_fallback(
ctx: &mut BindgenContext,
cursor: &clang::Cursor,
) -> Option<(Vec<u8>, MacroValue)> {
use clang_sys::{
CXType_FunctionNoProto, CXType_FunctionProto, CXType_Pointer,
};
if !ctx.options().clang_macro_fallback {
return None;
}
let ftu = ctx.try_ensure_fallback_translation_unit()?;
let name = cursor.spelling();
let contents = format!("int main() {{ {name}; }}");
ftu.reparse(&contents).ok()?;
// Children of root node of AST
let root_children = ftu.translation_unit().cursor().collect_children();
. Cursors and types obtained from that fallback translation unit must therefore not survive its next reparse. Deferring pointer materialization until the final fallback reparse keeps the primary cursor and the final pointee type valid through resolution:
pub(crate) fn finish_pending_pointer_macros(ctx: &mut BindgenContext) {
use clang_sys::{CXChildVisit_Break, CXChildVisit_Recurse, CXType_Pointer};
let pending = ctx.take_pending_pointer_macros();
if pending.is_empty() {
return;
}
let statements = pending
.iter()
.map(|pointer_macro| {
format!("{{ (unsigned long long)({}); }}", pointer_macro.name)
})
.collect::<Vec<_>>()
.join(" ");
let contents = format!("int main() {{ {statements} }}");
let expressions = {
let Some(ftu) = ctx.try_ensure_fallback_translation_unit() else {
return;
};
if ftu.reparse(&contents).is_err() {
return;
}
let root_children = ftu.translation_unit().cursor().collect_children();
let Some(main_func) = root_children.last() else {
.

So this does not depend on a guarantee that cursors from a reparsed translation unit survive; Clang explicitly says they do not.

— tamirdex

}

/// A `Var` is our intermediate representation of a variable.
Expand Down Expand Up @@ -207,10 +230,13 @@ impl ClangSubItemParser for Var {

let previously_defined = ctx.parsed_macro(&id);

// NB: It's important to "note" the macro even if the result is
// not an integer, otherwise we might loose other kind of
// derived macros.
ctx.note_parsed_macro(id.clone(), value.clone());
// Keep pointer macros unknown to `cexpr`: derived expressions
// must still be eligible for Clang fallback evaluation.
let cexpr_value = match &value {
MacroValue::Expr(value) => Some(value.clone()),
MacroValue::Pointer => None,
};
ctx.note_parsed_macro(id.clone(), cexpr_value);

if previously_defined {
let name = String::from_utf8(id).unwrap();
Expand All @@ -223,6 +249,18 @@ impl ClangSubItemParser for Var {
// enforce utf8 there, so we should have already panicked at
// this point.
let name = String::from_utf8(id).unwrap();
let value =
match value {
MacroValue::Expr(value) => value,
MacroValue::Pointer => {
let id = ctx.next_item_id();
ctx.note_pending_pointer_macro(
PendingPointerMacro { id, name, cursor },
);
return Err(ParseError::Continue);
}
};

let (type_kind, val) = match value {
EvalResult::Invalid => return Err(ParseError::Continue),
EvalResult::Float(f) => {
Expand Down Expand Up @@ -390,13 +428,18 @@ impl ClangSubItemParser for Var {
fn parse_macro_clang_fallback(
ctx: &mut BindgenContext,
cursor: &clang::Cursor,
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
) -> Option<(Vec<u8>, MacroValue)> {
use clang_sys::{
CXType_FunctionNoProto, CXType_FunctionProto, CXType_Pointer,
};

if !ctx.options().clang_macro_fallback {
return None;
}

let ftu = ctx.try_ensure_fallback_translation_unit()?;
let contents = format!("int main() {{ {}; }}", cursor.spelling());
let name = cursor.spelling();
let contents = format!("int main() {{ {name}; }}");
ftu.reparse(&contents).ok()?;
// Children of root node of AST
let root_children = ftu.translation_unit().cursor().collect_children();
Expand All @@ -413,18 +456,33 @@ fn parse_macro_clang_fallback(
// First child in all_exprs is the expression utilizing the given macro to be evaluated
// Should be ParenExpr
let paren = paren_exprs.first()?;
let canonical_ty = paren.cur_type().canonical_type();

if canonical_ty.kind() != CXType_Pointer {
return Some((
name.into_bytes(),
MacroValue::Expr(cexpr::expr::EvalResult::Int(Wrapping(
paren.evaluate()?.as_int()?,
))),
));
}

let pointee = canonical_ty.pointee_type()?;
if matches!(
pointee.kind(),
CXType_FunctionNoProto | CXType_FunctionProto
) {
return None;
}

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

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

let mut cexpr_tokens = cursor.cexpr_tokens();
Expand All @@ -436,8 +494,102 @@ fn parse_macro(
let parser = expr::IdentifierParser::new(ctx.parsed_macros());

match parser.macro_definition(&cexpr_tokens) {
Ok((_, (id, val))) => Some((id.into(), val)),
_ => parse_macro_clang_fallback(ctx, cursor),
Ok((_, (id, value))) => Some((id.into(), MacroValue::Expr(value))),
Err(_) => parse_macro_clang_fallback(ctx, cursor),
}
}

/// Materialize all pointer macro types from one final fallback translation
/// unit, which remains valid throughout deferred type resolution.
pub(crate) fn finish_pending_pointer_macros(ctx: &mut BindgenContext) {
use clang_sys::{CXChildVisit_Break, CXChildVisit_Recurse, CXType_Pointer};

let pending = ctx.take_pending_pointer_macros();
if pending.is_empty() {
return;
}

let statements = pending
.iter()
.map(|pointer_macro| {
format!("{{ (unsigned long long)({}); }}", pointer_macro.name)
})
.collect::<Vec<_>>()
.join(" ");
let contents = format!("int main() {{ {statements} }}");
let expressions = {
let Some(ftu) = ctx.try_ensure_fallback_translation_unit() else {
return;
};
if ftu.reparse(&contents).is_err() {
return;
}
let root_children = ftu.translation_unit().cursor().collect_children();
let Some(main_func) = root_children.last() else {
return;
};
let all_stmts = main_func.collect_children();
let Some(macro_stmt) = all_stmts.first() else {
return;
};
macro_stmt
.collect_children()
.into_iter()
.map(|statement| {
let value_expression = *statement.collect_children().first()?;
let value = value_expression.evaluate()?.as_int()? as u64;
let mut pointer_expression = None;
value_expression.visit(|child| {
if child.cur_type().canonical_type().kind() ==
CXType_Pointer
{
pointer_expression = Some(child);
CXChildVisit_Break
} else {
CXChildVisit_Recurse
}
});
Some((pointer_expression?, value))
})
.collect::<Vec<_>>()
};
if expressions.len() != pending.len() {
return;
}

for (pointer_macro, expression) in pending.into_iter().zip(expressions) {
let Some((expression, value)) = expression else {
continue;
};
let Ok(ty) = Item::from_ty(
&expression.cur_type().canonical_type(),
expression,
None,
ctx,
) else {
continue;
};
let cursor = pointer_macro.cursor;
let var = Var::new(
pointer_macro.name,
None,
None,
ty,
Some(VarType::Pointer(value)),
true,
);
ctx.add_item(
Item::new(
pointer_macro.id,
cursor.raw_comment(),
Annotations::new(&cursor),
ctx.root_module().into(),
IrItemKind::Var(var),
Some(cursor.location()),
),
Some(cursor),
Some(cursor),
);
}
}

Expand Down
1 change: 1 addition & 0 deletions bindgen/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,7 @@ fn parse(context: &mut BindgenContext) -> Result<(), BindgenError> {
context.root_module(),
"How did this happen?"
);
ir::var::finish_pending_pointer_macros(context);
Ok(())
}

Expand Down
Loading