mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix(cfg): harvest C++ structured-binding defs (auto [a,b]=e) (#2195)
The C++ def/use harvester only recorded a def when an init_declarator's declarator was a plain identifier, so a structured binding (auto [a,b] = mk(), incl. the auto& reference form whose binding sits under a reference_declarator) declared only the first name in phase 1 and emitted ZERO defs in phase 2 — a,b were walked as spurious uses and later use(a)/use(b) resolved to a synthetic module binding, silently corrupting REACHING_DEF/taint for an idiomatic C++17 shape. Unwrap the structured_binding_declarator in both phases and def every identifier leaf; result-of-initializer flows to the whole list. Inert for C (no structured bindings). Characterization tests added (plain + reference form). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
213ae2526e
commit
5165d71d7b
2 changed files with 67 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;}`);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue