Skip to content

fix(extractors): resolve inline object-literal dispatch tables on WASM#2035

Open
carlos-alm wants to merge 1 commit into
fix/issue-1895-roles-role-dead-object-literal-property-valuefrom
fix/issue-1897-engine-parity-wasm-doesnt-resolve-inline
Open

fix(extractors): resolve inline object-literal dispatch tables on WASM#2035
carlos-alm wants to merge 1 commit into
fix/issue-1895-roles-role-dead-object-literal-property-valuefrom
fix/issue-1897-engine-parity-wasm-doesnt-resolve-inline

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • WASM's extractSubscriptCallInfo had no equivalent of native's RES-2 extract_dispatch_table_call, so ({a:fnA,b:fnB})[key]() classified as a generic flagged computed-key sink on WASM while native resolved it to fnA/fnB via the points-to wildcard solver — a genuine engine-parity gap.
  • Adds extractDispatchTableCall in src/extractors/javascript.ts, mirroring extract_dispatch_table_call in crates/codegraph-core/src/extractors/javascript.rs: when a subscript call's object is an object literal (optionally unwrapped from a parenthesized expression) and the index is a bare identifier, seeds arrayElemBindings under a synthetic <dt_line_col> name and emits a <dt_line_col>[*] call with dynamicKind: 'dispatch-table' — the existing points-to wildcard resolution path (already used for array-literal dispatch, e.g. const arr=[f1,f2]; arr[i]()) then resolves it to each concrete target identically on both engines.
  • Threaded through both WASM extraction paths per this repo's two-path extractor architecture: dispatchQueryMatch (used by the WASM worker) and handleCallExpr (direct/test callers) both now pass arrayElemBindings into extractCallInfoextractSubscriptCallInfo.
  • Adds 'dispatch-table' to the DynamicKind union in src/types.ts, intentionally excluded from FLAG_ONLY_DYNAMIC_KINDS (matching native's sink-edge exclusion) so a resolved table never produces a spurious sink edge.

Note on history

While investigating, I found this exact fix (types.ts union member, extractSubscriptCallInfo dispatch-table branch, the pts-javascript/dispatch-table.js fixture, and points-to.test.ts unit tests) was already implemented and merged once before (commit 9a6b882f, closing #1665) but silently disappeared from the tree afterward — presumably lost in a merge/rebase — while the native-side mirror (added later in PR #1690, closing #1684) survived. This PR re-implements the WASM side from scratch against the current architecture (the extraction pipeline has changed substantially since then — single-pass collector walk, runCollectorWalk, etc.) and restores the dropped fixture/tests, plus adds new query-vs-walk and cross-engine parity regression coverage that didn't exist before.

Test plan

  • npx vitest run tests/parsers/javascript.test.ts tests/unit/points-to.test.ts tests/engines/query-walk-parity.test.ts — new unit tests pass (297/297)
  • npx vitest run tests/engines/parity.test.ts — new cross-engine parity case passes against a locally rebuilt native binary (37 passed, 8 skipped)
  • npx vitest run tests/benchmarks/resolution/resolution-benchmark.test.ts -t "pts-javascript" — 5/5 pass (restored dispatch-table.js + expected-edges entries)
  • npm test — 3978 passed, 30 skipped, 2 todo, 1 pre-existing known-flaky failure unrelated to this change (checkNoNewCycles node-ordering, tracked in Flaky cycle-node ordering in checkNoNewCycles full-suite run (tests/integration/check.test.ts) #1990)
  • cargo test --release -p codegraph-core — 591/591 pass (native side untouched; rebuilt binary confirms parity)
  • npx tsc --noEmit / npm run lint — clean (no new issues)

Closes #1897

WASM's extractSubscriptCallInfo had no equivalent of native's RES-2
extract_dispatch_table_call, so ({a:fnA,b:fnB})[key]() classified as a
generic flagged computed-key sink while native resolved it to fnA/fnB
via the points-to wildcard solver — a genuine engine-parity gap.

Add extractDispatchTableCall, mirroring the native extractor: when a
subscript call's object is an object literal (optionally unwrapped
from a parenthesized expression) and the index is a bare identifier,
seed arrayElemBindings under a synthetic <dt_line_col> name and emit a
<dt_line_col>[*] call with dynamicKind 'dispatch-table' so the existing
pts wildcard resolution path (already used for array-literal dispatch)
picks it up identically on both engines. Threaded through both
extraction paths (dispatchQueryMatch for the WASM worker, handleCallExpr
for direct/test callers) per the two-path extractor architecture.

Adds 'dispatch-table' to the DynamicKind union and restores the
pts-javascript dispatch-table fixture, points-to unit tests, and a
query-vs-walk + cross-engine parity case that were dropped from a prior
merge without being ported to WASM.

Impact: 9 functions changed, 20 affected
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Closes a WASM/native engine-parity gap (#1897): WASM's subscript-call extractor previously had no equivalent of the native extract_dispatch_table_call (RES-2), so ({a:fnA,b:fnB})[key]() was classified as a generic flagged computed-key sink on WASM while native resolved it to concrete targets via the points-to wildcard solver.

  • Adds extractDispatchTableCall in src/extractors/javascript.ts, seeding ArrayElemBinding entries under a synthetic <dt_line_col> name and emitting a <dt_line_col>[*] call with dynamicKind: 'dispatch-table'; the existing wildcard pts resolution path then resolves to each concrete target identically on both engines.
  • Threads the new arrayElemBindings parameter through both WASM extraction paths (dispatchQueryMatch for the query worker, handleCallExpr for the direct/test walk), and adds 'dispatch-table' to the DynamicKind union in src/types.ts, intentionally excluded from FLAG_ONLY_DYNAMIC_KINDS so unresolved tables never produce a spurious sink edge.
  • Restores the previously-dropped fixture and tests (lost in a prior rebase per the PR note) and adds new query-vs-walk parity and cross-engine regression coverage.

Confidence Score: 4/5

The core extraction logic and pts integration are correct; the only gap is the benchmark report under-counting points-to technique coverage for the new dispatch-table edges.

The extractDispatchTableCall implementation is faithful to the Rust mirror: parenthesized-expression unwrap, identifier-value filtering, BUILTIN_GLOBALS guard, synthetic dt_line_col naming, and wildcard call emission all look correct. Both WASM extraction paths now consistently thread arrayElemBindings, and dispatch-table is correctly excluded from FLAG_ONLY_DYNAMIC_KINDS. The one omission is pts-dispatch-table missing from TECHNIQUE_MAP in the resolution benchmark — those two new edges will appear under other instead of points-to in the per-technique report, which may confuse future contributors reading benchmark output.

tests/benchmarks/resolution/resolution-benchmark.test.ts — TECHNIQUE_MAP needs a pts-dispatch-table: points-to entry

Important Files Changed

Filename Overview
src/extractors/javascript.ts Adds extractDispatchTableCall (mirrors Rust RES-2), threads arrayElemBindings through both WASM extraction paths (dispatchQueryMatch/handleCallExpr); all non-subscript extractCallInfo call sites are correctly left without arrayElemBindings since they can never receive a subscript_expression fn node
src/types.ts Adds 'dispatch-table' to DynamicKind union and correctly excludes it from FLAG_ONLY_DYNAMIC_KINDS, matching native's sink-edge exclusion behaviour
tests/benchmarks/resolution/resolution-benchmark.test.ts New pts-dispatch-table mode in expected-edges.json will fall into the 'other' bucket of TECHNIQUE_MAP instead of 'points-to', causing the per-technique breakdown report to under-count points-to coverage (thresholds are unaffected)
tests/benchmarks/resolution/fixtures/pts-javascript/dispatch-table.js New benchmark fixture for inline object-literal dispatch table; correct and minimal
tests/benchmarks/resolution/fixtures/pts-javascript/expected-edges.json Adds two expected call edges (runDispatch to dtFn1/dtFn2) with mode pts-dispatch-table; edge shapes are correct
tests/engines/parity.test.ts Adds cross-engine regression guard for #1897; correctly checks that native and WASM emit the same synthetic dt_line_col[*] call name
tests/engines/query-walk-parity.test.ts Adds regression guard verifying that the query path and walk path produce identical dt_line_col[*] call names for the dispatch-table pattern
tests/parsers/javascript.test.ts Five new unit tests covering parenthesized/non-parenthesized object literals, shorthand-property form, non-literal fallback, and string-literal key fallback; all cases are correct
tests/unit/points-to.test.ts Three new pts unit tests for synthetic dispatch-table array-elem bindings; correctly validates wildcard resolution and name isolation between tables

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant AST as Tree-sitter AST
    participant ESQ as extractSymbolsQuery / handleCallExpr
    participant EDT as extractDispatchTableCall (new)
    participant AEB as arrayElemBindings[]
    participant PTS as buildPointsToMap
    participant Graph as Edge Builder

    AST->>ESQ: subscript_expression node
    ESQ->>EDT: "obj=object_literal, index=key, callNode, arrayElemBindings"
    EDT->>AEB: "push {arrayName:dt_5_2, index:0, elemName:fnA}"
    EDT->>AEB: "push {arrayName:dt_5_2, index:1, elemName:fnB}"
    EDT-->>ESQ: "Call { name:dt_5_2[*], dynamicKind:dispatch-table }"
    ESQ->>PTS: calls + arrayElemBindings
    PTS->>PTS: buildArrayElemConstraints
    PTS-->>Graph: "resolve(dt_5_2[*]) = {fnA, fnB}"
    Graph->>Graph: emit calls edges runDispatch to fnA and fnB
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant AST as Tree-sitter AST
    participant ESQ as extractSymbolsQuery / handleCallExpr
    participant EDT as extractDispatchTableCall (new)
    participant AEB as arrayElemBindings[]
    participant PTS as buildPointsToMap
    participant Graph as Edge Builder

    AST->>ESQ: subscript_expression node
    ESQ->>EDT: "obj=object_literal, index=key, callNode, arrayElemBindings"
    EDT->>AEB: "push {arrayName:dt_5_2, index:0, elemName:fnA}"
    EDT->>AEB: "push {arrayName:dt_5_2, index:1, elemName:fnB}"
    EDT-->>ESQ: "Call { name:dt_5_2[*], dynamicKind:dispatch-table }"
    ESQ->>PTS: calls + arrayElemBindings
    PTS->>PTS: buildArrayElemConstraints
    PTS-->>Graph: "resolve(dt_5_2[*]) = {fnA, fnB}"
    Graph->>Graph: emit calls edges runDispatch to fnA and fnB
Loading

Reviews (1): Last reviewed commit: "fix(extractors): resolve inline object-l..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

9 functions changed18 callers affected across 2 files

  • dispatchQueryMatch in src/extractors/javascript.ts:326 (2 transitive callers)
  • extractSymbolsQuery in src/extractors/javascript.ts:396 (1 transitive callers)
  • handleCallExpr in src/extractors/javascript.ts:1628 (3 transitive callers)
  • extractCallInfo in src/extractors/javascript.ts:3510 (20 transitive callers)
  • extractDispatchTableCall in src/extractors/javascript.ts:3717 (6 transitive callers)
  • extractSubscriptCallInfo in src/extractors/javascript.ts:3762 (8 transitive callers)
  • dtFn1 in tests/benchmarks/resolution/fixtures/pts-javascript/dispatch-table.js:2 (1 transitive callers)
  • dtFn2 in tests/benchmarks/resolution/fixtures/pts-javascript/dispatch-table.js:3 (1 transitive callers)
  • runDispatch in tests/benchmarks/resolution/fixtures/pts-javascript/dispatch-table.js:5 (0 transitive callers)

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