Skip to content

fix(parsers/c): guard macro-alias resolution against cyclic #defines#130

Open
gadievron wants to merge 1 commit into
pr/zig-php-generic-anonfrom
fix/c-macro-recursion-guard-on-127
Open

fix(parsers/c): guard macro-alias resolution against cyclic #defines#130
gadievron wants to merge 1 commit into
pr/zig-php-generic-anonfrom
fix/c-macro-recursion-guard-on-127

Conversation

@gadievron

Copy link
Copy Markdown
Collaborator

fix(parsers/c): guard macro-alias resolution against cyclic #defines

Base: pr/zig-php-generic-anon · Depends-on: #127 · Type: bug fix · Finding: F4 (HIGH)

Stacked on #127 because that branch's C-parser rework already restructures
_resolve_call (adds receiver_type/is_member, _resolve_same_file); the
macro-alias recursion it carries is still unguarded. Targeting master instead
would hard-conflict with #127 in this same function. This layers only the cycle
guard on top of #127's signature.

What

parsers/c/call_graph_builder.py_resolve_call threads an _alias_chain
visited-set through its macro-alias recursion, so a cyclic alias resolves to
None instead of recursing forever.

Why

macro_aliases is built from function-like #defines. A cyclic pair —
#define A(x) B(x) / #define B(x) A(x){"A":"B","B":"A"} — made
_resolve_call("A") recurse A→B→A→… with no guard → RecursionError.
build_call_graph iterates every function with no try/except, so one poisoned
define pair aborts the entire repository's C call-graph build (seen on vendored
LLVM in ziglang/zig). A self-alias was already safe via the resolved_name != call_name short-circuit; only 2+ node cycles triggered it.

Tests

tests/test_c_macro_alias_cycle.py (new): 2-node cycle, 3-node cycle, self-alias.

Upstream coordination

The cyclic-macro recursion is live on master, on the C staging stack (#114/#121),
and on #127 — all unguarded. This guard is non-overlapping with those refactors;
sequence after #127 (or the C stack), re-anchoring onto the post-rework
_resolve_call.

Author notes

  • Fix line: the if resolved_name not in _alias_chain: guard around the
    recursive _resolve_call(..., _alias_chain=_alias_chain).
  • Input it now handles: cyclic #define macro pairs that crashed the whole-repo
    parse.
  • Likely pushback: "set vs depth cap?" — a visited-set terminates on the actual
    cycle with no arbitrary limit, mirroring the file's other visited guards.

_resolve_call recursed on a macro alias target with no visited-set, so a cyclic
function-like #define pair ({"A":"B","B":"A"}) looped A->B->A->... until
RecursionError aborted the entire repository's C call-graph build. Thread an
_alias_chain set through the recursion so a cyclic alias resolves to None.

Stacked on pr/zig-php-generic-anon, whose C-parser rework carries the same
unguarded macro recursion; this layers the cycle guard on top of that signature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gadievron

Copy link
Copy Markdown
Collaborator Author

Finding F4 (HIGH). ⚠️ Stacked on #127 (base pr/zig-php-generic-anon). The cyclic-macro RecursionError it fixes is live on master, the C staging stack (#114/#121), AND #127's C rework — all carry the unguarded _resolve_call macro-alias recursion. This adds only the _alias_chain cycle guard. Merge after #127, or rebase onto master.

@gadievron gadievron marked this pull request as ready for review June 18, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant