mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
fix(impact): pin owning function in the statement seed anchor
The statement seed query matches blocks by startLine within the symbol's span (a forgiving window), so a closure body block that starts on the SAME source line as the seeded statement — but is owned by a different (nested) function — leaked into the seed and contaminated the intra slice with the closure's dependence. Filter the seed blocks to those whose owning fnLine === sym.startLine + 1 (block ids encode the 1-based function start line). Defensive: the filter only applies when it leaves >=1 seed, so a symbol kind whose fnLine convention differs never loses a real seed. Addresses PR #2227 tri-review finding (P3, adversarial). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3618ee62cf
commit
909a4401d6
2 changed files with 54 additions and 2 deletions
|
|
@ -988,9 +988,22 @@ export async function runImpactPDG(deps: RunPdgImpactDeps): Promise<PdgImpactRes
|
|||
queryParams,
|
||||
);
|
||||
const seedRows = rawSeedRows.slice(0, stepLimit);
|
||||
const seedBlocks: string[] = seedRows
|
||||
let seedBlocks: string[] = seedRows
|
||||
.map((r: any) => String(r.id ?? r[0] ?? ''))
|
||||
.filter((id: string) => id.length > 0);
|
||||
// Pin the OWNING function for a statement seed: a closure body block that
|
||||
// starts on the SAME source line as the seeded statement satisfies the
|
||||
// (forgiving) symbol-span window but belongs to a different function, so its
|
||||
// intra slice would leak in. The block id encodes the 1-based function start
|
||||
// line, and a block of THIS symbol has fnLine === sym.startLine + 1 (block
|
||||
// lines 1-based, symbol startLine 0-based). Drop foreign-fn seed blocks —
|
||||
// defensively: only when it leaves ≥1 seed, so a kind whose fnLine convention
|
||||
// differs never loses a real seed (it keeps the prior, slightly-loose set).
|
||||
if (statementMode && typeof sym.startLine === 'number') {
|
||||
const ownerFnLine = sym.startLine + 1;
|
||||
const owned = seedBlocks.filter((id) => fnLineOf(id) === ownerFnLine);
|
||||
if (owned.length > 0) seedBlocks = owned;
|
||||
}
|
||||
// FIX 7: the seed query probes one row past `stepLimit`, then processes at
|
||||
// most `stepLimit` rows like every BFS step. A function with more seed blocks
|
||||
// than `stepLimit` would silently under-seed (and thus under-report) — flag
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { IMPACT_MAX_DEPTH } from '../../src/mcp/tools.js';
|
||||
import { pdgLayerStatus, runImpactPDG } from '../../src/mcp/local/pdg-impact.js';
|
||||
import {
|
||||
pdgLayerStatus,
|
||||
runImpactPDG,
|
||||
type RunPdgImpactDeps,
|
||||
} from '../../src/mcp/local/pdg-impact.js';
|
||||
|
||||
describe('runImpactPDG', () => {
|
||||
it('clamps huge maxDepth values to the documented impact traversal cap', async () => {
|
||||
|
|
@ -76,6 +80,41 @@ describe('runImpactPDG', () => {
|
|||
expect((result as any).pdgEvidence.localSymbols).toBe('owner-projection');
|
||||
expect((result as any).byDepth[1][0].pdgEvidence).toBe('owner-projection');
|
||||
});
|
||||
|
||||
it('pins the owning function: a same-source-line closure block does not leak into the seed', async () => {
|
||||
// Symbol starts at 0 → owning fnLine === 1. The seed query (a forgiving
|
||||
// startLine-within-window match) returns BOTH the symbol's own block at the
|
||||
// seeded line AND a closure body block that happens to start on the same
|
||||
// source line but is owned by a function starting at line 5 (fnLine 5).
|
||||
const owned = 'BasicBlock:src/hot.ts:1:0:3'; // fnLine 1 === sym.startLine + 1
|
||||
const closureLeak = 'BasicBlock:src/hot.ts:5:10:0'; // fnLine 5 — a nested closure
|
||||
const exec: RunPdgImpactDeps['executeParameterized'] = async (_repo, query) => {
|
||||
if (query.includes('MATCH (a:BasicBlock) WHERE')) {
|
||||
return [{ id: owned }, { id: closureLeak }];
|
||||
}
|
||||
// No downstream reachability — exercises the seedBlocks-carrying branch.
|
||||
if (query.includes('MATCH (a:BasicBlock)-[r:CodeRelation]->(b:BasicBlock)')) return [];
|
||||
if (query.includes('MATCH (b:BasicBlock) WHERE b.id IN $ids')) return [];
|
||||
if (query.includes('MATCH (s:`Function`)')) return [];
|
||||
return [];
|
||||
};
|
||||
|
||||
const result = await runImpactPDG({
|
||||
repo: { lbugPath: 'repo' },
|
||||
sym: { id: 'func:hot', name: 'hot', filePath: 'src/hot.ts', startLine: 0, endLine: 20 },
|
||||
symType: 'Function',
|
||||
direction: 'downstream',
|
||||
maxDepth: 2,
|
||||
limit: 50,
|
||||
line: 7,
|
||||
executeParameterized: exec,
|
||||
});
|
||||
|
||||
// Only the owning-function block survives; the closure block is dropped.
|
||||
// Unconditional match: fails if `seedBlocks` is absent, has the wrong
|
||||
// length, or contains the closure block — no vacuous branch.
|
||||
expect(result).toMatchObject({ seedBlocks: [owned] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('pdgLayerStatus', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue