Skip to content

bug: native engine misses call edges when receiver's static type is an interface with multiple implementers (NativeDbProxy, TreeSitterNode, Repository) #2139

Description

@carlos-alm

Found during dogfooding v3.16.0

Severity: Medium
Command: codegraph build --engine native vs --engine wasm

Reproduction

codegraph build . --engine native --no-incremental
sqlite3 .codegraph/graph.db "SELECT n1.file,n1.name,n1.line,e.kind,n2.file,n2.name,n2.line \
  FROM edges e JOIN nodes n1 ON e.source_id=n1.id JOIN nodes n2 ON e.target_id=n2.id \
  ORDER BY 1,2,3,4,5,6,7;" | sort > /tmp/edges_native.txt

codegraph build . --engine wasm --no-incremental
sqlite3 .codegraph/graph.db "... same query ..." | sort > /tmp/edges_wasm.txt

comm -23 /tmp/edges_native.txt /tmp/edges_wasm.txt   # empty — native has no edges wasm lacks
comm -13 /tmp/edges_native.txt /tmp/edges_wasm.txt   # 84 edges wasm has that native lacks

Built against codegraph's own source (993 files): native = 43195 edges, wasm = 43279 edges. Every one of the 84-edge difference is wasm-only (native is a strict subset — no edges are unique to native), so this is a native under-resolution gap, not a wasm over-resolution one, for the dominant pattern below.

Dominant pattern (65 of 84 edges): interface-typed receiver with multiple implementers

src/domain/graph/builder/incremental.ts|backfillIncrementalEdgeTechniques|1187|calls|src/domain/graph/builder/native-db-proxy.ts|NativeDbProxy.prepare|28
src/domain/graph/builder/helpers.ts|runChaPostPass|621|calls|src/domain/graph/builder/native-db-proxy.ts|NativeDbProxy.transaction|82
src/ast-analysis/visitors/cfg-shared.ts|getBodyStatements|136|calls|src/types.ts|TreeSitterNode.namedChild|866
src/shared/hierarchy.ts|resolveViaRepo|29|calls|src/db/repository/in-memory-repository.ts|InMemoryRepository.getClassHierarchy|507
src/shared/hierarchy.ts|resolveViaRepo|29|calls|src/db/repository/native-repository.ts|NativeRepository.getClassHierarchy|441
src/shared/hierarchy.ts|resolveViaRepo|29|calls|src/db/repository/sqlite-repository.ts|SqliteRepository.getClassHierarchy|223

wasm consistently resolves db.prepare(...) / db.transaction(...) calls (receiver statically typed NativeDbProxy, a wrapper mimicking the better-sqlite3 Database shape) across many caller files in src/domain/graph/builder/*.ts. wasm also resolves resolveViaRepo's call to repo.getClassHierarchy() to all three concrete Repository implementers (InMemoryRepository, NativeRepository, SqliteRepository) — exactly the kind of interface-typed, multi-implementer receiver dispatch this project's CHA/RTA logic is supposed to handle. native produces none of these edges. This is a real gap in native's receiver-type resolution for interface/wrapper-typed receivers with multiple concrete implementations.

Secondary observation (6 edges): possible cross-fixture false positives in wasm (needs its own look)

tests/benchmarks/resolution/fixtures/jelly-micro/classes/classes.js|D.constructor|151|calls|tests/benchmarks/resolution/fixtures/jelly-micro/super/super.js|A.constructor|2
tests/benchmarks/resolution/fixtures/jelly-micro/classes/classes.js|D.constructor|151|calls|tests/benchmarks/resolution/fixtures/jelly-micro/super4/super4.js|A.constructor|2
tests/benchmarks/resolution/fixtures/jelly-micro/classes/classes.js|D.constructor|151|calls|tests/benchmarks/resolution/fixtures/jelly-micro/super5/super5.js|A.constructor|2

These are wasm-only edges from the jelly-micro/classes fixture's D.constructor to A.constructor in three separate, unrelated fixture directories (super/, super4/, super5/) that each define their own local, unrelated A class for a different resolution scenario. If these are genuinely unrelated fixtures (not meant to resolve across each other), this looks like wasm over-resolving/bleeding across fixture-directory boundaries rather than native under-resolving — the opposite direction from the dominant pattern above. Flagging for someone closer to the hierarchy-scoping resolver logic to confirm intent before fixing either direction.

Tertiary observation (7 edges): odd zero-line "file calls itself" edges

src/db/connection.ts|wrapInjectedRepo|521|calls|src/db/connection.ts|src/db/connection.ts|0
tests/unit/in-memory-repository.test.ts|tests/unit/in-memory-repository.test.ts|0|calls|tests/integration/triage.test.ts|BrokenRepo.findNodesForTriage|276

A handful of wasm-only edges where the source/target name/line is the file path itself at line 0 (module-level edge?), including one pointing at test-double classes (BrokenRepo, InvalidOptsRepo) defined locally in a different test file. Noted for completeness; lower priority than the two patterns above.

Suggested fix

Focus on the dominant pattern first: audit native's receiver-type resolution path for interface-typed / wrapper-typed receivers where the interface has multiple concrete implementations in scope (the Repository family and NativeDbProxy/TreeSitterNode-style wrapper types) and bring it to parity with wasm's CHA/RTA-based multi-implementer resolution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdogfoodFound during dogfooding

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions