mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
style(cfg): apply prettier + drop unused imports across the PDG files (#2195)
The merge of main into this branch pulled in the stricter quality gates (prettier --check . and eslint .), which surfaced pre-existing formatting in the PDG/CFG rollout (line-width wrapping across the visitor + harvest files, bench, tests) plus 9 no-unused-imports errors. Mechanical autofix only — npm run format + lint:fix equivalent, scoped to gitnexus/: removes unused FunctionCfg/ SiteRecord type imports left by the U8 helper consolidation and stale FinalizerFrame imports in python.ts/ruby.ts. No behavior change: tsc clean, cfg unit suite 617 passed, eslint 0 errors. (gitnexus-web class-order noise is a local tailwind-plugin artifact CI does not flag — left untouched.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b704aa9a3
commit
213ae2526e
33 changed files with 307 additions and 230 deletions
|
|
@ -403,7 +403,12 @@ function canonicalizeCfg(cfg) {
|
|||
}
|
||||
|
||||
function fingerprint(tk, scenario) {
|
||||
const out = collectFunctionCfgs(tk.parse(scenario.gen(FP_SIZE)).rootNode, tk.visitor, 'fp', NO_CAP);
|
||||
const out = collectFunctionCfgs(
|
||||
tk.parse(scenario.gen(FP_SIZE)).rootNode,
|
||||
tk.visitor,
|
||||
'fp',
|
||||
NO_CAP,
|
||||
);
|
||||
const canon = out.cfgs.map(canonicalizeCfg).sort().join('\n====\n');
|
||||
return {
|
||||
fingerprint: crypto.createHash('sha256').update(canon).digest('hex'),
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@
|
|||
* is the documented, intentional trade-off: recover the common goto-cycle case;
|
||||
* surface anything with a genuine residual anomaly rather than guess.
|
||||
*/
|
||||
import {
|
||||
isExitReachableFromAllBlocks,
|
||||
type PostDomTree,
|
||||
} from './post-dominators.js';
|
||||
import { isExitReachableFromAllBlocks, type PostDomTree } from './post-dominators.js';
|
||||
import type { CfgEdgeData, FunctionCfg } from './types.js';
|
||||
|
||||
/**
|
||||
|
|
@ -285,8 +282,7 @@ export function augmentForPostDom(cfg: FunctionCfg): FunctionCfg {
|
|||
// acyclic block (ENTRY / the spine) that is exit-unreachable only because it
|
||||
// FEEDS a trap downstream; it gets its path to EXIT for free once the trap is
|
||||
// bridged, so it is never bridged on its own.
|
||||
const isCycle =
|
||||
comp.length > 1 || cfg.edges.some((e) => e.from === rep && e.to === rep);
|
||||
const isCycle = comp.length > 1 || cfg.edges.some((e) => e.from === rep && e.to === rep);
|
||||
if (!isCycle) continue;
|
||||
|
||||
// Controlling representative: the entry-reachable member with a branch
|
||||
|
|
|
|||
|
|
@ -193,7 +193,12 @@ class CCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -561,7 +566,9 @@ class CCfgWalk {
|
|||
finishGotos(): void {
|
||||
for (const [label, froms] of this.pendingGotos) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`[cfg] unresolved goto label "${label}" routed to EXIT (${froms.length} site(s))`);
|
||||
console.warn(
|
||||
`[cfg] unresolved goto label "${label}" routed to EXIT (${froms.length} site(s))`,
|
||||
);
|
||||
for (const from of froms) this.builder.edge(from, this.builder.exitIndex, 'seq');
|
||||
}
|
||||
this.pendingGotos.clear();
|
||||
|
|
|
|||
|
|
@ -183,7 +183,12 @@ class CsharpCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -555,9 +560,7 @@ class CsharpCfgWalk {
|
|||
|
||||
this.cfc.pushSwitch(switchExit);
|
||||
const body = stmt.childForFieldName('body');
|
||||
const sections = body
|
||||
? body.namedChildren.filter((c) => c.type === 'switch_section')
|
||||
: [];
|
||||
const sections = body ? body.namedChildren.filter((c) => c.type === 'switch_section') : [];
|
||||
|
||||
// Each `switch_section`'s case-test patterns/`when` clauses evaluate before
|
||||
// the body runs — harvest their uses (and any pattern binding as a may-def)
|
||||
|
|
@ -599,9 +602,7 @@ class CsharpCfgWalk {
|
|||
|
||||
/** A switch_section's body statements (everything but its case labels/patterns). */
|
||||
private sectionStatements(section: SyntaxNode): SyntaxNode[] {
|
||||
return section.namedChildren.filter(
|
||||
(c) => this.isStatementLike(c) && c.type !== 'comment',
|
||||
);
|
||||
return section.namedChildren.filter((c) => this.isStatementLike(c) && c.type !== 'comment');
|
||||
}
|
||||
|
||||
/** The case-test expressions of a section (constant patterns + when clauses). */
|
||||
|
|
@ -655,13 +656,7 @@ class CsharpCfgWalk {
|
|||
// The resource (declaration or expression) is the named child before `body`.
|
||||
const resource = this.usingResource(stmt);
|
||||
const disposeFacts = resource ? this.harvest.facts(resource) : undefined;
|
||||
return this.buildProtectedSynthetic(
|
||||
bodyNode,
|
||||
stmt,
|
||||
'dispose',
|
||||
disposeFacts,
|
||||
resource ?? stmt,
|
||||
);
|
||||
return this.buildProtectedSynthetic(bodyNode, stmt, 'dispose', disposeFacts, resource ?? stmt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -704,9 +699,7 @@ class CsharpCfgWalk {
|
|||
for (const clause of catchClauses) {
|
||||
const clauseBody = clause.childForFieldName('body');
|
||||
if (finallyRes) this.handlers.push(finallyRes.entry);
|
||||
let res: SeqResult = clauseBody
|
||||
? this.visitSeq(this.statementsOf(clauseBody))
|
||||
: null;
|
||||
let res: SeqResult = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
|
||||
if (finallyRes) this.handlers.pop();
|
||||
if (res === null) {
|
||||
// Empty `catch {}` still catches — synthesize one block so exception
|
||||
|
|
@ -851,8 +844,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
|
|||
// The body is a `block` (field `body`) OR an `arrow_expression_clause`
|
||||
// (expression-bodied member) OR an expression (single-expression lambda).
|
||||
const body =
|
||||
fnNode.childForFieldName('body') ??
|
||||
fnNode.namedChildren.find((c) => c.type === 'block');
|
||||
fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'block');
|
||||
if (!body) return undefined; // abstract / partial / interface member — no body
|
||||
|
||||
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
||||
|
|
@ -864,8 +856,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
|
|||
if (body.type === 'arrow_expression_clause' || body.type !== 'block') {
|
||||
// Expression-bodied member / single-expression lambda: one block whose
|
||||
// value is returned. For an arrow clause the value is its inner expression.
|
||||
const expr =
|
||||
body.type === 'arrow_expression_clause' ? (body.namedChild(0) ?? body) : body;
|
||||
const expr = body.type === 'arrow_expression_clause' ? (body.namedChild(0) ?? body) : body;
|
||||
const blk = builder.newBlock(
|
||||
startLineOf(expr),
|
||||
endLineOf(expr),
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ export class DartHarvester {
|
|||
const names: SyntaxNode[] = [];
|
||||
for (const p of list.namedChildren) {
|
||||
if (p.type !== 'formal_parameter') continue;
|
||||
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
const name =
|
||||
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
if (name) names.push(name);
|
||||
}
|
||||
return names;
|
||||
|
|
@ -427,7 +428,10 @@ export class DartHarvester {
|
|||
// `.name` / `(...args)` — a member-access suffix name is not a scalar
|
||||
// binding; walk the argument part for uses but skip the bare property id.
|
||||
for (const c of node.namedChildren) {
|
||||
if (c.type === 'unconditional_assignable_selector' || c.type === 'conditional_assignable_selector') {
|
||||
if (
|
||||
c.type === 'unconditional_assignable_selector' ||
|
||||
c.type === 'conditional_assignable_selector'
|
||||
) {
|
||||
continue; // `.name` — property name is not a use
|
||||
}
|
||||
this.walkValue(c, acc);
|
||||
|
|
|
|||
|
|
@ -152,13 +152,11 @@ const isComment = (n: SyntaxNode): boolean => COMMENT_TYPES.has(n.type);
|
|||
|
||||
/** Whether an `expression_statement` is a bare `throw …;`. */
|
||||
const isThrowStatement = (n: SyntaxNode): boolean =>
|
||||
n.type === 'expression_statement' &&
|
||||
n.namedChildren.some((c) => c.type === 'throw_expression');
|
||||
n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'throw_expression');
|
||||
|
||||
/** Whether an `expression_statement` is a bare `rethrow;`. */
|
||||
const isRethrowStatement = (n: SyntaxNode): boolean =>
|
||||
n.type === 'expression_statement' &&
|
||||
n.namedChildren.some((c) => c.type === 'rethrow_expression');
|
||||
n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'rethrow_expression');
|
||||
|
||||
/** A statement sequence that produced no blocks (empty body) is "transparent". */
|
||||
type SeqResult = TraversalResult | null;
|
||||
|
|
@ -221,7 +219,12 @@ class DartCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -630,7 +633,11 @@ class DartCfgWalk {
|
|||
const contLabel = this.caseContinueLabel(cases[i]);
|
||||
if (!res) {
|
||||
// Empty case — spill to the next case (or switchExit).
|
||||
this.builder.edge(dispatch, i + 1 < cases.length ? entryOf[i + 1] : switchExit, 'fallthrough');
|
||||
this.builder.edge(
|
||||
dispatch,
|
||||
i + 1 < cases.length ? entryOf[i + 1] : switchExit,
|
||||
'fallthrough',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (contLabel) {
|
||||
|
|
@ -887,9 +894,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
|
|||
}
|
||||
|
||||
const walk = new DartCfgWalk(builder, harvest);
|
||||
const res = block
|
||||
? walk.visitSeq(block.namedChildren.filter((c) => !isComment(c)))
|
||||
: null;
|
||||
const res = block ? walk.visitSeq(block.namedChildren.filter((c) => !isComment(c))) : null;
|
||||
|
||||
if (!res) {
|
||||
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
||||
|
|
|
|||
|
|
@ -542,8 +542,7 @@ export class GoHarvester extends ScopeTreeHarvester {
|
|||
for (const target of targets) {
|
||||
// A `var_spec` carries its name(s) as children; an identifier resolves
|
||||
// directly. Skip the blank identifier (`_`), which binds nothing.
|
||||
const names =
|
||||
target.type === 'identifier' ? [target] : this.listIdentifiers(target);
|
||||
const names = target.type === 'identifier' ? [target] : this.listIdentifiers(target);
|
||||
for (const n of names) {
|
||||
if (n.text === '_') continue;
|
||||
defs.push(this.resolve(n));
|
||||
|
|
|
|||
|
|
@ -112,11 +112,7 @@ import type { CfgVisitor, FunctionCfg } from '../types.js';
|
|||
import { GoHarvester } from './go-harvest.js';
|
||||
|
||||
/** Go node types that own a CFG-bearing function body. */
|
||||
const GO_FUNCTION_TYPES = new Set([
|
||||
'function_declaration',
|
||||
'method_declaration',
|
||||
'func_literal',
|
||||
]);
|
||||
const GO_FUNCTION_TYPES = new Set(['function_declaration', 'method_declaration', 'func_literal']);
|
||||
|
||||
/** Statement node types that break a basic block (everything else coalesces). */
|
||||
const CONTROL_FLOW_TYPES = new Set([
|
||||
|
|
@ -212,7 +208,12 @@ class GoCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -347,7 +348,8 @@ class GoCfgWalk {
|
|||
private visitLabeled(stmt: SyntaxNode): SeqResult {
|
||||
const labelNode = stmt.childForFieldName('label');
|
||||
const label = labelNode?.text;
|
||||
const body = stmt.namedChildren.find((c) => c.id !== labelNode?.id && c.type !== 'comment') ?? null;
|
||||
const body =
|
||||
stmt.namedChildren.find((c) => c.id !== labelNode?.id && c.type !== 'comment') ?? null;
|
||||
|
||||
if (label !== undefined && body && this.isBreakableStatement(body)) {
|
||||
// Forward the label to the loop/switch/select frame this statement pushes.
|
||||
|
|
@ -699,9 +701,7 @@ class GoCfgWalk {
|
|||
if (test) this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
|
||||
}
|
||||
|
||||
const result = this.buildCases(dispatch, switchExit, cases, (c) =>
|
||||
this.exprCaseBody(c),
|
||||
);
|
||||
const result = this.buildCases(dispatch, switchExit, cases, (c) => this.exprCaseBody(c));
|
||||
this.cfc.pop();
|
||||
return result;
|
||||
}
|
||||
|
|
@ -721,20 +721,14 @@ class GoCfgWalk {
|
|||
const cases = stmt.namedChildren.filter(
|
||||
(c) => c.type === 'type_case' || c.type === 'default_case',
|
||||
);
|
||||
const result = this.buildCases(dispatch, switchExit, cases, (c) =>
|
||||
this.typeCaseBody(c),
|
||||
);
|
||||
const result = this.buildCases(dispatch, switchExit, cases, (c) => this.typeCaseBody(c));
|
||||
this.cfc.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
private visitSelect(stmt: SyntaxNode): TraversalResult {
|
||||
const labels = this.takeLabels();
|
||||
const dispatch = this.builder.newBlock(
|
||||
startLineOf(stmt),
|
||||
startLineOf(stmt),
|
||||
'select',
|
||||
);
|
||||
const dispatch = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), 'select');
|
||||
const selectExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
||||
|
||||
this.cfc.pushSwitch(selectExit, labels);
|
||||
|
|
@ -751,9 +745,7 @@ class GoCfgWalk {
|
|||
}
|
||||
|
||||
const hasDefault = cases.some((c) => c.type === 'default_case');
|
||||
const result = this.buildCases(dispatch, selectExit, cases, (c) =>
|
||||
this.commCaseBody(c),
|
||||
);
|
||||
const result = this.buildCases(dispatch, selectExit, cases, (c) => this.commCaseBody(c));
|
||||
|
||||
// A `select` with NO default BLOCKS until a case is ready — and a `select {}`
|
||||
// with no cases at all blocks forever. Either way EXIT must stay
|
||||
|
|
|
|||
|
|
@ -220,7 +220,12 @@ class JavaCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ export class KotlinHarvester {
|
|||
if (vd.type === 'variable_declaration') this.declareVariableDeclaration(vd, 'param');
|
||||
else if (vd.type === 'multi_variable_declaration') {
|
||||
for (const inner of vd.namedChildren) {
|
||||
if (inner.type === 'variable_declaration') this.declareVariableDeclaration(inner, 'param');
|
||||
if (inner.type === 'variable_declaration')
|
||||
this.declareVariableDeclaration(inner, 'param');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,12 @@ class KotlinCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -835,9 +840,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
|
|||
}
|
||||
|
||||
const walk = new KotlinCfgWalk(builder, harvest);
|
||||
const res = body
|
||||
? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c)))
|
||||
: null;
|
||||
const res = body ? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c))) : null;
|
||||
|
||||
if (!res) {
|
||||
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
||||
|
|
|
|||
|
|
@ -625,7 +625,10 @@ export class PhpHarvester {
|
|||
const accesses: string[] = [];
|
||||
let cur: SyntaxNode = this.unwrapParen(node);
|
||||
for (;;) {
|
||||
if (cur.type === 'member_access_expression' || cur.type === 'nullsafe_member_access_expression') {
|
||||
if (
|
||||
cur.type === 'member_access_expression' ||
|
||||
cur.type === 'nullsafe_member_access_expression'
|
||||
) {
|
||||
const field = cur.childForFieldName('name');
|
||||
accesses.unshift(field?.text ?? '');
|
||||
const obj = cur.childForFieldName('object');
|
||||
|
|
|
|||
|
|
@ -211,7 +211,12 @@ class PhpCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,9 +146,7 @@ export class PythonHarvester {
|
|||
private declareParams(fnNode: SyntaxNode): void {
|
||||
const params =
|
||||
fnNode.childForFieldName('parameters') ??
|
||||
fnNode.namedChildren.find(
|
||||
(c) => c.type === 'parameters' || c.type === 'lambda_parameters',
|
||||
);
|
||||
fnNode.namedChildren.find((c) => c.type === 'parameters' || c.type === 'lambda_parameters');
|
||||
if (!params) return;
|
||||
for (let i = 0; i < params.namedChildCount; i++) {
|
||||
const p = params.namedChild(i);
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ import {
|
|||
drainFinalizerPending,
|
||||
wireJumpThroughFinalizers,
|
||||
} from '../control-flow-context.js';
|
||||
import type { FinalizerFrame } from '../control-flow-context.js';
|
||||
import type { TraversalResult } from '../traversal-result.js';
|
||||
import type { CfgVisitor, FunctionCfg } from '../types.js';
|
||||
import { PythonHarvester } from './python-harvest.js';
|
||||
|
|
@ -191,7 +190,12 @@ class PythonCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -461,7 +465,11 @@ class PythonCfgWalk {
|
|||
return alt?.type === 'else_clause' ? alt : undefined;
|
||||
}
|
||||
|
||||
private loopHeaderText(stmt: SyntaxNode, left: SyntaxNode | null, right: SyntaxNode | null): string {
|
||||
private loopHeaderText(
|
||||
stmt: SyntaxNode,
|
||||
left: SyntaxNode | null,
|
||||
right: SyntaxNode | null,
|
||||
): string {
|
||||
const l = left?.text ?? '';
|
||||
const r = right?.text ?? '';
|
||||
return l || r ? `for ${l} in ${r}` : stmt.text.split('\n')[0];
|
||||
|
|
@ -550,7 +558,8 @@ class PythonCfgWalk {
|
|||
// runs OUTSIDE this try's finalizer frame (a return inside finally threads
|
||||
// only OUTER finallys).
|
||||
const finallyBlock = finallyClause
|
||||
? this.bodyBlockOf(finallyClause) ?? finallyClause.namedChildren.find((c) => c.type === 'block')
|
||||
? (this.bodyBlockOf(finallyClause) ??
|
||||
finallyClause.namedChildren.find((c) => c.type === 'block'))
|
||||
: undefined;
|
||||
const finallyRes = finallyBlock ? this.visitSeq(this.statementsOf(finallyBlock)) : null;
|
||||
const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
|
||||
|
|
@ -565,7 +574,13 @@ class PythonCfgWalk {
|
|||
// The `except E as e:` header binds `e` — its own facts-only block in front
|
||||
// of the handler body (the binding happens once, on handler entry).
|
||||
const headFacts = this.harvest.exceptHeadFacts(clause);
|
||||
const headBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', headFacts);
|
||||
const headBlock = this.builder.newBlock(
|
||||
startLineOf(clause),
|
||||
startLineOf(clause),
|
||||
'',
|
||||
'normal',
|
||||
headFacts,
|
||||
);
|
||||
const bodyRes = handlerBlock ? this.visitSeq(this.statementsOf(handlerBlock)) : null;
|
||||
if (bodyRes) {
|
||||
this.builder.edge(headBlock, bodyRes.entry, 'seq');
|
||||
|
|
@ -650,7 +665,8 @@ class PythonCfgWalk {
|
|||
);
|
||||
const matchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
||||
|
||||
const body = stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'block');
|
||||
const body =
|
||||
stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'block');
|
||||
const cases = body ? body.namedChildren.filter((c) => c.type === 'case_clause') : [];
|
||||
|
||||
// A case guard (`case P if g:`) evaluates conditionally — harvest its uses
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@ export class RubyHarvester {
|
|||
return;
|
||||
}
|
||||
if (NAMED_PARAM_TYPES.has(p.type)) {
|
||||
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
const name =
|
||||
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
if (name) this.declare(name, 'param');
|
||||
return;
|
||||
}
|
||||
|
|
@ -319,7 +320,8 @@ export class RubyHarvester {
|
|||
return;
|
||||
}
|
||||
if (NAMED_PARAM_TYPES.has(p.type)) {
|
||||
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
const name =
|
||||
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
|
||||
if (name) this.def(name, acc);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,19 +108,12 @@ import {
|
|||
drainFinalizerPending,
|
||||
wireJumpThroughFinalizers,
|
||||
} from '../control-flow-context.js';
|
||||
import type { FinalizerFrame } from '../control-flow-context.js';
|
||||
import type { TraversalResult } from '../traversal-result.js';
|
||||
import type { CfgVisitor, FunctionCfg } from '../types.js';
|
||||
import { RubyHarvester } from './ruby-harvest.js';
|
||||
|
||||
/** Ruby node types that own a CFG-bearing function/closure body. */
|
||||
const RUBY_FUNCTION_TYPES = new Set([
|
||||
'method',
|
||||
'singleton_method',
|
||||
'do_block',
|
||||
'block',
|
||||
'lambda',
|
||||
]);
|
||||
const RUBY_FUNCTION_TYPES = new Set(['method', 'singleton_method', 'do_block', 'block', 'lambda']);
|
||||
|
||||
/** Statement node types that break a basic block (everything else coalesces). */
|
||||
const CONTROL_FLOW_TYPES = new Set([
|
||||
|
|
@ -230,7 +223,12 @@ class RubyCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -666,7 +664,8 @@ class RubyCfgWalk {
|
|||
const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
||||
|
||||
const clauses = stmt.namedChildren.filter((c) => c.type === 'in_clause');
|
||||
const elseClause = stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
|
||||
const elseClause =
|
||||
stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
|
||||
|
||||
// An `in`-clause guard (`in P if g`) evaluates conditionally on the dispatch.
|
||||
for (const c of clauses) {
|
||||
|
|
|
|||
|
|
@ -137,9 +137,7 @@ export class RustHarvester {
|
|||
private declareParams(fnNode: SyntaxNode): void {
|
||||
const params =
|
||||
fnNode.childForFieldName('parameters') ??
|
||||
fnNode.namedChildren.find(
|
||||
(c) => c.type === 'parameters' || c.type === 'closure_parameters',
|
||||
);
|
||||
fnNode.namedChildren.find((c) => c.type === 'parameters' || c.type === 'closure_parameters');
|
||||
if (!params) return;
|
||||
for (let i = 0; i < params.namedChildCount; i++) {
|
||||
const p = params.namedChild(i);
|
||||
|
|
@ -292,7 +290,8 @@ export class RustHarvester {
|
|||
// Fallback: declare any identifier / shorthand leaf.
|
||||
for (let i = 0; i < field.namedChildCount; i++) {
|
||||
const c = field.namedChild(i);
|
||||
if (c?.type === 'shorthand_field_identifier' || c?.type === 'identifier') this.declare(c, kind);
|
||||
if (c?.type === 'shorthand_field_identifier' || c?.type === 'identifier')
|
||||
this.declare(c, kind);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -186,9 +186,7 @@ class RustCfgWalk {
|
|||
dangling = [...res.exits];
|
||||
} else {
|
||||
const idx =
|
||||
openSimple === undefined
|
||||
? this.openBlock(stmt)
|
||||
: this.extendOpen(openSimple, stmt);
|
||||
openSimple === undefined ? this.openBlock(stmt) : this.extendOpen(openSimple, stmt);
|
||||
if (openSimple === undefined) {
|
||||
if (entry === undefined) entry = idx;
|
||||
else this.builder.connect(dangling, idx, 'seq');
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ export class SwiftHarvester {
|
|||
* `lambda_literal` carries the `statements` directly.
|
||||
*/
|
||||
private bodyOf(fnNode: SyntaxNode): SyntaxNode | undefined {
|
||||
const fb = fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'function_body');
|
||||
const fb =
|
||||
fnNode.childForFieldName('body') ??
|
||||
fnNode.namedChildren.find((c) => c.type === 'function_body');
|
||||
if (fb && fb.type === 'function_body') {
|
||||
return fb.namedChildren.find((c) => c.type === 'statements') ?? fb;
|
||||
}
|
||||
|
|
@ -156,7 +158,9 @@ export class SwiftHarvester {
|
|||
for (const id of params.namedChildren) {
|
||||
if (id.type === 'simple_identifier') this.declare(id, 'param');
|
||||
else if (id.type === 'lambda_parameter') {
|
||||
const name = id.childForFieldName('name') ?? id.namedChildren.find((c) => c.type === 'simple_identifier');
|
||||
const name =
|
||||
id.childForFieldName('name') ??
|
||||
id.namedChildren.find((c) => c.type === 'simple_identifier');
|
||||
if (name) this.declare(name, 'param');
|
||||
}
|
||||
}
|
||||
|
|
@ -319,7 +323,9 @@ export class SwiftHarvester {
|
|||
for (const id of params.namedChildren) {
|
||||
if (id.type === 'simple_identifier') this.def(id, acc);
|
||||
else if (id.type === 'lambda_parameter') {
|
||||
const name = id.childForFieldName('name') ?? id.namedChildren.find((c) => c.type === 'simple_identifier');
|
||||
const name =
|
||||
id.childForFieldName('name') ??
|
||||
id.namedChildren.find((c) => c.type === 'simple_identifier');
|
||||
if (name) this.def(name, acc);
|
||||
}
|
||||
}
|
||||
|
|
@ -447,10 +453,11 @@ export class SwiftHarvester {
|
|||
// `try expr` / `try? expr` / `try! expr` — the wrapped expression's uses.
|
||||
const expr = node.childForFieldName('expr');
|
||||
if (expr) this.walkValue(expr, acc);
|
||||
else for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c && c.type !== 'try_operator') this.walkValue(c, acc);
|
||||
}
|
||||
else
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c && c.type !== 'try_operator') this.walkValue(c, acc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'conjunction_expression':
|
||||
|
|
|
|||
|
|
@ -162,7 +162,9 @@ class SwiftCfgWalk {
|
|||
|
||||
/** Statements of a `statements` node, ignoring comments. */
|
||||
private statementsOf(block: SyntaxNode): SyntaxNode[] {
|
||||
return block.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment');
|
||||
return block.namedChildren.filter(
|
||||
(c) => c.type !== 'comment' && c.type !== 'multiline_comment',
|
||||
);
|
||||
}
|
||||
|
||||
/** The body `statements` of a loop/branch node (the LAST `statements` child). */
|
||||
|
|
@ -206,7 +208,12 @@ class SwiftCfgWalk {
|
|||
openSimple = idx;
|
||||
dangling = [idx];
|
||||
} else {
|
||||
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
||||
this.builder.extendBlock(
|
||||
openSimple,
|
||||
endLineOf(stmt),
|
||||
stmt.text,
|
||||
this.harvest.facts(stmt),
|
||||
);
|
||||
}
|
||||
// A straight-line statement may contain a `try` — it can error-return.
|
||||
this.wireTryExits(stmt, openSimple);
|
||||
|
|
@ -661,8 +668,10 @@ class SwiftCfgWalk {
|
|||
}
|
||||
|
||||
private entryIsDefault(entry: SyntaxNode): boolean {
|
||||
return entry.namedChildren.some((c) => c.type === 'default_keyword') ||
|
||||
entry.children.some((c) => c.type === 'default_keyword');
|
||||
return (
|
||||
entry.namedChildren.some((c) => c.type === 'default_keyword') ||
|
||||
entry.children.some((c) => c.type === 'default_keyword')
|
||||
);
|
||||
}
|
||||
|
||||
/** A `switch_entry` ends in an explicit `fallthrough` keyword child. */
|
||||
|
|
@ -701,7 +710,13 @@ class SwiftCfgWalk {
|
|||
}
|
||||
const errFacts = this.harvest.catchErrorFacts(clause);
|
||||
if (errFacts) {
|
||||
const errBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', errFacts);
|
||||
const errBlock = this.builder.newBlock(
|
||||
startLineOf(clause),
|
||||
startLineOf(clause),
|
||||
'',
|
||||
'normal',
|
||||
errFacts,
|
||||
);
|
||||
this.builder.edge(errBlock, res.entry, 'seq');
|
||||
res = { entry: errBlock, exits: res.exits };
|
||||
}
|
||||
|
|
@ -896,7 +911,8 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
|
|||
*/
|
||||
function bodyStatementsOf(fnNode: SyntaxNode): SyntaxNode | undefined {
|
||||
const fb =
|
||||
fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'function_body');
|
||||
fnNode.childForFieldName('body') ??
|
||||
fnNode.namedChildren.find((c) => c.type === 'function_body');
|
||||
if (fb && fb.type === 'function_body') {
|
||||
return fb.namedChildren.find((c) => c.type === 'statements');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,7 @@ import { emitTsScopeCaptures } from '../../src/core/ingestion/languages/typescri
|
|||
import { interpretTsImport } from '../../src/core/ingestion/languages/typescript/interpret.js';
|
||||
import { makeCfgHarness } from './cfg-harness.js';
|
||||
|
||||
const harness = makeCfgHarness(
|
||||
TypeScript.typescript,
|
||||
createTypeScriptCfgVisitor(),
|
||||
'fixture.ts',
|
||||
);
|
||||
const harness = makeCfgHarness(TypeScript.typescript, createTypeScriptCfgVisitor(), 'fixture.ts');
|
||||
|
||||
export const parse = harness.parse;
|
||||
export const collectFunctions = harness.collectFunctions;
|
||||
|
|
|
|||
|
|
@ -447,8 +447,7 @@ describe('U7 — C-family worker-mode --pdg pipeline', () => {
|
|||
// pdg:false ≡ pdg-absent — the dominant existing-user path is untouched.
|
||||
expect(offDigest).toEqual(defaultDigest);
|
||||
// None of the PDG node/rel types leak into the flag-off graph.
|
||||
for (const t of ['BasicBlock'] as const)
|
||||
expect(defaultDigest.byType[t]).toBeUndefined();
|
||||
for (const t of ['BasicBlock'] as const) expect(defaultDigest.byType[t]).toBeUndefined();
|
||||
for (const t of ['CFG', 'REACHING_DEF', 'CDG', 'TAINTED', 'SANITIZES'] as const)
|
||||
expect(defaultDigest.byRelType[t]).toBeUndefined();
|
||||
// Committed golden: the flag-off graph is pinned by snapshot so a future
|
||||
|
|
@ -470,69 +469,77 @@ describe('U7 — remaining languages worker-mode --pdg pipeline (#2195 capstone)
|
|||
!vendored || isLanguageAvailable(SupportedLanguages[lang as keyof typeof SupportedLanguages])
|
||||
? it
|
||||
: it.skip;
|
||||
testFn(`${lang}: --pdg on emits BasicBlock + CFG + REACHING_DEF + CDG (> 0) via the worker`, async () => {
|
||||
const result = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_PDG);
|
||||
// The CFG is built in the worker — a stale dist silently zeros this.
|
||||
expect(result.usedWorkerPool, `${lang} used the worker pool`).toBe(true);
|
||||
const { basicBlocks, cfgEdges, reachingDefs, cdg } = counts(result);
|
||||
expect(basicBlocks, `${lang} BasicBlock count`).toBeGreaterThan(0);
|
||||
expect(cfgEdges, `${lang} CFG edge count`).toBeGreaterThan(0);
|
||||
// def/use harvest populated the data-dependence layer.
|
||||
expect(reachingDefs, `${lang} REACHING_DEF count`).toBeGreaterThan(0);
|
||||
// CDG > 0 proves the post-dom/CDG pass ran for SOME function in the fixture.
|
||||
expect(cdg, `${lang} CDG count`).toBeGreaterThan(0);
|
||||
// The load-bearing claim (#2197 U3): ≥1 CDG edge is sourced INSIDE the
|
||||
// non-terminating-loop function itself — so the post-dom/CDG pass was NOT
|
||||
// skipped for the function whose loop traps EXIT, i.e. EXIT stays
|
||||
// reverse-reachable end-to-end through the worker even with that loop. The
|
||||
// whole-fixture aggregate above would pass on any branching function; this
|
||||
// pins it to the hazard.
|
||||
if (hazard !== undefined) {
|
||||
expect(
|
||||
cdgSourcedInHazardFunction(result, hazard),
|
||||
`${lang} CDG edge sourced inside the non-terminating-loop function (marker ${JSON.stringify(hazard)})`,
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
// Both CFG and CDG endpoints are persisted BasicBlocks; CDG carries a T/F.
|
||||
const blockIds = new Set<string>();
|
||||
result.graph.forEachNode((n) => {
|
||||
if (n.label === 'BasicBlock') blockIds.add(n.id);
|
||||
});
|
||||
for (const rel of result.graph.iterRelationships()) {
|
||||
if (rel.type === 'CFG' || rel.type === 'REACHING_DEF' || rel.type === 'CDG') {
|
||||
expect(blockIds.has(rel.sourceId), `${lang} ${rel.type} source is a BasicBlock`).toBe(
|
||||
true,
|
||||
);
|
||||
expect(blockIds.has(rel.targetId), `${lang} ${rel.type} target is a BasicBlock`).toBe(
|
||||
true,
|
||||
);
|
||||
testFn(
|
||||
`${lang}: --pdg on emits BasicBlock + CFG + REACHING_DEF + CDG (> 0) via the worker`,
|
||||
async () => {
|
||||
const result = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_PDG);
|
||||
// The CFG is built in the worker — a stale dist silently zeros this.
|
||||
expect(result.usedWorkerPool, `${lang} used the worker pool`).toBe(true);
|
||||
const { basicBlocks, cfgEdges, reachingDefs, cdg } = counts(result);
|
||||
expect(basicBlocks, `${lang} BasicBlock count`).toBeGreaterThan(0);
|
||||
expect(cfgEdges, `${lang} CFG edge count`).toBeGreaterThan(0);
|
||||
// def/use harvest populated the data-dependence layer.
|
||||
expect(reachingDefs, `${lang} REACHING_DEF count`).toBeGreaterThan(0);
|
||||
// CDG > 0 proves the post-dom/CDG pass ran for SOME function in the fixture.
|
||||
expect(cdg, `${lang} CDG count`).toBeGreaterThan(0);
|
||||
// The load-bearing claim (#2197 U3): ≥1 CDG edge is sourced INSIDE the
|
||||
// non-terminating-loop function itself — so the post-dom/CDG pass was NOT
|
||||
// skipped for the function whose loop traps EXIT, i.e. EXIT stays
|
||||
// reverse-reachable end-to-end through the worker even with that loop. The
|
||||
// whole-fixture aggregate above would pass on any branching function; this
|
||||
// pins it to the hazard.
|
||||
if (hazard !== undefined) {
|
||||
expect(
|
||||
cdgSourcedInHazardFunction(result, hazard),
|
||||
`${lang} CDG edge sourced inside the non-terminating-loop function (marker ${JSON.stringify(hazard)})`,
|
||||
).toBe(true);
|
||||
}
|
||||
if (rel.type === 'CDG') expect(['T', 'F']).toContain(rel.reason);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
testFn(`${lang}: --pdg off emits zero PDG nodes/edges (the R3 flag-off gate)`, async () => {
|
||||
// Default (no pdg flag) and explicit pdg:false must both produce a graph
|
||||
// with NO PDG layer — the existing-user path stays untouched.
|
||||
const defaultRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_OFF);
|
||||
const offRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, {
|
||||
...WORKER_OFF,
|
||||
pdg: false,
|
||||
});
|
||||
// Both CFG and CDG endpoints are persisted BasicBlocks; CDG carries a T/F.
|
||||
const blockIds = new Set<string>();
|
||||
result.graph.forEachNode((n) => {
|
||||
if (n.label === 'BasicBlock') blockIds.add(n.id);
|
||||
});
|
||||
for (const rel of result.graph.iterRelationships()) {
|
||||
if (rel.type === 'CFG' || rel.type === 'REACHING_DEF' || rel.type === 'CDG') {
|
||||
expect(blockIds.has(rel.sourceId), `${lang} ${rel.type} source is a BasicBlock`).toBe(
|
||||
true,
|
||||
);
|
||||
expect(blockIds.has(rel.targetId), `${lang} ${rel.type} target is a BasicBlock`).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (rel.type === 'CDG') expect(['T', 'F']).toContain(rel.reason);
|
||||
}
|
||||
},
|
||||
60000,
|
||||
);
|
||||
|
||||
for (const r of [defaultRun, offRun]) {
|
||||
const { basicBlocks, cfgEdges, reachingDefs, tainted, sanitizes, cdg } = counts(r);
|
||||
expect(basicBlocks).toBe(0);
|
||||
expect(cfgEdges).toBe(0);
|
||||
expect(reachingDefs).toBe(0);
|
||||
expect(tainted).toBe(0);
|
||||
expect(sanitizes).toBe(0);
|
||||
expect(cdg).toBe(0);
|
||||
}
|
||||
testFn(
|
||||
`${lang}: --pdg off emits zero PDG nodes/edges (the R3 flag-off gate)`,
|
||||
async () => {
|
||||
// Default (no pdg flag) and explicit pdg:false must both produce a graph
|
||||
// with NO PDG layer — the existing-user path stays untouched.
|
||||
const defaultRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_OFF);
|
||||
const offRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, {
|
||||
...WORKER_OFF,
|
||||
pdg: false,
|
||||
});
|
||||
|
||||
// pdg:false ≡ pdg-absent — the symbolic graph digest is identical.
|
||||
expect(graphDigest(offRun)).toEqual(graphDigest(defaultRun));
|
||||
}, 90000);
|
||||
for (const r of [defaultRun, offRun]) {
|
||||
const { basicBlocks, cfgEdges, reachingDefs, tainted, sanitizes, cdg } = counts(r);
|
||||
expect(basicBlocks).toBe(0);
|
||||
expect(cfgEdges).toBe(0);
|
||||
expect(reachingDefs).toBe(0);
|
||||
expect(tainted).toBe(0);
|
||||
expect(sanitizes).toBe(0);
|
||||
expect(cdg).toBe(0);
|
||||
}
|
||||
|
||||
// pdg:false ≡ pdg-absent — the symbolic graph digest is identical.
|
||||
expect(graphDigest(offRun)).toEqual(graphDigest(defaultRun));
|
||||
},
|
||||
90000,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
createCCfgVisitor,
|
||||
createCppCfgVisitor,
|
||||
} from '../../../src/core/ingestion/cfg/visitors/c-cpp.js';
|
||||
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -158,7 +157,9 @@ describe('C CfgVisitor — switch (C-style fallthrough)', () => {
|
|||
|
||||
describe('C CfgVisitor — goto / labels', () => {
|
||||
it('backward goto wires to an already-seen label block', () => {
|
||||
const cfg = c.cfgOf(`void f() { int i = 0; loop: work(); i++; if (i < 10) goto loop; done(); }`);
|
||||
const cfg = c.cfgOf(
|
||||
`void f() { int i = 0; loop: work(); i++; if (i < 10) goto loop; done(); }`,
|
||||
);
|
||||
const gotoB = block(cfg, 'goto loop;');
|
||||
const label = block(cfg, 'work();');
|
||||
expect(reaches(cfg, gotoB, label)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createCsharpCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/csharp.js';
|
||||
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -61,7 +61,9 @@ describe('C# CfgVisitor — structure', () => {
|
|||
});
|
||||
|
||||
it('constructor and local function are CFG-bearing functions', () => {
|
||||
const cfgs = cs.cfgsOf(`class C { C(int a) { x = a; } void Outer() { int L(int z) { return z; } L(1); } }`);
|
||||
const cfgs = cs.cfgsOf(
|
||||
`class C { C(int a) { x = a; } void Outer() { int L(int z) { return z; } L(1); } }`,
|
||||
);
|
||||
// constructor C, Outer, and the local function L = 3 CFGs.
|
||||
expect(cfgs.length).toBeGreaterThanOrEqual(3);
|
||||
for (const cfg of cfgs) expect(reaches(cfg, cfg.entryIndex, cfg.exitIndex)).toBe(true);
|
||||
|
|
@ -121,7 +123,9 @@ describe('C# CfgVisitor — loops', () => {
|
|||
});
|
||||
|
||||
it('C-style for: init once, condition header, back-edge through update', () => {
|
||||
const cfg = cs.cfgOf(`class C { void M(int n) { for (int i = 0; i < n; i++) { step(); } done(); } }`);
|
||||
const cfg = cs.cfgOf(
|
||||
`class C { void M(int n) { for (int i = 0; i < n; i++) { step(); } done(); } }`,
|
||||
);
|
||||
const init = block(cfg, 'int i = 0');
|
||||
const header = block(cfg, 'i < n');
|
||||
const incr = block(cfg, 'i++');
|
||||
|
|
@ -133,7 +137,9 @@ describe('C# CfgVisitor — loops', () => {
|
|||
});
|
||||
|
||||
it('foreach: header + body + loop-back + exit; loop var is a def, source a use', () => {
|
||||
const cfg = cs.cfgOf(`class C { void M(int[] xs) { foreach (var x in xs) { use(x); } done(); } }`);
|
||||
const cfg = cs.cfgOf(
|
||||
`class C { void M(int[] xs) { foreach (var x in xs) { use(x); } done(); } }`,
|
||||
);
|
||||
const body = block(cfg, 'use(x);');
|
||||
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
|
||||
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
|
||||
|
|
@ -191,7 +197,9 @@ describe('C# CfgVisitor — switch', () => {
|
|||
});
|
||||
|
||||
it('switch_expression arms each dispatch as a guarded branch (switch-case)', () => {
|
||||
const cfg = cs.cfgOf(`class C { int M(int x) { return x switch { 1 => a(), 2 => b(), _ => c() }; } }`);
|
||||
const cfg = cs.cfgOf(
|
||||
`class C { int M(int x) { return x switch { 1 => a(), 2 => b(), _ => c() }; } }`,
|
||||
);
|
||||
// The switch-expression lives inside the return block — it does not break a
|
||||
// basic block, but the function still has a well-formed single-exit CFG.
|
||||
expect(reaches(cfg, cfg.entryIndex, cfg.exitIndex)).toBe(true);
|
||||
|
|
@ -275,7 +283,9 @@ describe('C# CfgVisitor — try/catch/finally completion edges', () => {
|
|||
|
||||
describe('C# CfgVisitor — goto / labels', () => {
|
||||
it('backward goto wires to an already-seen label block', () => {
|
||||
const cfg = cs.cfgOf(`class C { void M() { int i = 0; top: work(); i++; if (i < 10) goto top; done(); } }`);
|
||||
const cfg = cs.cfgOf(
|
||||
`class C { void M() { int i = 0; top: work(); i++; if (i < 10) goto top; done(); } }`,
|
||||
);
|
||||
const gotoB = block(cfg, 'goto top;');
|
||||
const label = block(cfg, 'work();');
|
||||
expect(reaches(cfg, gotoB, label)).toBe(true);
|
||||
|
|
@ -382,7 +392,8 @@ describe('C# CfgVisitor — does not throw on exotic shapes', () => {
|
|||
// is registered, so these sites produce zero TAINTED edges; they only give the
|
||||
// deferred per-language source/sink model something to match against.
|
||||
describe('C# CfgVisitor — call-site sites[] substrate', () => {
|
||||
const cfgOf = (body: string): FunctionCfg => cs.cfgOf(`class C { void f(int cmd, int x, int a) { ${body} } }`);
|
||||
const cfgOf = (body: string): FunctionCfg =>
|
||||
cs.cfgOf(`class C { void f(int cmd, int x, int a) { ${body} } }`);
|
||||
|
||||
it('a bare invocation records a `call` site with callee name + arg occurrence', () => {
|
||||
const cfg = cfgOf(`Exec(cmd);`);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createGoCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/go.js';
|
||||
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -73,9 +73,7 @@ describe('Go CfgVisitor — structure', () => {
|
|||
|
||||
describe('Go CfgVisitor — if', () => {
|
||||
it('if with initializer: init+cond on the header, both arms reach the join', () => {
|
||||
const cfg = go.cfgOf(
|
||||
pkg(`func f() { if v := compute(); v > 0 { a() } else { b() }; c() }`),
|
||||
);
|
||||
const cfg = go.cfgOf(pkg(`func f() { if v := compute(); v > 0 { a() } else { b() }; c() }`));
|
||||
const kinds = edgeKinds(cfg);
|
||||
expect(kinds.has('cond-true')).toBe(true);
|
||||
expect(kinds.has('cond-false')).toBe(true);
|
||||
|
|
@ -254,7 +252,9 @@ describe('Go CfgVisitor — defer (LIFO completion legs)', () => {
|
|||
});
|
||||
|
||||
it('defer runs on a return path too (return threads through the defer)', () => {
|
||||
const cfg = go.cfgOf(pkg(`func f(x bool) int { defer cleanup(); if x { return 1 }; return 2 }`));
|
||||
const cfg = go.cfgOf(
|
||||
pkg(`func f(x bool) int { defer cleanup(); if x { return 1 }; return 2 }`),
|
||||
);
|
||||
const cleanup = block(cfg, 'defer cleanup()');
|
||||
expect(reaches(cfg, block(cfg, 'return 1'), cleanup)).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'return 2'), cleanup)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ describe('makeCfgHarness — language-parameterized CFG harness (#2195 U1)', ()
|
|||
};
|
||||
},
|
||||
};
|
||||
const code = ['function fd() { return 1; }', 'const a = () => 2;', 'const b = (x) => x + 1;'].join(
|
||||
'\n',
|
||||
);
|
||||
const code = [
|
||||
'function fd() { return 1; }',
|
||||
'const a = () => 2;',
|
||||
'const b = (x) => x + 1;',
|
||||
].join('\n');
|
||||
const cfgs = makeCfgHarness(TypeScript.typescript, stub, 'stub.ts').cfgsOf(code);
|
||||
expect(cfgs).toHaveLength(2); // the two arrows; the function_declaration is ignored
|
||||
expect(built).toEqual(['arrow_function', 'arrow_function']);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createJavaCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/java.js';
|
||||
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -143,9 +143,7 @@ describe('Java CfgVisitor — loops', () => {
|
|||
});
|
||||
|
||||
it('enhanced for (for-each): header + body + loop-back + exit; loop var is a def', () => {
|
||||
const cfg = java.cfgOf(
|
||||
`class C { void m(int[] xs) { for (int v : xs) { use(v); } done(); } }`,
|
||||
);
|
||||
const cfg = java.cfgOf(`class C { void m(int[] xs) { for (int v : xs) { use(v); } done(); } }`);
|
||||
const body = block(cfg, 'use(v);');
|
||||
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
|
||||
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
|
||||
|
|
@ -417,7 +415,9 @@ describe('Java CfgVisitor — synchronized (deterministic finalizer)', () => {
|
|||
});
|
||||
|
||||
it('a return inside synchronized crosses the release (finally-return)', () => {
|
||||
const cfg = java.cfgOf(`class C { int m(Object lock) { synchronized (lock) { return get(); } } }`);
|
||||
const cfg = java.cfgOf(
|
||||
`class C { int m(Object lock) { synchronized (lock) { return get(); } } }`,
|
||||
);
|
||||
expect(edgeKinds(cfg).has('finally-return')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -439,13 +439,17 @@ describe('Java CfgVisitor — def/use harvest', () => {
|
|||
});
|
||||
|
||||
it('a && (x = g()) records x as a may-def inside the short-circuit', () => {
|
||||
const cfg = java.cfgOf(`class C { void m(boolean a) { int x = 0; if (a && (x = g()) > 0) h(x); } }`);
|
||||
const cfg = java.cfgOf(
|
||||
`class C { void m(boolean a) { int x = 0; if (a && (x = g()) > 0) h(x); } }`,
|
||||
);
|
||||
const x = bindingIdx(cfg, 'x');
|
||||
expect(hasMayDef(cfg, x)).toBe(true);
|
||||
});
|
||||
|
||||
it('ternary arms record their writes as may-defs', () => {
|
||||
const cfg = java.cfgOf(`class C { void m(int a) { int x = 0; int y = a > 0 ? (x = 1) : (x = 2); use(y); } }`);
|
||||
const cfg = java.cfgOf(
|
||||
`class C { void m(int a) { int x = 0; int y = a > 0 ? (x = 1) : (x = 2); use(y); } }`,
|
||||
);
|
||||
const x = bindingIdx(cfg, 'x');
|
||||
expect(hasMayDef(cfg, x)).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createPhpCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/php.js';
|
||||
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -169,7 +169,9 @@ describe('PHP CfgVisitor — loops', () => {
|
|||
describe('PHP CfgVisitor — switch / match', () => {
|
||||
it('switch: C-style FALLTHROUGH (a case with no break flows to the next)', () => {
|
||||
const cfg = php.cfgOf(
|
||||
wrap(`switch ($x) { case 1: a(); break; case 2: b(); case 3: c(); break; default: d(); } e();`),
|
||||
wrap(
|
||||
`switch ($x) { case 1: a(); break; case 2: b(); case 3: c(); break; default: d(); } e();`,
|
||||
),
|
||||
);
|
||||
const kinds = edgeKinds(cfg);
|
||||
expect(kinds.has('switch-case')).toBe(true);
|
||||
|
|
@ -334,7 +336,9 @@ describe('PHP CfgVisitor — def/use harvest', () => {
|
|||
});
|
||||
|
||||
it('closure use ($b, &$c) captures bind in the closure body', () => {
|
||||
const cfgs = php.cfgsOf(`<?php function outer($b) { return function ($a) use ($b, &$c) { return $a + $b + $c; }; }`);
|
||||
const cfgs = php.cfgsOf(
|
||||
`<?php function outer($b) { return function ($a) use ($b, &$c) { return $a + $b + $c; }; }`,
|
||||
);
|
||||
const closure = cfgs.find((c) => (c.bindings ?? []).some((bd) => bd.name === '$a'));
|
||||
expect(closure).toBeDefined();
|
||||
expect((closure!.bindings ?? []).some((bd) => bd.name === '$b')).toBe(true);
|
||||
|
|
|
|||
|
|
@ -160,7 +160,9 @@ describe('Python CfgVisitor — for / while with the loop else', () => {
|
|||
});
|
||||
|
||||
it('continue re-tests the loop header', () => {
|
||||
const cfg = py.cfgOf(`def f(xs):\n for i in xs:\n if i:\n continue\n use(i)\n`);
|
||||
const cfg = py.cfgOf(
|
||||
`def f(xs):\n for i in xs:\n if i:\n continue\n use(i)\n`,
|
||||
);
|
||||
const header = cfg.blocks.find((b) => b.text.startsWith('for '))!.index;
|
||||
expect(edgeKinds(cfg).has('continue')).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'continue'), header)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -246,9 +246,7 @@ describe('Ruby CfgVisitor — begin / rescue / else / ensure', () => {
|
|||
});
|
||||
|
||||
it('method-level implicit begin: a bare def + rescue/ensure threads correctly', () => {
|
||||
const cfg = rb.cfgOf(
|
||||
`def f\n risky()\nrescue => e\n handle()\nensure\n done()\nend\n`,
|
||||
);
|
||||
const cfg = rb.cfgOf(`def f\n risky()\nrescue => e\n handle()\nensure\n done()\nend\n`);
|
||||
const ensureB = block(cfg, 'done()');
|
||||
expect(reaches(cfg, block(cfg, 'risky()'), block(cfg, 'handle()'))).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'handle()'), ensureB)).toBe(true);
|
||||
|
|
@ -275,9 +273,7 @@ describe('Ruby CfgVisitor — begin / rescue / else / ensure', () => {
|
|||
});
|
||||
|
||||
it('retry re-enters the begin protected body (loop-back)', () => {
|
||||
const cfg = rb.cfgOf(
|
||||
`def f\n begin\n risky()\n rescue\n retry\n end\nend\n`,
|
||||
);
|
||||
const cfg = rb.cfgOf(`def f\n begin\n risky()\n rescue\n retry\n end\nend\n`);
|
||||
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
|
||||
const retryB = block(cfg, 'retry');
|
||||
const bodyB = block(cfg, 'risky()');
|
||||
|
|
|
|||
|
|
@ -104,9 +104,7 @@ describe('Rust CfgVisitor — if / else / if let', () => {
|
|||
});
|
||||
|
||||
it('if let with a let-chain (let PAT = e && cond) still binds + branches', () => {
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f(opt: Option<i32>) { if let Some(n) = opt && n > 0 { both(); } }`,
|
||||
);
|
||||
const cfg = rust.cfgOf(`fn f(opt: Option<i32>) { if let Some(n) = opt && n > 0 { both(); } }`);
|
||||
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
|
||||
expect(hasDef(cfg, bindingIdx(cfg, 'n'))).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'both()'), cfg.exitIndex)).toBe(true);
|
||||
|
|
@ -221,9 +219,7 @@ describe('Rust CfgVisitor — match (no fallthrough) + guards', () => {
|
|||
});
|
||||
|
||||
it('a match with NO `_` arm keeps a no-match path to the exit', () => {
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f(x: i32) { match x { 1 => a(), 2 => b(), } after(); }`,
|
||||
);
|
||||
const cfg = rust.cfgOf(`fn f(x: i32) { match x { 1 => a(), 2 => b(), } after(); }`);
|
||||
expect(reaches(cfg, cfg.entryIndex, block(cfg, 'after()'))).toBe(true);
|
||||
expect(isExitReachableFromAllBlocks(cfg)).toBe(true);
|
||||
});
|
||||
|
|
@ -262,7 +258,7 @@ describe('Rust CfgVisitor — labeled break / continue', () => {
|
|||
expect(reaches(cfg, block(cfg, "continue 'outer"), outerHeader)).toBe(true);
|
||||
});
|
||||
|
||||
it("a plain (unlabeled) break exits the INNER loop only", () => {
|
||||
it('a plain (unlabeled) break exits the INNER loop only', () => {
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f() {
|
||||
for i in 0..10 {
|
||||
|
|
@ -281,9 +277,7 @@ describe('Rust CfgVisitor — labeled break / continue', () => {
|
|||
|
||||
describe('Rust CfgVisitor — return and the ? operator', () => {
|
||||
it('return Some(n) flows directly to EXIT (return edge)', () => {
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f(n: i32) -> Option<i32> { if n > 0 { return Some(n); } Some(0) }`,
|
||||
);
|
||||
const cfg = rust.cfgOf(`fn f(n: i32) -> Option<i32> { if n > 0 { return Some(n); } Some(0) }`);
|
||||
expect(edgeKinds(cfg).has('return')).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'return Some(n)'), cfg.exitIndex)).toBe(true);
|
||||
});
|
||||
|
|
@ -324,7 +318,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
|
|||
});
|
||||
|
||||
it('let Some(n) = opt() in a let_else defines n (the tuple-struct inner binds, not the path)', () => {
|
||||
const cfg = rust.cfgOf(`fn f(opt: Option<i32>) { let Some(n) = opt() else { return; }; use_n(n); }`);
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f(opt: Option<i32>) { let Some(n) = opt() else { return; }; use_n(n); }`,
|
||||
);
|
||||
expect(hasDef(cfg, bindingIdx(cfg, 'n'))).toBe(true);
|
||||
expect(hasUse(cfg, bindingIdx(cfg, 'n'))).toBe(true);
|
||||
// the let_else `else { return; }` jumps to EXIT.
|
||||
|
|
@ -339,7 +335,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
|
|||
});
|
||||
|
||||
it('plain assignment (x = p) defines x; a field write (obj.f = …) is NOT a scalar def', () => {
|
||||
const cfg = rust.cfgOf(`fn f(p: i32, obj: T) { let mut x = 0; x = p; obj.field = 1; use_x(x); }`);
|
||||
const cfg = rust.cfgOf(
|
||||
`fn f(p: i32, obj: T) { let mut x = 0; x = p; obj.field = 1; use_x(x); }`,
|
||||
);
|
||||
expect(hasDef(cfg, bindingIdx(cfg, 'x'))).toBe(true);
|
||||
// `obj` is used (its field is written), never a scalar def for `field`.
|
||||
expect(hasUse(cfg, bindingIdx(cfg, 'obj'))).toBe(true);
|
||||
|
|
@ -351,7 +349,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
|
|||
});
|
||||
|
||||
it('a closure body is opaque to the enclosing function harvest (own CFG)', () => {
|
||||
const cfgs = rust.cfgsOf(`fn f() { let cb = || { let inner = 1; use_inner(inner); }; call(cb); }`);
|
||||
const cfgs = rust.cfgsOf(
|
||||
`fn f() { let cb = || { let inner = 1; use_inner(inner); }; call(cb); }`,
|
||||
);
|
||||
// The OUTER fn has a `cb` binding but NOT `inner` (the closure body is opaque).
|
||||
const outer = cfgs.find((c) => (c.bindings ?? []).some((b) => b.name === 'cb'));
|
||||
expect(outer).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { requireVendoredGrammar } from '../../../src/core/tree-sitter/vendored-grammars.js';
|
||||
import { createSwiftCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/swift.js';
|
||||
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
|
||||
import {
|
||||
makeCfgHarness,
|
||||
type CfgHarness,
|
||||
|
|
@ -74,7 +73,9 @@ describe('Swift CfgVisitor — branching', () => {
|
|||
});
|
||||
|
||||
it('else-if chain branches each condition', () => {
|
||||
const cfg = swift.cfgOf(`func f(x: Int) { if x == 1 { a() } else if x == 2 { b() } else { c() } ; d() }`);
|
||||
const cfg = swift.cfgOf(
|
||||
`func f(x: Int) { if x == 1 { a() } else if x == 2 { b() } else { c() } ; d() }`,
|
||||
);
|
||||
const join = block(cfg, 'd()');
|
||||
expect(reaches(cfg, block(cfg, 'a()'), join)).toBe(true);
|
||||
expect(reaches(cfg, block(cfg, 'b()'), join)).toBe(true);
|
||||
|
|
@ -95,7 +96,9 @@ describe('Swift CfgVisitor — branching', () => {
|
|||
|
||||
describe('Swift CfgVisitor — guard', () => {
|
||||
it('guard let ... else { return }: the else DIVERGES, the body CONTINUES', () => {
|
||||
const cfg = swift.cfgOf(`func f(opt: Int?) -> Int { guard let y = opt else { return 0 } ; use(y) ; return y }`);
|
||||
const cfg = swift.cfgOf(
|
||||
`func f(opt: Int?) -> Int { guard let y = opt else { return 0 } ; use(y) ; return y }`,
|
||||
);
|
||||
const header = block(cfg, 'guard');
|
||||
const elseBlk = block(cfg, 'return 0');
|
||||
// The else is the cond-false (diverging) arm and returns.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue