Skip to content

fix(extract): port cross-language isolation fixes from upstream PR 2503 (#24, #33) - #34

Merged
filipechagas merged 2 commits into
v8from
fix/cross-language-isolation-port
Aug 6, 2026
Merged

fix(extract): port cross-language isolation fixes from upstream PR 2503 (#24, #33)#34
filipechagas merged 2 commits into
v8from
fix/cross-language-isolation-port

Conversation

@filipechagas

Copy link
Copy Markdown
Collaborator

Fixes #24. Fixes #33.

What

Port of upstream PR Graphify-Labs/graphify#2503 commits 2–5 to the fork's diverged v8, adapted rather than cherry-picked (the fork already had ObjC index scoping from 2e68ec6 and the raw-call ownership filter from #28's fix; the PHP feature surrounds the resolver code here).

Test evidence

  • Red-first per commit against unfixed fork code; each commit's tree independently green.
  • Baseline cec9cdd: 4087/36 → after 49c25ff: 4097/36 → after 10cdee2: 4107 passed / 36 skipped / 0 failed (+10 each, zero pre-existing tests changed status).
  • Independently reproduced by the orchestrator: 4107/36.
  • Parity for Import edges die when a same-stem foreign sibling exists (Python, PowerShell, Zig, Rust, Pascal, Elixir, bash) #33: bit-identical serialized graph (md5-matched) on a collision-free 7-language corpus; under collision, 7 dangling import edges → 0, with bash's duplicate 8th edge deduping away.

Deliberately untouched (recorded on the issues)

Python's missing builtin-globals guard (out of #24's scope), the .h C++/ObjC ambiguity, and the Go import dangle that reproduces without any collision (not this defect family; unasserted pending its own investigation).

🤖 Generated with Claude Code

filipechagas and others added 2 commits August 6, 2026 10:11
…sources (#24)

The C++, Swift, TypeScript and Python member-call resolvers built their
receiver-type index from every type-like node in the corpus, so a receiver's
declared type name was matched against class definitions written in ANY
language. #8 (PHP/ObjC) and #10 (Java/C#) closed the same defect on the other
six; these were the copies left over.

The defect cuts both ways, so it is two fixes per resolver:

* a foreign class TYPES the receiver, minting a cross-language edge that should
  not exist — `Lead lead; lead.search()` in C++ bound to a Python `class Lead`
  at INFERRED;
* a foreign class merely SHARING the short name pushes the single-definition
  god-node guard to 2 and silently suppresses the correct same-language edge.

Python needed more than a copy-paste on both arms. Its class index is built
from the SOURCES of `method` edges rather than from a scan over `all_nodes`, so
the suffix test goes on the looked-up owner node; and its `module.func()` arm
matched any corpus file whose stem equalled the receiver, so `import lead`
beside a `lead.ts` bound the call to a TypeScript function at EXTRACTED.

C++ had no suffix tuple yet. The new `_CPP_RESOLVER_SUFFIXES` includes `.h`,
because a C++ class is routinely declared in a header — `.h` routes to
extract_cpp or extract_objc by content, so it sits in both the C++ and the ObjC
definition-index sets. That is the accepted compromise: the two are isolated
from every other language but not from each other, which no suffix can fix.
Raw-call ownership for C++/ObjC is unaffected and stays on the extractor-
stamped `lang`, for exactly that reason. The tuple also now feeds the
`cpp_member_calls` registration, so registration and scoping cannot drift.

All ten indexes now go through one `_is_owned_definition` predicate, the
definition-index twin of `_raw_call_is_owned`.

Tests: ten new cases in tests/test_mixed_corpus_member_calls.py, both defect
directions for C++, Swift, TypeScript and the Python class arm, plus the Python
module arm's leak direction and its positive control. Nine were red before this
change; the tenth is the module-arm positive control. The module arm's
suppression direction is NOT covered here — two same-stem files salt the FILE
node ids apart while the `imports` edge target stays the bare alias, so the arm
sees zero candidates rather than an ambiguous two. That is a separate defect one
layer down, tracked as #33.

The existing ObjC defect-1 case is widened to assert through
`_cross_language_targets`, so a leak landing as a `references` edge onto the
foreign TYPE (rather than a `calls` edge onto its method) is caught too. It was
already green — #8 scoped that index — and stands as a regression guard.

Suite: 4097 passed / 36 skipped, up from 4087 / 36 by exactly the ten new tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ibling (#33)

Python, Rust, Zig, Elixir, PowerShell, Pascal and Bash all name an import's
target by the imported file's bare stem id (`import lead` -> `lead`). That
resolves only while the id is unique: add ANY same-stem file — a `lead.md` will
do — and the two file nodes collide, so `_disambiguate_colliding_node_ids` salts
them apart into `lead_py_lead` and `lead_md_lead`. The edge's target salt is
keyed by the IMPORTER's source_file, which matches neither, so the edge was left
pointing at an id that no longer named anything: silently dropped, along with
everything downstream of it — the Python resolver's `module.func()` arm among
them, which is the loose end #24 recorded and deferred here.

`_hint_import_targets` stamps the `target_file` hint the disambiguator already
accepts for this shape (Graphify-Labs#1814), keying the target salt by that file instead.
Stamped centrally off one `_IMPORT_STEM_LANGUAGES` table rather than in each
extractor, because an extractor sees one file and cannot know which of the
corpus's same-stem candidates the id will end up naming.

Bash needed a second fix. Its `resolve_bash_source_edges` pass runs AFTER
disambiguation but re-derives file ids from the path formula, so under a
collision it emitted the edge twice — once correct, once dangling — and its
source-backed `calls` edges named nodes that did not exist. It now receives the
ids as they actually stand, via `_file_nids_by_path`; the formula remains the
fallback for paths the caller did not resolve and for direct callers that pass
nothing, so existing behavior is untouched.

Audited and NOT vulnerable, so untouched: TS/JS and C/C++/ObjC already stamp
their own equivalent protection; Julia, Fortran and Verilog target an
importer-scoped node; Dart mints its own stub nodes; Ruby/PHP emit no
file-targeting import edges.

Tests: ten new cases. tests/test_import_alias_disambiguation.py parametrizes
control-vs-collided corpora over the six non-Python languages (seven cases —
PowerShell has two import spellings), each asserting BOTH directions so an inert
fixture cannot pass by accident, plus one case pinning that the transient
`target_file` hint never reaches the graph. tests/test_mixed_corpus_member_calls
.py adds the Python pair: the `module.func()` call edge and the `imports` edge
one layer under it, asserted separately because the module arm is only one
consumer. Nine were red before this change; the hint-leak case passed vacuously
beforehand (nothing stamped the hint yet) and is a real guard only now.

Parity evidence for the no-collision case, on a 14-file corpus covering all
seven languages with every stem unique: the serialized node+edge set is
bit-identical across this change, md5 4c6281a76937fe051d641673f486c89a both
sides. Adding a same-stem `.md` beside each of the seven targets goes from 8
import edges / 7 dangling to 7 / 0 — the extra edge before being exactly the
Bash duplicate.

Suite: 4107 passed / 36 skipped, up from 4097 / 36 by exactly the ten new tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant