diff --git a/gitnexus/src/core/ingestion/cfg/visitors/c-cpp-harvest.ts b/gitnexus/src/core/ingestion/cfg/visitors/c-cpp-harvest.ts index 5556616a9..730bb8930 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/c-cpp-harvest.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/c-cpp-harvest.ts @@ -190,13 +190,46 @@ export class CCppHarvester extends ScopeTreeHarvester { } } + /** + * The `structured_binding_declarator` a declarator binds (C++17 `auto [a,b]`, + * or `auto& [a,b]` whose binding sits under a `reference_declarator`), or + * undefined. C has no structured bindings, so this is inert for C. + */ + private structuredBinding(declarator: SyntaxNode | null | undefined): SyntaxNode | undefined { + let cur = declarator ?? undefined; + let hops = 4; + while (cur && hops-- > 0) { + if (cur.type === 'structured_binding_declarator') return cur; + if (cur.type !== 'reference_declarator' && cur.type !== 'pointer_declarator') break; + cur = cur.namedChildren.find( + (c) => + c.type === 'structured_binding_declarator' || + c.type === 'reference_declarator' || + c.type === 'pointer_declarator', + ); + } + return undefined; + } + + /** The identifier leaves a `structured_binding_declarator` binds (`[a, b]`). */ + private structuredBindingNames(sbd: SyntaxNode): SyntaxNode[] { + return sbd.namedChildren.filter((c) => c.type === 'identifier'); + } + private declareDeclarators(declNode: SyntaxNode, scope: Scope): void { for (let i = 0; i < declNode.namedChildCount; i++) { const d = declNode.namedChild(i); if (!d) continue; if (d.type === 'init_declarator') { - const name = this.declaratorName(d.childForFieldName('declarator') ?? null); - if (name) this.declare(name, 'var', scope); + const declarator = d.childForFieldName('declarator'); + const sbd = this.structuredBinding(declarator); + if (sbd) { + // `auto [a, b] = e;` — every identifier binds a scalar local. + for (const id of this.structuredBindingNames(sbd)) this.declare(id, 'var', scope); + } else { + const name = this.declaratorName(declarator ?? null); + if (name) this.declare(name, 'var', scope); + } } else if ( d.type === 'identifier' || d.type === 'pointer_declarator' || @@ -301,6 +334,16 @@ export class CCppHarvester extends ScopeTreeHarvester { if (d?.type !== 'init_declarator') continue; const declarator = d.childForFieldName('declarator'); const value = d.childForFieldName('value'); + const sbd = this.structuredBinding(declarator); + if (sbd && value) { + // `auto [a, b] = e;` — each identifier is a scalar def; the result + // of `e` flows into all of them (resultDefs covers the whole list). + const snap = acc.defSnapshot(); + for (const id of this.structuredBindingNames(sbd)) this.def(id, acc); + this.registerResultDefs(value, acc.defsSince(snap)); + this.walkValue(value, acc); + continue; + } const name = declarator ? this.declaratorName(declarator) : undefined; // Only an INITIALIZED declarator writes (`int x = e;`). A bare // `int x;` is not a def (it writes nothing at runtime), matching the diff --git a/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts b/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts index 2ec67c919..d3aa6c5c1 100644 --- a/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts +++ b/gitnexus/test/unit/cfg/c-cpp-visitor.test.ts @@ -231,6 +231,28 @@ describe('C CfgVisitor — def/use harvest', () => { }); }); +describe('C++ CfgVisitor — structured bindings (#2195 P1)', () => { + const isDef = (cfg: FunctionCfg, idx: number): boolean => + cfg.blocks.some((bl) => bl.statements?.some((s) => s.defs.includes(idx))); + + it('auto [a, b] = mk(); defines BOTH a and b (not just the first / neither)', () => { + const cfg = cpp.cfgOf(`void f() { auto [a, b] = mk(); use(a); use(b); }`); + expect(isDef(cfg, bindingIdx(cfg, 'a'))).toBe(true); + expect(isDef(cfg, bindingIdx(cfg, 'b'))).toBe(true); + // a later use(a) must resolve to the SAME binding the declaration defs — + // i.e. `a` is a real local, not a synthetic module binding. + const a = bindingIdx(cfg, 'a'); + const usedA = cfg.blocks.some((bl) => bl.statements?.some((s) => s.uses.includes(a))); + expect(usedA).toBe(true); + }); + + it('auto& [a, b] = ref(); (reference structured binding) defines both names', () => { + const cfg = cpp.cfgOf(`void f() { auto& [a, b] = ref(); sink(a, b); }`); + expect(isDef(cfg, bindingIdx(cfg, 'a'))).toBe(true); + expect(isDef(cfg, bindingIdx(cfg, 'b'))).toBe(true); + }); +}); + describe('C CfgVisitor — functionStartColumn', () => { it('two same-line functions get distinct functionStartColumn', () => { const cfgs = c.cfgsOf(`int a(){return 1;} int b(){return 2;}`);