import { describe, expect, it } from 'vitest'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { SupportedLanguages } from 'gitnexus-shared'; import { providers } from '../../../src/core/ingestion/languages/index.js'; import { getRelationships, runPipelineFromRepo, type PipelineOptions } from './helpers.js'; import { loadParseCache, PARSE_CACHE_VERSION, saveParseCache, type ParseCache, } from '../../../src/storage/parse-cache.js'; import { getDurableParsedFileDir, pruneAndSaveDurableParsedFileStore, } from '../../../src/storage/parsedfile-store.js'; const CALLABLE_FLOW_PROVIDER_COVERAGE = { [SupportedLanguages.JavaScript]: 'matrix', [SupportedLanguages.TypeScript]: 'dedicated', [SupportedLanguages.Python]: 'matrix', [SupportedLanguages.Java]: 'matrix', [SupportedLanguages.C]: 'dedicated', [SupportedLanguages.CPlusPlus]: 'dedicated', [SupportedLanguages.CSharp]: 'matrix', [SupportedLanguages.Go]: 'matrix', [SupportedLanguages.Ruby]: 'matrix', [SupportedLanguages.Rust]: 'matrix', [SupportedLanguages.PHP]: 'matrix', [SupportedLanguages.Kotlin]: 'matrix', [SupportedLanguages.Swift]: 'matrix', [SupportedLanguages.Dart]: 'matrix', [SupportedLanguages.Vue]: 'matrix', [SupportedLanguages.Cobol]: 'matrix', [SupportedLanguages.Zig]: 'matrix', } as const satisfies Record; const PROVIDER_FLOW_CASES = [ { language: SupportedLanguages.JavaScript, extension: 'js', caller: 'invoke', target: 'target', source: ` function target() {} function invoke(callback) { callback(); } const first = target; const second = first; invoke(second); `, }, { language: SupportedLanguages.Python, extension: 'py', caller: 'invoke', target: 'target', source: ` def target(): pass def invoke(callback): callback() first = target second = first invoke(second) `, }, { language: SupportedLanguages.Java, extension: 'java', caller: 'invoke', target: 'target', source: ` class Demo { static void target() {} static void invoke(Runnable callback) { callback.run(); } static void main(String[] args) { Runnable first = Demo::target; Runnable second = first; invoke(second); } } `, }, { language: SupportedLanguages.Kotlin, extension: 'kt', caller: 'invoke', target: 'target', source: ` fun target() {} fun invoke(callback: () -> Unit) { callback() } fun main() { val first = ::target val second = first invoke(second) } `, }, { language: SupportedLanguages.Go, extension: 'go', caller: 'invoke', target: 'target', source: ` package main func target() {} func invoke(callback func()) { callback() } func main() { first := target second := first invoke(second) } `, }, { language: SupportedLanguages.Rust, extension: 'rs', caller: 'invoke', target: 'target', source: ` fn target() {} fn invoke(callback: fn()) { callback(); } fn main() { let first = target; let second = first; invoke(second); } `, }, { language: SupportedLanguages.CSharp, extension: 'cs', caller: 'Invoke', target: 'Target', source: ` using System; class Demo { static void Target() {} static void Invoke(Action callback) { callback(); } static void Main() { Action first = Target; Action second = first; Invoke(second); } } `, }, { language: SupportedLanguages.PHP, extension: 'php', caller: 'invoke', target: 'target', source: ` Void) { callback() } func main() { let first = target let second = first invoke(second) } `, }, { language: SupportedLanguages.Dart, extension: 'dart', caller: 'invoke', target: 'target', source: ` void target() {} void invoke(void Function() callback) { callback(); } void main() { final first = target; final second = first; invoke(second); } `, }, { language: SupportedLanguages.Vue, extension: 'vue', caller: 'invoke', target: 'target', source: ` `, }, { language: SupportedLanguages.Cobol, extension: 'cbl', caller: 'INVOKE', target: 'TARGET', source: ` >>SOURCE FORMAT FREE IDENTIFICATION DIVISION. PROGRAM-ID. MAIN. DATA DIVISION. WORKING-STORAGE SECTION. 01 CALLBACK USAGE PROCEDURE-POINTER. PROCEDURE DIVISION. SET CALLBACK TO ENTRY "TARGET". CALL "INVOKE" USING CALLBACK. STOP RUN. END PROGRAM MAIN. IDENTIFICATION DIVISION. PROGRAM-ID. INVOKE. DATA DIVISION. LINKAGE SECTION. 01 CB USAGE PROCEDURE-POINTER. PROCEDURE DIVISION USING CB. CALL CB. GOBACK. END PROGRAM INVOKE. IDENTIFICATION DIVISION. PROGRAM-ID. TARGET. PROCEDURE DIVISION. GOBACK. END PROGRAM TARGET. `, }, { language: SupportedLanguages.Zig, extension: 'zig', caller: 'invoke', target: 'target', source: ` fn target() void {} fn invoke(callback: *const fn () void) void { callback(); } pub fn main() void { const first = target; const second = first; invoke(second); } `, }, ] as const; async function runSource(extension: string, source: string, options: PipelineOptions = {}) { return runSources({ [`main.${extension}`]: source }, options); } async function runSources(files: Record, options: PipelineOptions = {}) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-callable-flow-')); try { for (const [filePath, source] of Object.entries(files)) { const fullPath = path.join(root, filePath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, source, 'utf8'); } return await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true, ...options }); } finally { fs.rmSync(root, { recursive: true, force: true }); } } function callableCallSiteLines( result: Awaited>, source: string, target: string, ): number[] { return getRelationships(result, 'CALLS') .filter( (edge) => edge.source === source && edge.target === target && edge.rel.reason === 'callable-value-flow', ) .map((edge) => Number(edge.rel.id.match(/:(\d+):(\d+)$/)?.[1])) .sort((left, right) => left - right); } function callsFrom( result: Awaited>, source: string, ): Array<{ target: string; reason: string }> { return getRelationships(result, 'CALLS') .filter((edge) => edge.source === source) .map((edge) => ({ target: edge.target, reason: edge.rel.reason ?? '' })); } function callableTargetQualifiedNames( result: Awaited>, source: string, ): string[] { return getRelationships(result, 'CALLS') .filter((edge) => edge.source === source && edge.rel.reason === 'callable-value-flow') .map((edge) => result.graph.getNode(edge.rel.targetId)?.properties.qualifiedName ?? edge.target) .sort(); } function callableTargetIds( result: Awaited>, source: string, ): string[] { return getRelationships(result, 'CALLS') .filter((edge) => edge.source === source && edge.rel.reason === 'callable-value-flow') .map((edge) => edge.rel.targetId) .sort(); } describe('callable value flow', () => { it('governs every registered language provider', () => { expect(Object.keys(CALLABLE_FLOW_PROVIDER_COVERAGE).sort()).toEqual( Object.keys(providers).sort(), ); }); it.each(PROVIDER_FLOW_CASES)( 'resolves assign → copy → argument → invoke for $language', async ({ extension, source, caller, target }) => { const result = await runSource(extension, source); expect(callsFrom(result, caller)).toContainEqual({ target, reason: 'callable-value-flow', }); }, 90_000, ); it('resolves C function pointers, pointer copies, pointer-to-pointer loads, and two wrappers', async () => { const result = await runSource( 'c', ` void target(void) {} void callback(void) {} void installed_target(void) {} void invoke(void (*callback)(void)) { callback(); } void outer(void (*cb)(void)) { invoke(cb); } void install(void (**slot)(void)) { *slot = &installed_target; } int entry(void) { void (*fp)(void) = ⌖ void (*fp2)(void) = fp; void (**slot)(void) = &fp2; fp(); (*fp2)(); (*slot)(); (**slot)(); invoke(*slot); outer(target); void (*installed)(void) = &callback; install(&installed); installed(); return 0; } `, ); expect(callsFrom(result, 'entry')).toEqual( expect.arrayContaining([ { target: 'target', reason: 'callable-value-flow' }, { target: 'installed_target', reason: 'callable-value-flow' }, expect.objectContaining({ target: 'invoke' }), expect.objectContaining({ target: 'outer' }), expect.objectContaining({ target: 'install' }), ]), ); expect(callsFrom(result, 'invoke')).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); expect(callsFrom(result, 'invoke').some((edge) => edge.target === 'callback')).toBe(false); expect(callableCallSiteLines(result, 'entry', 'target')).toEqual([13, 14, 15, 16]); expect(callableCallSiteLines(result, 'entry', 'installed_target')).toEqual([21]); expect(callableCallSiteLines(result, 'invoke', 'target')).toEqual([5]); }, 60_000); it('joins C prototypes to cross-file definitions without leaking a static decoy', async () => { const result = await runSources({ 'target.c': 'void target(void) {}\n', 'decoy.c': 'static void target(void) {}\n', 'wrapper.c': 'void invoke(void (*callback)(void)) { callback(); }\n', 'main.c': ` void target(void); void invoke(void (*callback)(void)); int main(void) { void (*assigned)(void) = target; invoke(assigned); return 0; } `, }); const callbackEdges = getRelationships(result, 'CALLS').filter( (edge) => edge.source === 'invoke' && edge.target === 'target' && edge.rel.reason === 'callable-value-flow', ); expect(callbackEdges).toHaveLength(1); expect(callbackEdges[0]!.sourceFilePath).toBe('wrapper.c'); expect(callbackEdges[0]!.targetFilePath).toBe('target.c'); }, 60_000); it('uses a C++ function-pointer signature to select one overload and suppresses untyped overload sets', async () => { const result = await runSource( 'cpp', ` void target(int) {} void target(double) {} int entry() { void (*typed)(int) = ⌖ auto unresolved = target; typed(1); unresolved(1); return 0; } `, ); expect(callableTargetQualifiedNames(result, 'entry')).toEqual(['target']); expect(callableCallSiteLines(result, 'entry', 'target')).toEqual([8]); const targetEdge = getRelationships(result, 'CALLS').find( (edge) => edge.source === 'entry' && edge.target === 'target' && edge.rel.reason === 'callable-value-flow', ); expect(result.graph.getNode(targetEdge!.rel.targetId)?.properties.parameterTypes).toEqual([ 'int', ]); }, 60_000); it('retains C++ pointer and member-pointer signatures across split declaration and assignment', async () => { const result = await runSource( 'cpp', ` void target(int) {} void target(double) {} struct Base { virtual void run() const {} virtual void run() {} }; struct Derived : Base { void run() const override {} void run() override {} }; void invoke(void (*callback)(int), Derived& value, void (Base::*member)() const) { callback(1); (value.*member)(); } int entry() { void (*callback)(int); callback = ⌖ void (Base::*member)() const; member = &Base::run; Derived value; invoke(callback, value, member); return 0; } `, { skipGraphPhases: false }, ); const targets = getRelationships(result, 'CALLS') .filter((edge) => edge.source === 'invoke' && edge.rel.reason === 'callable-value-flow') .map((edge) => ({ id: edge.rel.targetId, properties: result.graph.getNode(edge.rel.targetId)?.properties, })); expect(targets).toHaveLength(2); expect( targets.some( ({ id, properties }) => id.includes('target') && JSON.stringify(properties?.parameterTypes) === JSON.stringify(['int']), ), ).toBe(true); expect( targets.some( ({ id, properties }) => id.includes('Derived.run') && properties?.isConst === true, ), ).toBe(true); expect( targets.some( ({ id, properties }) => JSON.stringify(properties?.parameterTypes) === JSON.stringify(['double']) || (id.includes('Derived.run') && properties?.isConst !== true), ), ).toBe(false); }, 90_000); it('resolves an imported C++ function assigned through an auto pointer', async () => { const result = await runSources({ 'target.hpp': 'void target();\n', 'target.cpp': '#include "target.hpp"\nvoid target() {}\n', 'main.cpp': '#include "target.hpp"\nint entry() { auto callback = ⌖ callback(); }\n', }); const targetEdges = getRelationships(result, 'CALLS').filter( (edge) => edge.source === 'entry' && edge.target === 'target' && edge.rel.reason === 'callable-value-flow', ); expect(targetEdges).toHaveLength(1); expect(result.graph.getNode(targetEdges[0]!.rel.targetId)?.properties.filePath).toBe( 'target.cpp', ); }, 90_000); it('dispatches C++ member pointers through object/reference and pointer receivers to the virtual override', async () => { const result = await runSource( 'cpp', ` struct Base { virtual void run() {} }; struct Derived : Base { void run() override {} }; void invoke(Derived& value, void (Base::*member)()) { (value.*member)(); Derived* pointer = &value; (pointer->*member)(); } int entry() { Derived value; auto member = &Base::run; invoke(value, member); return 0; } `, { skipGraphPhases: false }, ); const targets = callableTargetIds(result, 'invoke'); expect(targets).toHaveLength(2); expect(new Set(targets)).toEqual(new Set(['Method:main.cpp:Derived.run#0'])); }, 90_000); it('resolves (obj->*ptr)() regardless of tree-sitter ERROR-recovery grouping (#2522 review)', async () => { const result = await runSource( 'cpp', ` struct Base { virtual void run() {} }; struct Derived : Base { void run() override {} }; void invoke(Derived& v, void (Base::*ptr)()) { Derived* obj = &v; (obj->*ptr)(); } int entry() { Derived v; auto ptr = &Base::run; invoke(v, ptr); return 0; } `, { skipGraphPhases: false }, ); const targets = callableTargetIds(result, 'invoke'); expect(targets).toHaveLength(1); expect(new Set(targets)).toEqual(new Set(['Method:main.cpp:Derived.run#0'])); }, 90_000); it('keeps non-virtual C++ member pointers exact and dispatches virtual overload/cv signatures precisely', async () => { const result = await runSource( 'cpp', ` struct Base { virtual void run(int) {} virtual void run(double) {} void fixed() {} virtual void cv() const {} virtual void cv() {} }; struct Derived : Base { void run(int) override {} void run(double) override {} void fixed() {} void cv() const override {} void cv() override {} }; void invoke( Derived& value, void (Base::*runMember)(int), void (Base::*fixedMember)(), void (Base::*constMember)() const, void (Base::*mutableMember)() ) { (value.*runMember)(1); (value.*fixedMember)(); (value.*constMember)(); (value.*mutableMember)(); } int entry() { Derived value; void (Base::*runMember)(int) = &Base::run; void (Base::*fixedMember)() = &Base::fixed; void (Base::*constMember)() const = &Base::cv; void (Base::*mutableMember)() = &Base::cv; invoke(value, runMember, fixedMember, constMember, mutableMember); return 0; } `, { skipGraphPhases: false }, ); const targets = getRelationships(result, 'CALLS') .filter((edge) => edge.source === 'invoke' && edge.rel.reason === 'callable-value-flow') .map((edge) => ({ id: edge.rel.targetId, properties: result.graph.getNode(edge.rel.targetId)?.properties, })); expect(targets).toHaveLength(4); expect( targets.some( ({ id, properties }) => id.includes('Derived.run') && JSON.stringify(properties?.parameterTypes) === JSON.stringify(['int']), ), ).toBe(true); expect( targets.some( ({ id, properties }) => id.includes('Derived.run') && JSON.stringify(properties?.parameterTypes) === JSON.stringify(['double']), ), ).toBe(false); expect(targets.some(({ id }) => id.includes('Base.fixed'))).toBe(true); expect(targets.some(({ id }) => id.includes('Derived.fixed'))).toBe(false); expect( targets.some( ({ id, properties }) => id.includes('Derived.cv') && properties?.isConst === true, ), ).toBe(true); expect( targets.some( ({ id, properties }) => id.includes('Derived.cv') && properties?.isConst !== true, ), ).toBe(true); }, 90_000); it('resolves C++ function references and references to pointer variables', async () => { const result = await runSource( 'cpp', ` void target() {} void invoke(void (&cb)()) { cb(); } int entry() { void (*fp)(void) = ⌖ void (&fr)(void) = target; void (*&fpr)(void) = fp; fr(); fpr(); invoke(fr); return 0; } `, ); expect(callsFrom(result, 'entry')).toEqual( expect.arrayContaining([ { target: 'target', reason: 'callable-value-flow' }, expect.objectContaining({ target: 'invoke' }), ]), ); expect(callsFrom(result, 'invoke')).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); expect(callableCallSiteLines(result, 'entry', 'target')).toEqual([9, 10]); expect(callableCallSiteLines(result, 'invoke', 'target')).toEqual([3]); }, 60_000); it('propagates a C++ function reference through a cross-file prototype and formal', async () => { const result = await runSources({ 'target.cpp': 'void target() {}\n', 'decoy.cpp': 'static void target() {}\n', 'wrapper.cpp': ` using Callback = void (*)(); void invoke(Callback callback) { callback(); } `, 'main.cpp': ` using Callback = void (*)(); void target(); void invoke(Callback callback); int main() { void (&reference)() = target; invoke(reference); return 0; } `, }); const callbackEdges = getRelationships(result, 'CALLS').filter( (edge) => edge.source === 'invoke' && edge.target === 'target' && edge.rel.reason === 'callable-value-flow', ); expect(callbackEdges).toHaveLength(1); expect(callbackEdges[0]!.sourceFilePath).toBe('wrapper.cpp'); expect(callbackEdges[0]!.targetFilePath).toBe('target.cpp'); }, 60_000); it('resolves TypeScript callable assignment, copy, and actual-to-formal invocation', async () => { const result = await runSource( 'ts', ` function target(): void {} function invoke(callback: () => void): void { callback(); } function outer(cb: () => void): void { invoke(cb); } const first = target; const second = first; second(); outer(second); `, ); expect(callsFrom(result, 'main.ts')).toEqual( expect.arrayContaining([ { target: 'target', reason: 'callable-value-flow' }, expect.objectContaining({ target: 'outer' }), ]), ); expect(callsFrom(result, 'invoke')).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); }, 60_000); it('propagates an imported callable through a cross-file wrapper without selecting a decoy', async () => { const result = await runSources({ 'target.ts': `export function target(): void {}`, 'decoy.ts': `export function target(): void {}`, 'wrapper.ts': ` export function invoke(callback: () => void): void { callback(); } `, 'main.ts': ` import { target } from './target'; import { invoke } from './wrapper'; const assigned = target; invoke(assigned); `, }); const edge = getRelationships(result, 'CALLS').find( (candidate) => candidate.source === 'invoke' && candidate.target === 'target' && candidate.rel.reason === 'callable-value-flow', ); expect(edge).toBeDefined(); expect(edge!.sourceFilePath).toBe('wrapper.ts'); expect(edge!.targetFilePath).toBe('target.ts'); expect( getRelationships(result, 'CALLS').some( (candidate) => candidate.source === 'invoke' && candidate.targetFilePath === 'decoy.ts', ), ).toBe(false); }, 60_000); it('resolves a bound method assigned to a variable and passed into a wrapper', async () => { const result = await runSource( 'ts', ` class Worker { run(): void {} } class Decoy { run(): void {} } function invoke(callback: () => void): void { callback(); } const worker = new Worker(); const assigned = worker.run; invoke(assigned); `, ); const edge = getRelationships(result, 'CALLS').find( (candidate) => candidate.source === 'invoke' && candidate.target === 'run' && candidate.rel.reason === 'callable-value-flow', ); expect(edge).toBeDefined(); expect(edge!.targetLabel).toBe('Method'); expect(edge!.rel.targetId).toContain('Worker.run'); }, 60_000); it('does not reinterpret call results passed as arguments as callable designators', async () => { const result = await runSource( 'ts', ` function target(): void {} function factory(): () => void { return target; } function invoke(callback: () => void): void { callback(); } invoke(factory()); `, ); expect(callableCallSiteLines(result, 'invoke', 'target')).toEqual([]); expect(callableCallSiteLines(result, 'invoke', 'factory')).toEqual([]); }, 60_000); it('resolves COBOL procedure pointers in sequence-numbered fixed format (#2522 review)', async () => { const result = await runSource( 'cbl', ` 000100 IDENTIFICATION DIVISION. 000200 PROGRAM-ID. MAIN. 000300 DATA DIVISION. 000400 WORKING-STORAGE SECTION. 000500 01 CALLBACK USAGE IS PROCEDURE-POINTER. 000600 PROCEDURE DIVISION. 000700 SET CALLBACK TO ENTRY "TARGET". 000800 CALL "INVOKE" USING CALLBACK. 000900 STOP RUN. 001000 END PROGRAM MAIN. 001100 IDENTIFICATION DIVISION. 001200 PROGRAM-ID. INVOKE. 001300 DATA DIVISION. 001400 LINKAGE SECTION. 001500 01 CB USAGE IS PROCEDURE-POINTER. 001600 PROCEDURE DIVISION USING CB. 001700 CALL CB. 001800 GOBACK. 001900 END PROGRAM INVOKE. 002000 IDENTIFICATION DIVISION. 002100 PROGRAM-ID. TARGET. 002200 PROCEDURE DIVISION. 002300 GOBACK. 002400 END PROGRAM TARGET. `, ); expect(callableCallSiteLines(result, 'INVOKE', 'TARGET')).toEqual([18]); }, 60_000); it('propagates COBOL procedure pointers through SET x TO y copies (#2522 review)', async () => { const result = await runSource( 'cbl', ` >>SOURCE FORMAT FREE IDENTIFICATION DIVISION. PROGRAM-ID. MAIN. DATA DIVISION. WORKING-STORAGE SECTION. 01 P1 USAGE PROCEDURE-POINTER. 01 P2 USAGE PROCEDURE-POINTER. PROCEDURE DIVISION. SET P1 TO ENTRY "TARGET". SET P2 TO P1. CALL P2. STOP RUN. END PROGRAM MAIN. IDENTIFICATION DIVISION. PROGRAM-ID. TARGET. PROCEDURE DIVISION. GOBACK. END PROGRAM TARGET. `, ); expect(callableCallSiteLines(result, 'MAIN', 'TARGET')).toEqual([12]); }, 60_000); it('ignores commented-out COBOL SET statements (#2522 review)', async () => { const result = await runSource( 'cbl', ` 000100 IDENTIFICATION DIVISION. 000200 PROGRAM-ID. MAIN. 000300 DATA DIVISION. 000400 WORKING-STORAGE SECTION. 000500 01 CALLBACK USAGE IS PROCEDURE-POINTER. 000600 PROCEDURE DIVISION. 000700 SET CALLBACK TO ENTRY "TARGET". 000750* SET CALLBACK TO ENTRY "MAIN". 000800 CALL "INVOKE" USING CALLBACK. 000900 STOP RUN. 001000 END PROGRAM MAIN. 001100 IDENTIFICATION DIVISION. 001200 PROGRAM-ID. INVOKE. 001300 DATA DIVISION. 001400 LINKAGE SECTION. 001500 01 CB USAGE IS PROCEDURE-POINTER. 001600 PROCEDURE DIVISION USING CB. 001700 CALL CB. 001800 GOBACK. 001900 END PROGRAM INVOKE. 002000 IDENTIFICATION DIVISION. 002100 PROGRAM-ID. TARGET. 002200 PROCEDURE DIVISION. 002300 GOBACK. 002400 END PROGRAM TARGET. `, ); // The dead SET in the comment must not add MAIN to the callee set. expect(callableCallSiteLines(result, 'INVOKE', 'MAIN')).toEqual([]); expect(callableCallSiteLines(result, 'INVOKE', 'TARGET')).toEqual([19]); }, 60_000); it('does not resolve a Rust unit enum variant seeded as a callable value (#2522 review)', async () => { const result = await runSource( 'rs', ` enum Shape { Square, } fn dispatch(cb: fn()) { cb(); } fn entry() { let x = Shape::Square; let _ = x; } fn main() { entry(); dispatch(main); } `, ); // Shape::Square is a value, not a callable — the qualified-name guard // must keep the over-captured seed edge-free. expect( getRelationships(result, 'CALLS').filter( (edge) => edge.rel.reason === 'callable-value-flow' && edge.target === 'Square', ), ).toEqual([]); }, 60_000); it('pairs Go multi-value := positionally instead of cross-wiring (#2522 review)', async () => { const result = await runSource( 'go', `package main func targetFunc() {} func otherFunc() {} func entry() { a, b := targetFunc, otherFunc a() b() } `, ); expect(callableCallSiteLines(result, 'entry', 'targetFunc')).toEqual([8]); expect(callableCallSiteLines(result, 'entry', 'otherFunc')).toEqual([9]); }, 60_000); it('does not treat a bare Ruby identifier as a callable reference — it is a call (#2522 review)', async () => { const result = await runSource( 'rb', ` def process 'done' end def dispatch(cb) cb.call end action = process dispatch(action) `, ); // `action` holds process's RETURN VALUE (a String); a CALLS edge from // dispatch to process here is over-linking. expect(callableCallSiteLines(result, 'dispatch', 'process')).toEqual([]); expect( getRelationships(result, 'CALLS').filter( (edge) => edge.rel.reason === 'callable-value-flow' && edge.target === 'process', ), ).toEqual([]); }, 60_000); it('resolves the C ops-vtable pattern: struct-field pointer stored then called (#2522 review)', async () => { const result = await runSource( 'c', ` void handler(int x) {} struct ops { void (*run)(int); }; int entry(struct ops *o) { o->run = handler; o->run(1); return 0; } `, ); expect(callableCallSiteLines(result, 'entry', 'handler')).toEqual([6]); }, 60_000); it('resolves a file-scope function pointer assigned in one function and called in another (#2522 review)', async () => { const result = await runSource( 'c', ` void handler(int x) {} static void (*fp)(int); void init(void) { fp = handler; } void run(void) { fp(1); } `, ); expect(callableCallSiteLines(result, 'run', 'handler')).toEqual([5]); }, 60_000); it('binds variable-indexed function-pointer array cells to the array, not the index (#2522 review)', async () => { const result = await runSource( 'c', ` void handler(int x) {} int entry(void) { void (*tbl[2])(int); int i = 0; tbl[i] = handler; tbl[i](7); return 0; } `, ); expect(callableCallSiteLines(result, 'entry', 'handler')).toEqual([7]); }, 60_000); it('does not store a factory function itself through a C pointer assignment', async () => { const result = await runSource( 'c', ` void initial(void) {} void produced(void) {} void (*factory(void))(void) { return &produced; } void install(void (**slot)(void)) { *slot = factory(); } int entry(void) { void (*callback)(void) = &initial; install(&callback); callback(); return 0; } `, ); expect(callableCallSiteLines(result, 'entry', 'factory')).toEqual([]); expect(callableCallSiteLines(result, 'entry', 'initial')).toEqual([9]); }, 60_000); it('keeps a direct call outside a nested same-name callable binding on ordinary resolution', async () => { const result = await runSource( 'ts', ` function target(): void {} function replacement(): void {} function entry(): void { target(); { const target = replacement; target(); } } `, ); expect( getRelationships(result, 'CALLS').some( (edge) => edge.source === 'entry' && edge.target === 'target', ), ).toBe(true); expect(callableCallSiteLines(result, 'entry', 'replacement')).toEqual([8]); }, 60_000); it('keeps the declaration as a target when a function is reassigned through its own name and the RHS is unresolvable (#2522 review)', async () => { const result = await runSource( 'js', ` import { fancyGreet } from 'external-pkg'; function greet() {} function entry() { greet = fancyGreet; greet(); } `, ); expect(callableCallSiteLines(result, 'entry', 'greet')).toEqual([6]); }, 60_000); it('unions the declaration with a resolvable reassignment target (inclusion semantics)', async () => { const result = await runSource( 'js', ` function greet() {} function fancy() {} function entry() { greet = fancy; greet(); } `, ); expect(callableCallSiteLines(result, 'entry', 'greet')).toEqual([6]); expect(callableCallSiteLines(result, 'entry', 'fancy')).toEqual([6]); }, 60_000); it.each([ { language: 'Python', extension: 'py', source: ` def target(): pass def entry(enabled): if enabled: callback = target callback() `, }, { language: 'PHP', extension: 'php', source: ` Unit) { callback() } fun entry(enabled: Boolean) { var chosen = ::other if (enabled) { chosen = ::target } invoke(chosen) } `, }, { language: 'C#', extension: 'cs', source: ` class Demo { static void target() {} static void entry(bool enabled) { System.Action callback = () => {}; if (enabled) { callback = target; } callback(); } } `, }, { language: 'Swift', extension: 'swift', caller: 'invoke', source: ` func target() {} func other() {} func invoke(_ callback: () -> Void) { callback() } func entry(enabled: Bool) { var chosen = other if enabled { chosen = target } invoke(chosen) } `, }, { language: 'Dart', extension: 'dart', source: ` void target() {} void entry(bool enabled) { var callback = () {}; if (enabled) { callback = target; } callback(); } `, }, ])( 'keeps function-scoped callable assignments visible outside nested $language blocks', async ({ extension, source, caller = 'entry' }) => { const result = await runSource(extension, source); expect(callsFrom(result, caller)).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); }, 60_000, ); it('does not leak actuals between distinct C++ wrapper overloads', async () => { const result = await runSource( 'cpp', ` void target() {} void invoke(void (*callback)()) { callback(); } void invoke(void (*callback)(), int) { callback(); } int entry() { invoke(target); return 0; } `, ); const sourceParameterCounts = getRelationships(result, 'CALLS') .filter((edge) => edge.target === 'target' && edge.rel.reason === 'callable-value-flow') .map((edge) => result.graph.getNode(edge.rel.sourceId)?.properties.parameterCount) .sort(); expect(sourceParameterCounts).toEqual([1]); }, 90_000); it('terminates copy cycles and preserves the reachable callable target', async () => { const result = await runSource( 'ts', ` function target(): void {} let first = target; let second = first; first = second; second = first; second(); `, ); expect(callsFrom(result, 'main.ts')).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); }, 60_000); it('suppresses rather than partially emitting an over-cap candidate set', async () => { const targets = Array.from({ length: 33 }, (_, index) => `target${index}`); const result = await runSource( 'js', [ ...targets.map((name) => `function ${name}() {}`), `let callback = ${targets[0]};`, ...targets.slice(1).map((name) => `callback = ${name};`), 'callback();', ].join('\n'), ); expect(callableTargetIds(result, 'main.js')).toEqual([]); }, 60_000); it('does not steal an ordinary Java method whose name matches a callable protocol', async () => { const result = await runSource( 'java', ` class Worker { void run() {} } class Demo { static void entry() { Worker worker = new Worker(); worker.run(); } } `, ); expect(callsFrom(result, 'entry')).toContainEqual(expect.objectContaining({ target: 'run' })); expect( callsFrom(result, 'entry').some( (edge) => edge.target === 'Worker' && edge.reason === 'callable-value-flow', ), ).toBe(false); }, 60_000); it('does not treat a callable invocation result as the callable itself', async () => { const result = await runSource( 'ts', ` function target(): number { return 1; } const value = target(); value(); `, ); expect(callsFrom(result, 'main.ts')).not.toContainEqual({ target: 'target', reason: 'callable-value-flow', }); }, 60_000); it('replays identical callable-flow semantics from the durable warm parse cache', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-callable-warm-repo-')); const storage = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-callable-warm-store-')); try { fs.writeFileSync( path.join(root, 'main.ts'), ` function target(): void {} function invoke(callback: () => void): void { callback(); } const assigned = target; invoke(assigned); `, 'utf8', ); const coldCache: ParseCache = { version: PARSE_CACHE_VERSION, entries: new Map(), usedKeys: new Set(), storagePath: storage, onDiskKeys: new Set(), }; const cold = await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true, parseCache: coldCache, }); const savedKeys = await saveParseCache(storage, coldCache); await pruneAndSaveDurableParsedFileStore( getDurableParsedFileDir(storage), PARSE_CACHE_VERSION, new Set(savedKeys), ); const warmCache = await loadParseCache(storage); const warm = await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true, parseCache: warmCache, }); const project = (result: Awaited>) => getRelationships(result, 'CALLS') .filter((edge) => edge.rel.reason === 'callable-value-flow') .map( (edge) => `${edge.sourceFilePath}:${edge.source}->${edge.targetFilePath}:${edge.target}`, ) .sort(); expect(project(warm)).toEqual(project(cold)); expect(project(warm)).toEqual(['main.ts:invoke->main.ts:target']); } finally { fs.rmSync(root, { recursive: true, force: true }); fs.rmSync(storage, { recursive: true, force: true }); } }, 120_000); it('replays closure-binding resolution from the durable warm parse cache (#2693)', async () => { // The #2693 captures are provider-synthesized (Dart's fieldless // `initialized_identifier` seed) and the function-local closure `Function` // node is minted at parse time — both are replayed VERBATIM from the parse // cache, so a serialization change would surface only on the SECOND // analyze. Every other test in this PR runs cold and would stay green. const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-closure-warm-repo-')); const storage = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-closure-warm-store-')); try { fs.writeFileSync( path.join(root, 'app.dart'), 'var handler = (int x) => x;\n\nint caller() {\n return handler(1);\n}\n', 'utf8', ); fs.writeFileSync( path.join(root, 'App.kt'), 'val handler = { x: Int -> x }\n\nfun caller(): Int {\n return handler(1)\n}\n', 'utf8', ); const coldCache: ParseCache = { version: PARSE_CACHE_VERSION, entries: new Map(), usedKeys: new Set(), storagePath: storage, onDiskKeys: new Set(), }; const cold = await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true, parseCache: coldCache, }); const savedKeys = await saveParseCache(storage, coldCache); await pruneAndSaveDurableParsedFileStore( getDurableParsedFileDir(storage), PARSE_CACHE_VERSION, new Set(savedKeys), ); const warmCache = await loadParseCache(storage); const warm = await runPipelineFromRepo(root, () => {}, { skipGraphPhases: true, parseCache: warmCache, }); const project = (result: Awaited>) => getRelationships(result, 'CALLS') .map( (edge) => `${edge.sourceFilePath}:${edge.source}->${edge.targetFilePath}:${edge.target}`, ) .sort(); expect(project(warm)).toEqual(project(cold)); expect(project(warm)).toEqual([ 'App.kt:caller->App.kt:handler', 'app.dart:caller->app.dart:handler', ]); } finally { fs.rmSync(root, { recursive: true, force: true }); fs.rmSync(storage, { recursive: true, force: true }); } }, 120_000); it('keeps normal/PDG targets identical and stamps calleeIds at the indirect invocation', async () => { const source = ` function target(): void {} function invoke(callback: () => void): void { callback(); } const assigned = target; invoke(assigned); `; const normal = await runSource('ts', source); const pdg = await runSource('ts', source, { pdg: true }); const project = (result: Awaited>) => callsFrom(result, 'invoke') .filter((edge) => edge.reason === 'callable-value-flow') .map((edge) => edge.target) .sort(); expect(project(pdg)).toEqual(project(normal)); expect(project(pdg)).toEqual(['target']); const matchingBlocks: Array> = []; pdg.graph.forEachNode((node) => { if ( node.label === 'BasicBlock' && typeof node.properties.text === 'string' && node.properties.text.includes('callback()') ) { matchingBlocks.push(node.properties); } }); expect(matchingBlocks).not.toHaveLength(0); expect( matchingBlocks.some( (properties) => typeof properties.calleeIds === 'string' && properties.calleeIds.includes('target'), ), ).toBe(true); }, 90_000); it('does not read a Zig member call `x.f(arg)` as a direct call named `f` (Zig PR review, F2)', async () => { // tree-sitter-zig spells `field_expression` as `object:`/`member:`. Read as // a direct call, `self.slot.release()` next to the container alias // `pub const release = deinit;` minted a `deinit → deinit` self-loop, and // every `.init(...)` fanned its arguments out to all same-named callables // (4,761 cap warnings on Lightpanda). A receiver-typed member call must // still carry its actual into the formal AFTER the implicit `self`. const result = await runSource( 'zig', ` pub const Slot = struct { n: u32 = 0, pub fn release(self: *Slot) void { self.n = 0; } }; pub const Global = struct { slot: *Slot, pub fn deinit(self: Global) void { self.slot.release(); } pub const release = deinit; }; fn target(x: u32) void { _ = x; } fn explicitTarget(x: u32) void { _ = x; } fn namespaceTarget(x: u32) void { _ = x; } pub const Runner = struct { pub fn run(self: *Runner, cb: *const fn (u32) void) void { _ = self; cb(2); } pub fn init(cb: *const fn (u32) void) Runner { cb(3); return .{}; } }; pub const Decoy = struct { pub fn init(cb: *const fn (u32) void) Decoy { cb(4); return .{}; } }; pub const helpers = struct { pub fn apply(cb: *const fn (u32) void) void { cb(5); } }; pub fn main() void { var r: Runner = undefined; r.run(target); Runner.run(&r, explicitTarget); helpers.apply(namespaceTarget); const made: Runner = .init(target); _ = made; } `, ); // (i) no self-loop through the alias: `self.slot.release()` is a call on // Slot, not an invoke through Global's `release` name-cell. expect(callsFrom(result, 'deinit')).not.toContainEqual({ target: 'deinit', reason: 'callable-value-flow', }); // (ii) both spellings of a method call reach formal `cb` (index 1, after // the explicit `self`): the implicit receiver of `r.run(target)` is // prepended as actual 0, the explicit `Runner.run(&r, explicitTarget)` // already carries it. Dropping `self` from the formals instead lined up // only the implicit form (PR #1432 review). expect(callsFrom(result, 'run')).toContainEqual({ target: 'target', reason: 'callable-value-flow', }); expect(callsFrom(result, 'run')).toContainEqual({ target: 'explicitTarget', reason: 'callable-value-flow', }); // A namespace receiver passes nothing implicitly: `helpers.apply(cb)` must // not shift its actual off formal `cb@0`. expect(callsFrom(result, 'apply')).toContainEqual({ target: 'namespaceTarget', reason: 'callable-value-flow', }); // (iii) decl literal `.init(target)` names no callee by simple name — the // unrelated `Decoy.init` must not gain a flow edge to `target`. expect( getRelationships(result, 'CALLS').filter( (edge) => edge.rel.sourceId.includes('Decoy.init') && edge.target === 'target' && edge.rel.reason === 'callable-value-flow', ), ).toEqual([]); }, 60_000); });