mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
fix(impact): prove callees invoked on the seeded line (statement-precise)
The statement-precise projection dropped a callee invoked directly on the changed line when that callee was not also called from a downstream-dependent block: sliceCalleeNames was built only from reachableBlocks, which excludes the seed block by the seed-minus-reachable convention. Such a callee — the most directly impacted of all — was labeled unproven-bridge and dropped from statementPreciseByDepth, deflating statementPrecision. runImpactPDG now surfaces its seedBlocks (threaded through assemblePdgImpactResult and both empty/no-reachability early returns), and the dispatch unions calleesOfBlocks(seedBlocks ∪ reachableBlocks). A callee on the changed line is now proven. Recall was already preserved (full interproceduralByDepth unchanged); this fixes the precision label. Verified: parseSourceSafe@231 statementPrecision 0.500 → 0.667 (the seed-line callee is now proven). New dispatch regression test covers the empty-reachable / non-empty-seed case. Addresses PR #2227 tri-review headline (P2, Codex + correctness + adversarial). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a303cffc3
commit
3ed88b330f
3 changed files with 75 additions and 4 deletions
|
|
@ -4773,13 +4773,19 @@ export class LocalBackend {
|
|||
|
||||
// Statement-precise inter-procedural reach: a first-hop callee is "proven"
|
||||
// iff it is invoked in a block of the criterion's dependence slice. The
|
||||
// slice blocks carry the leaf callee names they call (`BasicBlock.callees`);
|
||||
// upstream/whole-symbol seeds have no discriminating slice, so the bridge
|
||||
// slice = the seed block(s) (the changed line itself) UNION the dependent
|
||||
// reachable blocks — both carry the leaf callee names they call
|
||||
// (`BasicBlock.callees`). The seed block is included because a callee
|
||||
// invoked directly on the changed line is the most-directly-impacted one,
|
||||
// yet `reachableBlocks` excludes the seed by the seed-minus-reachable
|
||||
// convention. Upstream seeds carry no discriminating slice, so the bridge
|
||||
// falls back to preserving callgraph reach.
|
||||
const reachableBlocks = ((pdgResult as any).reachableBlocks ?? []) as string[];
|
||||
const seedBlocks = ((pdgResult as any).seedBlocks ?? []) as string[];
|
||||
const sliceBlocks = [...seedBlocks, ...reachableBlocks];
|
||||
const sliceCalleeNames =
|
||||
direction === 'downstream' && reachableBlocks.length > 0
|
||||
? await this.calleesOfBlocks(repo, reachableBlocks)
|
||||
direction === 'downstream' && sliceBlocks.length > 0
|
||||
? await this.calleesOfBlocks(repo, sliceBlocks)
|
||||
: new Set<string>();
|
||||
const pdgBridge: PdgBridgeOptions | undefined =
|
||||
sliceCalleeNames.size > 0 ? { sliceCalleeNames } : undefined;
|
||||
|
|
|
|||
|
|
@ -362,6 +362,8 @@ export interface PdgImpactSuccessResult extends PdgImpactBaseResult {
|
|||
target: Required<PdgImpactTarget>;
|
||||
epistemic: 'pdg-intra-procedural';
|
||||
reachableBlocks: string[];
|
||||
/** The criterion's own seed blocks (changed statement / whole-symbol body). */
|
||||
seedBlocks: string[];
|
||||
blockCount: number;
|
||||
affectedStatements: PdgStatement[];
|
||||
affectedStatementCount: number;
|
||||
|
|
@ -378,6 +380,8 @@ export interface PdgImpactEmptyResult extends PdgImpactBaseResult {
|
|||
target: Required<PdgImpactTarget>;
|
||||
epistemic: 'no-pdg-body' | 'pdg-no-block-at-line' | 'pdg-intra-procedural';
|
||||
reachableBlocks: string[];
|
||||
/** The criterion's own seed blocks (changed statement / whole-symbol body). */
|
||||
seedBlocks: string[];
|
||||
blockCount: number;
|
||||
affectedStatements: PdgStatement[];
|
||||
affectedStatementCount: number;
|
||||
|
|
@ -500,6 +504,13 @@ function assemblePdgImpactResult(input: {
|
|||
target: { id: string; name: string; type: string; filePath: string };
|
||||
direction: 'upstream' | 'downstream';
|
||||
reachableBlocks: string[];
|
||||
/**
|
||||
* The criterion's own seed blocks (the changed statement / whole-symbol body).
|
||||
* Surfaced so the dispatcher can prove inter-procedural callees invoked
|
||||
* directly on the changed line, which are NOT in `reachableBlocks` (the
|
||||
* seed-minus-reachable convention — seeds are the target, not dependents).
|
||||
*/
|
||||
seedBlocks: string[];
|
||||
/** Reachable blocks resolved to source statements (the useful slice output). */
|
||||
affectedStatements?: PdgStatement[];
|
||||
/** The 1-based source line the slice was seeded on (statement mode only). */
|
||||
|
|
@ -604,6 +615,7 @@ function assemblePdgImpactResult(input: {
|
|||
// Raw block-level detail retained alongside the symbol projection (U3 tests
|
||||
// and the accuracy harness read these).
|
||||
reachableBlocks,
|
||||
seedBlocks: input.seedBlocks,
|
||||
blockCount: reachableBlocks.length,
|
||||
depthReached: input.depthReached,
|
||||
unresolvedBlockCount: unresolvedCount,
|
||||
|
|
@ -999,6 +1011,7 @@ export async function runImpactPDG(deps: RunPdgImpactDeps): Promise<PdgImpactRes
|
|||
direction,
|
||||
...(statementMode ? { criterionLine: line } : {}),
|
||||
reachableBlocks: [],
|
||||
seedBlocks: [],
|
||||
blockCount: 0,
|
||||
affectedStatements: [],
|
||||
affectedStatementCount: 0,
|
||||
|
|
@ -1126,6 +1139,11 @@ export async function runImpactPDG(deps: RunPdgImpactDeps): Promise<PdgImpactRes
|
|||
`(what depends on the code at that line). Inter-procedural symbol reach is attached ` +
|
||||
`separately by the unified impact dispatcher.`,
|
||||
reachableBlocks: [] as string[],
|
||||
// Carry the real seed blocks (non-empty here — the function HAS blocks, they
|
||||
// are all seeds): a callee invoked directly on the seeded line must still be
|
||||
// provable even when the line has no downstream dependents (the seed-line FN
|
||||
// the tri-review found). Empty reachableBlocks must NOT zero the seed callees.
|
||||
seedBlocks,
|
||||
blockCount: 0,
|
||||
affectedStatements: [],
|
||||
affectedStatementCount: 0,
|
||||
|
|
@ -1155,6 +1173,7 @@ export async function runImpactPDG(deps: RunPdgImpactDeps): Promise<PdgImpactRes
|
|||
},
|
||||
direction,
|
||||
reachableBlocks,
|
||||
seedBlocks,
|
||||
affectedStatements,
|
||||
criterionLine: statementMode ? (line as number) : undefined,
|
||||
projection,
|
||||
|
|
|
|||
|
|
@ -1659,6 +1659,52 @@ describe('LocalBackend impact mode (KTD1/KTD5/KTD12)', () => {
|
|||
expect(result.pdgInterprocedural).toBeDefined();
|
||||
});
|
||||
|
||||
it("mode:'pdg' downstream: a callee invoked ON the seeded line is proven even with no downstream dependents", async () => {
|
||||
// Regression for the PR #2227 tri-review P2: the seed block is excluded from
|
||||
// `reachableBlocks` (seed-minus-reachable convention), so a callee called
|
||||
// directly on the changed line — with NO downstream-dependent block — used to
|
||||
// be dropped from the statement-precise set. The dispatch now unions the seed
|
||||
// block's callees, so it must be proven.
|
||||
resolveSingleTarget();
|
||||
(executeParameterized as any).mockResolvedValue([
|
||||
{
|
||||
id: 'func:main',
|
||||
name: 'main',
|
||||
type: 'Function',
|
||||
filePath: 'src/index.ts',
|
||||
callees: 'seedCallee',
|
||||
},
|
||||
]);
|
||||
// reachableBlocks EMPTY (line N has no downstream dependents) but seedBlocks
|
||||
// carries the changed line's own block — the case that regressed.
|
||||
vi.spyOn(backend as any, '_runImpactPDG').mockResolvedValueOnce({
|
||||
mode: 'pdg',
|
||||
target: { id: 'func:main', name: 'main', type: 'Function', filePath: 'src/index.ts' },
|
||||
direction: 'downstream',
|
||||
risk: 'UNKNOWN',
|
||||
impactedCount: 0,
|
||||
epistemic: 'pdg-intra-procedural',
|
||||
reachableBlocks: [],
|
||||
seedBlocks: ['BasicBlock:src/index.ts:8:0:0'],
|
||||
blockCount: 0,
|
||||
affectedStatements: [],
|
||||
affectedStatementCount: 0,
|
||||
criterionLine: 8,
|
||||
});
|
||||
const bfsSpy = vi.spyOn(backend as any, '_runImpactBFS');
|
||||
await backend.callTool('impact', {
|
||||
target: 'main',
|
||||
direction: 'downstream',
|
||||
mode: 'pdg',
|
||||
line: 8,
|
||||
});
|
||||
const bridge = bfsSpy.mock.calls[0][4].pdgBridge;
|
||||
// The bridge is seeded from the seed block (not just reachableBlocks), so the
|
||||
// seed-line callee is provable.
|
||||
expect(bridge).toBeDefined();
|
||||
expect([...bridge.sliceCalleeNames]).toContain('seedCallee');
|
||||
});
|
||||
|
||||
it("mode:'pdg' + crossDepth → hard {error} (single-repo PDG impact)", async () => {
|
||||
resolveSingleTarget();
|
||||
const bfsSpy = vi.spyOn(backend as any, '_runImpactBFS');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue