fix(parsers/c): header static-inline call edges + same-(file,name) func_id collisions#121
fix(parsers/c): header static-inline call edges + same-(file,name) func_id collisions#121gadievron wants to merge 2 commits into
Conversation
`_is_visible_from` rejected any cross-file `static` callee, which is correct
for a `.c` translation unit but wrong for a `static inline` function in an
`#include`d header (the header-inline idiom — every including TU gets its own
copy and can call it). The include-resolution loop found the header callee via
`include_map`, then dropped the edge as internal-linkage.
Fix is scoped to the include-resolution site only: a candidate whose file ends
in a header extension (.h/.hpp/.hxx/.hh) is visible regardless of `static`. The
repo-wide unique-name fallback and the prototype fallback stay strict — a
`static` helper in another `.c` still does not resolve cross-TU.
Tests: tests/parsers/c/test_c_static_inline_header_resolution.py (static-inline
header resolves; static-in-other-.c still rejected; extern header still
resolves). Precision invariant preserved:
$ pytest tests/parsers/c/test_c_static_inline_header_resolution.py \
tests/parsers/c/test_c_call_resolution_precision.py -q
12 passed in 0.66s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified end-to-end on tree-sitter (post-fix, parse-only — no API)Re-parsed Commit 2 (collision): 67 raw (13 flips total — correcting the PR description's "14": 14 names collide, 13 had the real body displaced by a stub.) Commit 1 (header static-inline): parsing |
Blast-radius review (independent re-derivation + judge)Verified that both changes are surgical and fully contained. Re-derived independently, then reconciled. Confirmed:
Known edge cases (documented, not blocking)
Tests: 609 passed / 22 skipped. |
…c_id
`func_id = "<file>:<name>"` carries no signature or preprocessor condition, and
the store was an unguarded `self.functions[func_id] = ...` (keep-last). Two
functions sharing (file, name) silently overwrote each other, corrupting the
inventory AND the call graph: on tree-sitter, 14 real wasm_store.c
implementations were replaced by their `#else` no-op stubs; C++ overloads
collapsed onto one node so a call into one overload reached the other's callee.
New `_store_function` disambiguates collision-only (unique names keep the exact
`path:name` id — the hundreds of hardcoded id literals in the suite are
untouched):
- SAME signature (#ifdef/#else, ODR-dup): keep ONE node, prefer the larger
body — the stub is shorter regardless of which arm tree-sitter emits first.
- DIFFERENT signature (overload): both are real; fold a COLON-FREE signature
discriminator into the func_id key only. `name` stays bare so the call-graph
builder (which resolves by `name`) still finds both. Colon-free because
downstream id splits rsplit on ':' (std::string -> std..string).
Both store sites (process-function + lambda) route through the helper.
Tests: tests/parsers/c/test_c_collision_discriminator.py (overloads survive,
callees not conflated, #ifdef keeps larger body, ids colon-free, unique-name id
byte-identical):
$ pytest tests/parsers/c/test_c_collision_discriminator.py -q
5 passed in 0.47s
$ pytest tests/parsers/c/ -q
71 passed in 5.71s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cf959dd to
099e83f
Compare
Two pre-existing C/C++ parser-correctness fixes, stacked on
staging/parser-fix-stackbecause both build on the C-parser commits already in this stack (deterministic include-resolution, u1/u2 extractor fixes, member-dispatch) — none of which are onmasteryet. Surfaced while auditing a tree-sitter parse; details in the dossier carried alongside this work.Commit 1 —
static inlinefunctions in included headers resolve as call edges_is_visible_fromrejected every cross-filestaticcallee. That is right for a.ctranslation unit but wrong for astatic inlinehelper in an#included header (each including TU gets its own copy and can call it). The include-resolution loop already found the header callee viainclude_map, then dropped the edge as internal-linkage.The fix is scoped to the include-resolution site only: a candidate whose file ends in a header extension (
.h/.hpp/.hxx/.hh) is visible regardless ofstatic. The repo-wide unique-name fallback and the prototype fallback stay strict — astatichelper in another.cstill does not resolve cross-TU (the#84precision invarianttest_unique_name_fallback_skips_static_in_other_filestays green).Commit 2 — same-
(file,name)functions no longer collapse to onefunc_idfunc_id = "<file>:<name>"carries no signature or preprocessor condition, and the store was an unguarded keep-last assignment. Two functions sharing(file, name)silently overwrote each other, corrupting the inventory and the call graph: on tree-sitter, 14 realwasm_store.cimplementations were replaced by their#elseno-op stubs, and C++ overloads collapsed onto one node so a call into one overload reached the other's callee.New
_store_functiondisambiguates collision-only — uniquely-named functions keep the exactpath:nameid, so the existing hardcoded id literals are untouched:#ifdef/#else, ODR-dup): keep one node, prefer the larger body — the stub is shorter regardless of which arm tree-sitter emits first.namestays bare so the call-graph builder (which resolves byname) still finds both overloads. Colon-free because downstream id splits rsplit on:(std::string→std..string).Both store sites (process-function + lambda) route through the helper. This complements the existing template-specialization disambiguation (bug
[39]), which folds its discriminator into thename(correct there — a template call is writteng<int>; an overload call is written bare).Tests
New:
tests/parsers/c/test_c_static_inline_header_resolution.py,tests/parsers/c/test_c_collision_discriminator.py.Full repo suite: 609 passed, 22 skipped.
Scope / follow-ups
The same
(file,name)-collapse exists in the JS/TS, Python, PHP, and Ruby extractors (each with different collision semantics and parameter shapes). Those are intentionally not in this PR — each needs its own fix and tests. This PR addresses the C/C++ parser, where the measured impact (14 lostwasm_store.cfunctions) was demonstrated.🤖 Generated with Claude Code