From 324bde03689cd814856e0d2391a069d1719c1b70 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sun, 14 Jun 2026 19:33:45 +0000 Subject: [PATCH] fix(cfg): harvest all names of a Dart multi-variable declaration (#2195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `var a = 1, b = 2;` is one initialized_variable_definition whose first binding is the name/value field pair and whose subsequent bindings are trailing initialized_identifier children. Both prescan (declareInitializedVar) and the walkValue case read only the name/value fields, so every name after the first was never declared or def'd — `b` resolved to a synthetic module binding and its REACHING_DEF/taint flow was lost. Iterate the trailing initialized_identifier nodes in both phases. (Dart-3 record/list pattern declarations `var (a,b)=pair` remain a separate follow-up.) dart suite 35 passed, tsc + grammar gate green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ingestion/cfg/visitors/dart-harvest.ts | 20 ++++++++++++++++++- gitnexus/test/unit/cfg/dart-visitor.test.ts | 9 +++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/gitnexus/src/core/ingestion/cfg/visitors/dart-harvest.ts b/gitnexus/src/core/ingestion/cfg/visitors/dart-harvest.ts index 0abe244b0..631a7197f 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/dart-harvest.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/dart-harvest.ts @@ -212,10 +212,18 @@ export class DartHarvester { } } - /** Declare the `name:identifier` of an `initialized_variable_definition`. */ + /** Declare every name of an `initialized_variable_definition` (`var a = 1, b = 2`). */ private declareInitializedVar(node: SyntaxNode, kind: BindingEntry['kind']): void { const name = node.childForFieldName('name'); if (name) this.declare(name, kind); + // Trailing comma-separated bindings: each `initialized_identifier` (`b = 2`) + // names another local that the `name` field alone misses. + for (let i = 0; i < node.namedChildCount; i++) { + const c = node.namedChild(i); + if (c?.type !== 'initialized_identifier') continue; + const id = c.namedChildren.find((g) => g.type === 'identifier'); + if (id) this.declare(id, kind); + } } /** @@ -367,6 +375,16 @@ export class DartHarvester { if (value) this.walkValue(value, acc); const name = node.childForFieldName('name'); if (name) this.def(name, acc); + // Trailing comma-separated bindings (`var a = 1, b = 2;`): each + // `initialized_identifier` is an `identifier` + its own value expr. + for (let i = 0; i < node.namedChildCount; i++) { + const c = node.namedChild(i); + if (c?.type !== 'initialized_identifier') continue; + const id = c.namedChildren.find((g) => g.type === 'identifier'); + const val = c.namedChildren.find((g) => g.type !== 'identifier'); + if (val) this.walkValue(val, acc); + if (id) this.def(id, acc); + } return; } case 'assignment_expression': { diff --git a/gitnexus/test/unit/cfg/dart-visitor.test.ts b/gitnexus/test/unit/cfg/dart-visitor.test.ts index 413b0650a..abadd2019 100644 --- a/gitnexus/test/unit/cfg/dart-visitor.test.ts +++ b/gitnexus/test/unit/cfg/dart-visitor.test.ts @@ -387,6 +387,15 @@ describe('Dart CfgVisitor — def/use harvest', () => { // `obj` is used (it is the assignment target's root); no scalar `x` binding. expect(usesBinding(cfg, bindingIdx(cfg, 'obj'))).toBe(true); }); + + it('multi-variable declaration `var a = 1, b = 2;` defines BOTH names (#2195 P2)', () => { + const cfg = dart.cfgOf(`void f() { var a = 1, b = 2; use(a); use(b); }`); + // The bug dropped every name after the first — `b` became a synthetic + // global. Both must be real locals defined by the declaration. + expect(definesBinding(cfg, bindingIdx(cfg, 'a'))).toBe(true); + expect(definesBinding(cfg, bindingIdx(cfg, 'b'))).toBe(true); + expect(usesBinding(cfg, bindingIdx(cfg, 'b'))).toBe(true); // use(b) + }); }); describe('Dart CfgVisitor — functionStartColumn', () => {