mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
refactor(cfg): consolidate no-site def/use accumulator into DefUseAccumulator (#2195 U7)
The Kotlin/Python/Ruby/Rust/Dart/Swift harvesters each carried a byte-identical copy of the no-site def/use accumulator (~270 lines total; only Ruby's adds the live useCount() emit-guard helper). Extract it as an exported DefUseAccumulator beside CallSiteFactAccumulator in call-site-harvest.ts (the PR's own model for the with-site superset); the six harvesters import it under their existing local FactAccumulator name. Pure byte-equivalent move, no logic change: cfg unit suite 613 passed, tsc clean, bench --check fingerprints unchanged (TS/Go paths untouched). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ebd579524
commit
ccbfef2eb6
7 changed files with 65 additions and 316 deletions
|
|
@ -57,6 +57,65 @@ interface SiteFrame {
|
|||
argIdx: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record,
|
||||
* with NO call-site machinery (#2195 U7). The Kotlin / Python / Ruby / Rust /
|
||||
* Dart / Swift harvesters each carried a BYTE-IDENTICAL copy of this class:
|
||||
* those units harvest NO call sites (the taint substrate is a later step), so a
|
||||
* site-free accumulator keeps their emitted facts free of any `sites` key
|
||||
* (matching the Python harvester) and byte-identical to one another. This is the
|
||||
* no-site sibling of {@link CallSiteFactAccumulator}; `finish` omits `sites`
|
||||
* entirely. `useCount` is live (Ruby's emit guard is `defCount() ||
|
||||
* useCount()`).
|
||||
*/
|
||||
export class DefUseAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
useCount(): number {
|
||||
return this.uses.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordered, deduplicating def/use collector for one statement record, PLUS the
|
||||
* call-site harvest machinery (#2195 U6). A drop-in superset of the simple
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set(['function_expression', 'function_body']);
|
||||
|
|
@ -86,58 +87,6 @@ const COMMENT_TYPES = new Set(['comment', 'documentation_comment']);
|
|||
|
||||
const FUNCTION_VALUE_TYPES = new Set(['function_expression']);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate is
|
||||
* a later step), so a local accumulator with only the def/use/may-def machinery
|
||||
* keeps `dart-harvest.ts` free of site logic and guarantees the emitted facts
|
||||
* carry no `sites` key (mirrors the Kotlin / Swift / Python / Rust harvesters).
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DartHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (v1: no block scope). */
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set([
|
||||
|
|
@ -90,58 +91,6 @@ const NESTED_FUNCTION_TYPES = new Set([
|
|||
|
||||
const COMMENT_TYPES = new Set(['line_comment', 'multiline_comment', 'shebang_line']);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate is
|
||||
* a later step), so a local accumulator with only the def/use/may-def machinery
|
||||
* keeps `kotlin-harvest.ts` free of site logic and guarantees the emitted facts
|
||||
* carry no `sites` key (mirrors the Swift / Python / Rust harvesters).
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class KotlinHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (v1: no block scope). */
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set(['function_definition', 'lambda']);
|
||||
|
|
@ -99,58 +100,6 @@ const NESTED_FUNCTION_TYPES = new Set(['function_definition', 'lambda']);
|
|||
/** LHS pattern containers whose identifier/splat leaves are assignment targets. */
|
||||
const PATTERN_LIST_TYPES = new Set(['pattern_list', 'tuple_pattern', 'list_pattern']);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate
|
||||
* is a later step), so a local accumulator with only the def/use/may-def
|
||||
* machinery keeps `python-harvest.ts` free of site logic and guarantees the
|
||||
* emitted facts carry no `sites` key.
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class PythonHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (Python has no block scope). */
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set([
|
||||
|
|
@ -102,62 +103,6 @@ const NAMED_PARAM_TYPES = new Set([
|
|||
'keyword_parameter',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate
|
||||
* is a later step), so a local accumulator with only the def/use/may-def
|
||||
* machinery keeps `ruby-harvest.ts` free of site logic and guarantees the
|
||||
* emitted facts carry no `sites` key (matching the Python harvester).
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
useCount(): number {
|
||||
return this.uses.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class RubyHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (documented over-approximation). */
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set(['function_item', 'closure_expression']);
|
||||
|
|
@ -92,58 +93,6 @@ const PATTERN_CONTAINER_TYPES = new Set([
|
|||
'reference_pattern',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate
|
||||
* is a later step), so a local accumulator with only the def/use/may-def
|
||||
* machinery keeps `rust-harvest.ts` free of site logic and guarantees the
|
||||
* emitted facts carry no `sites` key (mirrors the Python harvester).
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class RustHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (v1: no block scope). */
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
*/
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { BindingEntry, StatementFacts } from '../types.js';
|
||||
import { DefUseAccumulator as FactAccumulator } from './call-site-harvest.js';
|
||||
|
||||
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
||||
const NESTED_FUNCTION_TYPES = new Set([
|
||||
|
|
@ -87,58 +88,6 @@ const NESTED_FUNCTION_TYPES = new Set([
|
|||
'lambda_literal',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Minimal ordered, deduplicating def/use collector for one statement record.
|
||||
* Deliberately NOT the shared {@link import('./call-site-harvest.js')
|
||||
* CallSiteFactAccumulator} — this unit harvests NO call sites (taint substrate
|
||||
* is a later step), so a local accumulator with only the def/use/may-def
|
||||
* machinery keeps `swift-harvest.ts` free of site logic and guarantees the
|
||||
* emitted facts carry no `sites` key (mirrors the Python / Rust harvesters).
|
||||
*/
|
||||
class FactAccumulator {
|
||||
private readonly defs: number[] = [];
|
||||
private readonly uses: number[] = [];
|
||||
private readonly mayDefs: number[] = [];
|
||||
private readonly defSeen = new Set<number>();
|
||||
private readonly useSeen = new Set<number>();
|
||||
private readonly mayDefSeen = new Set<number>();
|
||||
|
||||
constructor(private readonly line: number) {}
|
||||
|
||||
addDef(idx: number): void {
|
||||
if (this.defSeen.has(idx)) return;
|
||||
this.defSeen.add(idx);
|
||||
this.defs.push(idx);
|
||||
}
|
||||
|
||||
/** A def that may not execute (conditional context) — gen without kill. */
|
||||
addMayDef(idx: number): void {
|
||||
if (this.mayDefSeen.has(idx)) return;
|
||||
this.mayDefSeen.add(idx);
|
||||
this.mayDefs.push(idx);
|
||||
}
|
||||
|
||||
addUse(idx: number): void {
|
||||
if (this.useSeen.has(idx)) return;
|
||||
this.useSeen.add(idx);
|
||||
this.uses.push(idx);
|
||||
}
|
||||
|
||||
defCount(): number {
|
||||
return this.defs.length + this.mayDefs.length;
|
||||
}
|
||||
|
||||
finish(): StatementFacts {
|
||||
return {
|
||||
line: this.line,
|
||||
defs: this.defs,
|
||||
uses: this.uses,
|
||||
// Stay absent when empty — keeps the serialized side-channel payload lean.
|
||||
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SwiftHarvester {
|
||||
private readonly bindings: BindingEntry[] = [];
|
||||
/** Single function-scope name → binding index (v1: no block scope). */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue