mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix(cfg): harvest C# out-var and deconstruction-declaration defs (#2195)
Two idiomatic C# write shapes recorded ZERO defs, silently breaking REACHING_DEF/taint: - out-var (G(out var n) / G(out int n)) parses as a declaration_expression; it was neither declared (phase 1) nor def'd (phase 2), so n resolved to a synthetic module binding and the callee-written value had no reaching def. - deconstruction declaration (var (a, b) = T()) has a variable_declarator whose name slot is a tuple_pattern (null name field), so declareVariableDeclaration + the variable_declaration walk skipped it entirely (only the assignment form (a,b)=T() was handled). Both a and b were dropped. Declare + def the declaration_expression's identifier (must-def: out params are definitely-assigned), and route a null-name variable_declarator through the tuple_pattern via the existing declareForeachTarget/defTupleTargets helpers. Characterization tests added; csharp suite 42 passed, grammar gate + bench --check green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a654c1c764
commit
8a5478c6f0
2 changed files with 51 additions and 1 deletions
|
|
@ -155,6 +155,14 @@ export class CsharpHarvester extends ScopeTreeHarvester {
|
|||
if (name) this.declare(name, 'var', childScope);
|
||||
break;
|
||||
}
|
||||
case 'declaration_expression': {
|
||||
// `f(out var n)` / `f(out int n)` — `n` is a fresh out-binding written
|
||||
// by the callee; declare it so its def + later uses resolve to a real
|
||||
// local rather than a synthetic module binding.
|
||||
const name = node.childForFieldName('name');
|
||||
if (name) this.declare(name, 'var', childScope);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -171,7 +179,15 @@ export class CsharpHarvester extends ScopeTreeHarvester {
|
|||
const d = declNode.namedChild(i);
|
||||
if (d?.type !== 'variable_declarator') continue;
|
||||
const name = d.childForFieldName('name');
|
||||
if (name) this.declare(name, 'var', scope);
|
||||
if (name) {
|
||||
this.declare(name, 'var', scope);
|
||||
} else {
|
||||
// Deconstruction declaration `var (a, b) = …;` — the declarator's name
|
||||
// slot is a `tuple_pattern`; declare each identifier under it (reusing
|
||||
// the foreach tuple-target logic).
|
||||
const tuple = d.namedChildren.find((c) => c.type === 'tuple_pattern');
|
||||
if (tuple) this.declareForeachTarget(tuple, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,6 +306,20 @@ export class CsharpHarvester extends ScopeTreeHarvester {
|
|||
const d = decl.namedChild(i);
|
||||
if (d?.type !== 'variable_declarator') continue;
|
||||
const name = d.childForFieldName('name');
|
||||
if (!name) {
|
||||
// Deconstruction declaration `var (a, b) = e;` — def each
|
||||
// identifier in the `tuple_pattern`; the initializer is the
|
||||
// declarator's non-pattern child.
|
||||
const tuple = d.namedChildren.find((c) => c.type === 'tuple_pattern');
|
||||
const tupleInit = d.namedChildren.find((c) => c.type !== 'tuple_pattern');
|
||||
if (tuple) {
|
||||
const snap = acc.defSnapshot();
|
||||
this.defTupleTargets(tuple, acc);
|
||||
if (tupleInit) this.registerResultDefs(tupleInit, acc.defsSince(snap));
|
||||
}
|
||||
if (tupleInit) this.walkValue(tupleInit, acc);
|
||||
continue;
|
||||
}
|
||||
// The initializer (if any) is the LAST named child after `name`.
|
||||
const init = this.declaratorInit(d);
|
||||
if (name && init) {
|
||||
|
|
@ -302,6 +332,13 @@ export class CsharpHarvester extends ScopeTreeHarvester {
|
|||
}
|
||||
return;
|
||||
}
|
||||
case 'declaration_expression': {
|
||||
// `out var n` / `out int n` — the callee writes `n` (a must-def: C#
|
||||
// requires an out parameter to be assigned before the method returns).
|
||||
const name = node.childForFieldName('name');
|
||||
if (name) this.def(name, acc);
|
||||
return;
|
||||
}
|
||||
case 'assignment_expression': {
|
||||
const left = node.childForFieldName('left');
|
||||
const right = node.childForFieldName('right');
|
||||
|
|
|
|||
|
|
@ -360,6 +360,19 @@ describe('C# CfgVisitor — def/use harvest', () => {
|
|||
expect(hasDef(cfg, z)).toBe(true);
|
||||
expect(hasUse(cfg, z)).toBe(true);
|
||||
});
|
||||
|
||||
it('out var n records n as a callee-written def (#2195 P1)', () => {
|
||||
const cfg = cs.cfgOf(`class C { void M(string s) { int.TryParse(s, out var n); use(n); } }`);
|
||||
const n = bindingIdx(cfg, 'n');
|
||||
expect(hasDef(cfg, n)).toBe(true);
|
||||
expect(hasUse(cfg, n)).toBe(true);
|
||||
});
|
||||
|
||||
it('var (a, b) = T() deconstruction declaration defines BOTH a and b (#2195 P1)', () => {
|
||||
const cfg = cs.cfgOf(`class C { void M() { var (a, b) = T(); use(a); use(b); } }`);
|
||||
expect(hasDef(cfg, bindingIdx(cfg, 'a'))).toBe(true);
|
||||
expect(hasDef(cfg, bindingIdx(cfg, 'b'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C# CfgVisitor — functionStartColumn', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue