gitnexus 1.6.8-rc.15 → 1.6.8-rc.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +18 -5
- package/dist/core/ingestion/cfg/cfg-builder.js +34 -5
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +80 -10
- package/dist/core/ingestion/cfg/control-flow-context.js +90 -26
- package/dist/core/ingestion/cfg/emit.d.ts +65 -0
- package/dist/core/ingestion/cfg/emit.js +217 -4
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +83 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +355 -0
- package/dist/core/ingestion/cfg/types.d.ts +76 -2
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +106 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +616 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +33 -16
- package/dist/core/ingestion/cfg/visitors/typescript.js +143 -51
- package/dist/core/ingestion/pipeline.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +1 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +3 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +31 -2
- package/dist/core/run-analyze.d.ts +4 -1
- package/dist/core/run-analyze.js +19 -3
- package/dist/mcp/local/local-backend.js +19 -1
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +8 -0
- package/package.json +1 -1
- package/web/assets/{agent-B0-_Ihgi.js → agent-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-PdDPNXTO.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-u4-0IIru.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV--R3bV0KH.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-CLDcI2Tc.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-dwefssU_.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-DMYhnaiQ.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CgWD3Go0.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-Bwu9Wi_f.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-CaxeST-3.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-WlblWt40.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CRrYkD90.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/{index-D_6rUFGW.js → index-Do8AE5yF.js} +47 -47
- package/web/assets/{infoDiagram-3YFTVSEB-pvT6ArHz.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-D_0fBKRi.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DGABptDC.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-r-bFdhPw.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-CS3o1Ug4.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-BMYGXwjH.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-BJnTiLwO.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-G35bS8CQ.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-BUaZpLs6.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-B9o1j771.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-Vz5Sin3H.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-D42Nu4nI.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-CSImZ1y9.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +1 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* hand-built block sequences, which is how the classic CFG hazards are pinned
|
|
13
13
|
* before the tree-sitter visitor (U2) drives it.
|
|
14
14
|
*/
|
|
15
|
-
import type { BasicBlockData, CfgEdgeKind, FunctionCfg } from './types.js';
|
|
15
|
+
import type { BasicBlockData, BindingEntry, CfgEdgeKind, FunctionCfg, StatementFacts } from './types.js';
|
|
16
16
|
export declare class CfgBuilder {
|
|
17
17
|
private readonly filePath;
|
|
18
18
|
private readonly functionStartLine;
|
|
@@ -32,17 +32,30 @@ export declare class CfgBuilder {
|
|
|
32
32
|
* Defaults to 0 for hand-built test CFGs that don't model columns. */
|
|
33
33
|
functionStartColumn?: number);
|
|
34
34
|
/** Create a block and return its index. */
|
|
35
|
-
newBlock(startLine: number, endLine: number, text: string, kind?: BasicBlockData['kind']): number;
|
|
35
|
+
newBlock(startLine: number, endLine: number, text: string, kind?: BasicBlockData['kind'], facts?: StatementFacts): number;
|
|
36
36
|
/** Add a single edge (idempotent on from+to+kind). */
|
|
37
37
|
edge(from: number, to: number, kind: CfgEdgeKind): void;
|
|
38
38
|
/** Wire a set of dangling exits to a single target block with one kind. */
|
|
39
39
|
connect(exits: readonly number[], to: number, kind?: CfgEdgeKind): void;
|
|
40
40
|
/** Extend a block's end line as more statements accrue to it. */
|
|
41
|
-
extendBlock(index: number, endLine: number, appendText?: string): void;
|
|
41
|
+
extendBlock(index: number, endLine: number, appendText?: string, facts?: StatementFacts): void;
|
|
42
|
+
/**
|
|
43
|
+
* Attach a facts-only statement record to a block WITHOUT touching its text
|
|
44
|
+
* or line span (#2082 M2 U1) — bench fingerprints and CFG snapshots include
|
|
45
|
+
* block text, so harvesting must never perturb it (ENTRY-block param defs
|
|
46
|
+
* are the canonical use; records that must precede a walked body get their
|
|
47
|
+
* own facts-only block instead, see the catch-param handling in visitTry).
|
|
48
|
+
*/
|
|
49
|
+
attachFacts(index: number, facts: StatementFacts): void;
|
|
42
50
|
get blockCount(): number;
|
|
43
51
|
/** Produce the serializable CFG. Caller is responsible for having wired the
|
|
44
|
-
* function's dangling exits to {@link exitIndex} before calling.
|
|
45
|
-
|
|
52
|
+
* function's dangling exits to {@link exitIndex} before calling.
|
|
53
|
+
*
|
|
54
|
+
* Pass `bindings` (the function's binding table, possibly empty) to emit
|
|
55
|
+
* statement facts (#2082 M2 U1) — every block then carries a `statements`
|
|
56
|
+
* array. Omit it (hand-built test CFGs, pre-M2 producers) and both fields
|
|
57
|
+
* are absent, which the reaching-defs solver reports as `no-facts`. */
|
|
58
|
+
finish(bindings?: readonly BindingEntry[]): FunctionCfg;
|
|
46
59
|
}
|
|
47
60
|
/**
|
|
48
61
|
* Block indices reachable from `entryIndex` by following edges. Backs the
|
|
@@ -21,8 +21,14 @@ export class CfgBuilder {
|
|
|
21
21
|
this.exitIndex = this.newBlock(functionEndLine, functionEndLine, '', 'exit');
|
|
22
22
|
}
|
|
23
23
|
/** Create a block and return its index. */
|
|
24
|
-
newBlock(startLine, endLine, text, kind = 'normal') {
|
|
25
|
-
this.blocks.push({
|
|
24
|
+
newBlock(startLine, endLine, text, kind = 'normal', facts) {
|
|
25
|
+
this.blocks.push({
|
|
26
|
+
startLine,
|
|
27
|
+
endLine,
|
|
28
|
+
textParts: text ? [text] : [],
|
|
29
|
+
kind,
|
|
30
|
+
statements: facts ? [facts] : [],
|
|
31
|
+
});
|
|
26
32
|
return this.blocks.length - 1;
|
|
27
33
|
}
|
|
28
34
|
/** Add a single edge (idempotent on from+to+kind). */
|
|
@@ -39,7 +45,7 @@ export class CfgBuilder {
|
|
|
39
45
|
this.edge(from, to, kind);
|
|
40
46
|
}
|
|
41
47
|
/** Extend a block's end line as more statements accrue to it. */
|
|
42
|
-
extendBlock(index, endLine, appendText) {
|
|
48
|
+
extendBlock(index, endLine, appendText, facts) {
|
|
43
49
|
const b = this.blocks[index];
|
|
44
50
|
if (!b)
|
|
45
51
|
return;
|
|
@@ -47,13 +53,34 @@ export class CfgBuilder {
|
|
|
47
53
|
b.endLine = endLine;
|
|
48
54
|
if (appendText)
|
|
49
55
|
b.textParts.push(appendText);
|
|
56
|
+
if (facts)
|
|
57
|
+
b.statements.push(facts);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Attach a facts-only statement record to a block WITHOUT touching its text
|
|
61
|
+
* or line span (#2082 M2 U1) — bench fingerprints and CFG snapshots include
|
|
62
|
+
* block text, so harvesting must never perturb it (ENTRY-block param defs
|
|
63
|
+
* are the canonical use; records that must precede a walked body get their
|
|
64
|
+
* own facts-only block instead, see the catch-param handling in visitTry).
|
|
65
|
+
*/
|
|
66
|
+
attachFacts(index, facts) {
|
|
67
|
+
const b = this.blocks[index];
|
|
68
|
+
if (!b)
|
|
69
|
+
return;
|
|
70
|
+
b.statements.push(facts);
|
|
50
71
|
}
|
|
51
72
|
get blockCount() {
|
|
52
73
|
return this.blocks.length;
|
|
53
74
|
}
|
|
54
75
|
/** Produce the serializable CFG. Caller is responsible for having wired the
|
|
55
|
-
* function's dangling exits to {@link exitIndex} before calling.
|
|
56
|
-
|
|
76
|
+
* function's dangling exits to {@link exitIndex} before calling.
|
|
77
|
+
*
|
|
78
|
+
* Pass `bindings` (the function's binding table, possibly empty) to emit
|
|
79
|
+
* statement facts (#2082 M2 U1) — every block then carries a `statements`
|
|
80
|
+
* array. Omit it (hand-built test CFGs, pre-M2 producers) and both fields
|
|
81
|
+
* are absent, which the reaching-defs solver reports as `no-facts`. */
|
|
82
|
+
finish(bindings) {
|
|
83
|
+
const withFacts = bindings !== undefined;
|
|
57
84
|
return {
|
|
58
85
|
filePath: this.filePath,
|
|
59
86
|
functionStartLine: this.functionStartLine,
|
|
@@ -67,8 +94,10 @@ export class CfgBuilder {
|
|
|
67
94
|
endLine: b.endLine,
|
|
68
95
|
text: b.textParts.join('\n'),
|
|
69
96
|
kind: b.kind,
|
|
97
|
+
...(withFacts ? { statements: b.statements } : {}),
|
|
70
98
|
})),
|
|
71
99
|
edges: [...this.edges],
|
|
100
|
+
...(withFacts ? { bindings } : {}),
|
|
72
101
|
};
|
|
73
102
|
}
|
|
74
103
|
}
|
|
@@ -1,27 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ControlFlowContext (issue #2081
|
|
2
|
+
* ControlFlowContext (issue #2081 M1; finalizer frames added by #2082 M2 U2).
|
|
3
3
|
*
|
|
4
4
|
* Resolves the targets of `break`/`continue` (plain and labeled) as the visitor
|
|
5
5
|
* descends through loops and switches. Loops and switches push a target frame
|
|
6
6
|
* on entry and pop it on exit; a labeled statement attaches its label to the
|
|
7
7
|
* frame of the construct it labels, so `break outer` / `continue outer` resolve
|
|
8
8
|
* against the right enclosing loop/switch rather than the nearest one.
|
|
9
|
+
*
|
|
10
|
+
* M2 adds FINALIZER frames, interleaved on the SAME stack as loop/switch frames
|
|
11
|
+
* — interleaving is load-bearing: a jump must route through exactly the
|
|
12
|
+
* `finally` bodies lexically BETWEEN it and its target (target-relative
|
|
13
|
+
* threading). A `break` whose loop lives entirely inside the `try` crosses no
|
|
14
|
+
* finally and must keep its direct edge; re-routing it anyway would force the
|
|
15
|
+
* only path to the in-try continuation through the finally, letting a finally
|
|
16
|
+
* redefinition falsely KILL in-loop definitions for the downstream
|
|
17
|
+
* reaching-defs pass (a taint false negative). A parallel stack cannot express
|
|
18
|
+
* that between-ness, which is why the frames live here.
|
|
9
19
|
*/
|
|
20
|
+
import type { CfgBuilder } from './cfg-builder.js';
|
|
21
|
+
import type { CfgEdgeKind } from './types.js';
|
|
22
|
+
/** A `finally` whose body any crossing jump must route through. */
|
|
23
|
+
export interface FinalizerFrame {
|
|
24
|
+
readonly kind: 'finalizer';
|
|
25
|
+
/** Entry block of the finally body. */
|
|
26
|
+
readonly entry: number;
|
|
27
|
+
/**
|
|
28
|
+
* Completion legs registered by jumps that crossed this finally: once the
|
|
29
|
+
* owning try pops the frame, it wires `finally-exits → to` with `kind` for
|
|
30
|
+
* each entry. Mutated by the jump handlers via {@link ControlFlowContext}.
|
|
31
|
+
*/
|
|
32
|
+
readonly pending: {
|
|
33
|
+
to: number;
|
|
34
|
+
kind: CfgEdgeKind;
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
/** A resolved jump: its ultimate target + the finallys it crosses (inner→outer). */
|
|
38
|
+
export interface JumpResolution {
|
|
39
|
+
readonly target: number;
|
|
40
|
+
readonly finalizers: readonly FinalizerFrame[];
|
|
41
|
+
}
|
|
10
42
|
export declare class ControlFlowContext {
|
|
11
43
|
private readonly stack;
|
|
12
|
-
pushLoop(continueTo: number, breakTo: number,
|
|
13
|
-
pushSwitch(breakTo: number,
|
|
44
|
+
pushLoop(continueTo: number, breakTo: number, labels?: readonly string[]): void;
|
|
45
|
+
pushSwitch(breakTo: number, labels?: readonly string[]): void;
|
|
46
|
+
/** Push a labeled non-loop statement's break-target frame. */
|
|
47
|
+
pushLabeledBlock(breakTo: number, labels: readonly string[]): void;
|
|
48
|
+
/**
|
|
49
|
+
* Push a finalizer frame and return it — the owning `visitTry` keeps the
|
|
50
|
+
* reference to wire {@link FinalizerFrame.pending} after popping it.
|
|
51
|
+
*/
|
|
52
|
+
pushFinalizer(entry: number): FinalizerFrame;
|
|
14
53
|
pop(): void;
|
|
15
54
|
/**
|
|
16
|
-
*
|
|
17
|
-
* carrying that label
|
|
18
|
-
*
|
|
55
|
+
* Resolve a `break`: the nearest enclosing loop/switch frame (or, with a
|
|
56
|
+
* label, the nearest frame carrying that label) plus every finalizer frame
|
|
57
|
+
* stacked ABOVE it — i.e. exactly the finallys the jump crosses, innermost
|
|
58
|
+
* first. Returns `undefined` if there is no valid target (malformed input or
|
|
59
|
+
* an unmodeled label) — the caller falls back to its conservative routing and
|
|
60
|
+
* threads nothing.
|
|
19
61
|
*/
|
|
20
|
-
|
|
62
|
+
resolveBreak(label?: string): JumpResolution | undefined;
|
|
63
|
+
/** Resolve a `continue`: like {@link resolveBreak} but only loop frames match. */
|
|
64
|
+
resolveContinue(label?: string): JumpResolution | undefined;
|
|
65
|
+
/** Every active finalizer, innermost first — what a `return` must cross. */
|
|
66
|
+
finalizersForReturn(): readonly FinalizerFrame[];
|
|
21
67
|
/**
|
|
22
|
-
* Target block for a `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
68
|
+
* Target block for a `break` (no finalizer info) — see {@link resolveBreak}.
|
|
69
|
+
* Prefer `resolveBreak` + {@link wireJumpThroughFinalizers} in visitors: a
|
|
70
|
+
* target-only lookup silently loses finalizer threading (the M2 soundness
|
|
71
|
+
* fix). Kept for target-shape assertions in tests.
|
|
25
72
|
*/
|
|
73
|
+
breakTarget(label?: string): number | undefined;
|
|
74
|
+
/** Target block for a `continue` — same caveat as {@link breakTarget}. */
|
|
26
75
|
continueTarget(label?: string): number | undefined;
|
|
76
|
+
private resolve;
|
|
27
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Wire a jump from `from` to `target`, routing through the finallys it
|
|
80
|
+
* crosses (innermost first). The first leg keeps the bare jump `kind`
|
|
81
|
+
* (preserving the "kind ⟹ source-block terminator" invariant in types.ts);
|
|
82
|
+
* each finally's completion leg is registered as pending on its frame with the
|
|
83
|
+
* matching `finally-*` kind and wired by the owning try via
|
|
84
|
+
* {@link drainFinalizerPending} once the finally's exits are known.
|
|
85
|
+
*
|
|
86
|
+
* Language-agnostic on purpose (#2082 M2): the threading protocol encodes
|
|
87
|
+
* three subtle invariants every future language visitor needs identically —
|
|
88
|
+
* keeping it here means a new visitor cannot drift on any of them.
|
|
89
|
+
*/
|
|
90
|
+
export declare function wireJumpThroughFinalizers(builder: CfgBuilder, from: number, finalizers: readonly FinalizerFrame[], target: number, kind: 'return' | 'break' | 'continue'): void;
|
|
91
|
+
/**
|
|
92
|
+
* Wire a popped finalizer frame's pending completion legs from the finally's
|
|
93
|
+
* exit blocks. A finally that itself always jumps (`finally { return 2; }`)
|
|
94
|
+
* has no exits — its pending legs wire nowhere, matching JS's
|
|
95
|
+
* finally-override semantics.
|
|
96
|
+
*/
|
|
97
|
+
export declare function drainFinalizerPending(builder: CfgBuilder, frame: FinalizerFrame, finallyExits: readonly number[]): void;
|
|
@@ -1,49 +1,113 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ControlFlowContext (issue #2081, M1).
|
|
3
|
-
*
|
|
4
|
-
* Resolves the targets of `break`/`continue` (plain and labeled) as the visitor
|
|
5
|
-
* descends through loops and switches. Loops and switches push a target frame
|
|
6
|
-
* on entry and pop it on exit; a labeled statement attaches its label to the
|
|
7
|
-
* frame of the construct it labels, so `break outer` / `continue outer` resolve
|
|
8
|
-
* against the right enclosing loop/switch rather than the nearest one.
|
|
9
|
-
*/
|
|
10
1
|
export class ControlFlowContext {
|
|
11
2
|
stack = [];
|
|
12
|
-
pushLoop(continueTo, breakTo,
|
|
13
|
-
this.stack.push({ kind: 'loop', continueTo, breakTo,
|
|
3
|
+
pushLoop(continueTo, breakTo, labels = []) {
|
|
4
|
+
this.stack.push({ kind: 'loop', continueTo, breakTo, labels });
|
|
14
5
|
}
|
|
15
|
-
pushSwitch(breakTo,
|
|
16
|
-
this.stack.push({ kind: 'switch', breakTo,
|
|
6
|
+
pushSwitch(breakTo, labels = []) {
|
|
7
|
+
this.stack.push({ kind: 'switch', breakTo, labels });
|
|
8
|
+
}
|
|
9
|
+
/** Push a labeled non-loop statement's break-target frame. */
|
|
10
|
+
pushLabeledBlock(breakTo, labels) {
|
|
11
|
+
this.stack.push({ kind: 'block', breakTo, labels });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Push a finalizer frame and return it — the owning `visitTry` keeps the
|
|
15
|
+
* reference to wire {@link FinalizerFrame.pending} after popping it.
|
|
16
|
+
*/
|
|
17
|
+
pushFinalizer(entry) {
|
|
18
|
+
const frame = { kind: 'finalizer', entry, pending: [] };
|
|
19
|
+
this.stack.push(frame);
|
|
20
|
+
return frame;
|
|
17
21
|
}
|
|
18
22
|
pop() {
|
|
19
23
|
this.stack.pop();
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
|
-
*
|
|
23
|
-
* carrying that label
|
|
24
|
-
*
|
|
26
|
+
* Resolve a `break`: the nearest enclosing loop/switch frame (or, with a
|
|
27
|
+
* label, the nearest frame carrying that label) plus every finalizer frame
|
|
28
|
+
* stacked ABOVE it — i.e. exactly the finallys the jump crosses, innermost
|
|
29
|
+
* first. Returns `undefined` if there is no valid target (malformed input or
|
|
30
|
+
* an unmodeled label) — the caller falls back to its conservative routing and
|
|
31
|
+
* threads nothing.
|
|
25
32
|
*/
|
|
26
|
-
|
|
33
|
+
resolveBreak(label) {
|
|
34
|
+
return this.resolve((f) => label === undefined
|
|
35
|
+
? f.kind !== 'block' // an unlabeled break never targets a labeled block
|
|
36
|
+
: f.labels.includes(label));
|
|
37
|
+
}
|
|
38
|
+
/** Resolve a `continue`: like {@link resolveBreak} but only loop frames match. */
|
|
39
|
+
resolveContinue(label) {
|
|
40
|
+
return this.resolve((f) => f.kind === 'loop' && (label === undefined || f.labels.includes(label)), (f) => f.continueTo);
|
|
41
|
+
}
|
|
42
|
+
/** Every active finalizer, innermost first — what a `return` must cross. */
|
|
43
|
+
finalizersForReturn() {
|
|
44
|
+
const fins = [];
|
|
27
45
|
for (let i = this.stack.length - 1; i >= 0; i--) {
|
|
28
46
|
const f = this.stack[i];
|
|
29
|
-
if (
|
|
30
|
-
|
|
47
|
+
if (f.kind === 'finalizer')
|
|
48
|
+
fins.push(f);
|
|
31
49
|
}
|
|
32
|
-
return
|
|
50
|
+
return fins;
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
|
-
* Target block for a `
|
|
36
|
-
*
|
|
37
|
-
*
|
|
53
|
+
* Target block for a `break` (no finalizer info) — see {@link resolveBreak}.
|
|
54
|
+
* Prefer `resolveBreak` + {@link wireJumpThroughFinalizers} in visitors: a
|
|
55
|
+
* target-only lookup silently loses finalizer threading (the M2 soundness
|
|
56
|
+
* fix). Kept for target-shape assertions in tests.
|
|
38
57
|
*/
|
|
58
|
+
breakTarget(label) {
|
|
59
|
+
return this.resolveBreak(label)?.target;
|
|
60
|
+
}
|
|
61
|
+
/** Target block for a `continue` — same caveat as {@link breakTarget}. */
|
|
39
62
|
continueTarget(label) {
|
|
63
|
+
return this.resolveContinue(label)?.target;
|
|
64
|
+
}
|
|
65
|
+
resolve(matches, targetOf = (f) => f.breakTo) {
|
|
66
|
+
const crossed = [];
|
|
40
67
|
for (let i = this.stack.length - 1; i >= 0; i--) {
|
|
41
68
|
const f = this.stack[i];
|
|
42
|
-
if (f.kind
|
|
69
|
+
if (f.kind === 'finalizer') {
|
|
70
|
+
crossed.push(f);
|
|
43
71
|
continue;
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
}
|
|
73
|
+
if (matches(f))
|
|
74
|
+
return { target: targetOf(f), finalizers: crossed };
|
|
46
75
|
}
|
|
47
76
|
return undefined;
|
|
48
77
|
}
|
|
49
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Wire a jump from `from` to `target`, routing through the finallys it
|
|
81
|
+
* crosses (innermost first). The first leg keeps the bare jump `kind`
|
|
82
|
+
* (preserving the "kind ⟹ source-block terminator" invariant in types.ts);
|
|
83
|
+
* each finally's completion leg is registered as pending on its frame with the
|
|
84
|
+
* matching `finally-*` kind and wired by the owning try via
|
|
85
|
+
* {@link drainFinalizerPending} once the finally's exits are known.
|
|
86
|
+
*
|
|
87
|
+
* Language-agnostic on purpose (#2082 M2): the threading protocol encodes
|
|
88
|
+
* three subtle invariants every future language visitor needs identically —
|
|
89
|
+
* keeping it here means a new visitor cannot drift on any of them.
|
|
90
|
+
*/
|
|
91
|
+
export function wireJumpThroughFinalizers(builder, from, finalizers, target, kind) {
|
|
92
|
+
if (finalizers.length === 0) {
|
|
93
|
+
builder.edge(from, target, kind);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const completionKind = `finally-${kind}`;
|
|
97
|
+
builder.edge(from, finalizers[0].entry, kind);
|
|
98
|
+
for (let i = 0; i < finalizers.length; i++) {
|
|
99
|
+
const to = i + 1 < finalizers.length ? finalizers[i + 1].entry : target;
|
|
100
|
+
finalizers[i].pending.push({ to, kind: completionKind });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wire a popped finalizer frame's pending completion legs from the finally's
|
|
105
|
+
* exit blocks. A finally that itself always jumps (`finally { return 2; }`)
|
|
106
|
+
* has no exits — its pending legs wire nowhere, matching JS's
|
|
107
|
+
* finally-override semantics.
|
|
108
|
+
*/
|
|
109
|
+
export function drainFinalizerPending(builder, frame, finallyExits) {
|
|
110
|
+
for (const p of frame.pending) {
|
|
111
|
+
builder.connect(finallyExits, p.to, p.kind);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -28,6 +28,26 @@ import type { FunctionCfg } from './types.js';
|
|
|
28
28
|
* default.
|
|
29
29
|
*/
|
|
30
30
|
export declare const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
|
|
31
|
+
/**
|
|
32
|
+
* Default per-function REACHING_DEF edge cap (#2082 M2 KTD9). 4000 mirrors
|
|
33
|
+
* Joern's per-method `maxNumberOfDefinitions` — the closest production prior
|
|
34
|
+
* art — but truncates-and-warns instead of silently skipping the function.
|
|
35
|
+
* Counts (defBlock, useBlock, binding) DEDUPED edges, not statement-level
|
|
36
|
+
* facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
|
|
39
|
+
/**
|
|
40
|
+
* Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
|
|
41
|
+
* O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
|
|
42
|
+
* GRAPH, not the per-function memory spike of materializing facts before
|
|
43
|
+
* dedup. {@link emitFileReachingDefs} hands `edgeCap × this` to
|
|
44
|
+
* `computeReachingDefs` as `maxFacts` (unlimited when the edge cap is 0) —
|
|
45
|
+
* single source of truth; the DEFAULT constant below is derived, never the
|
|
46
|
+
* mechanism.
|
|
47
|
+
*/
|
|
48
|
+
export declare const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
|
|
49
|
+
/** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
|
|
50
|
+
export declare const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION: number;
|
|
31
51
|
export interface CfgEmitResult {
|
|
32
52
|
blocks: number;
|
|
33
53
|
edges: number;
|
|
@@ -53,6 +73,17 @@ export interface CfgEmitResult {
|
|
|
53
73
|
* join this check).
|
|
54
74
|
*/
|
|
55
75
|
export declare const isEmitSafeCfg: (cfg: FunctionCfg | undefined | null) => cfg is FunctionCfg;
|
|
76
|
+
/**
|
|
77
|
+
* Whether a structurally-valid CFG's M2 statement facts are safe to feed to
|
|
78
|
+
* the reaching-defs solver + REACHING_DEF id templating (#2082 U1/U4): the
|
|
79
|
+
* binding table's name/declLine/declColumn template into edge ids, and
|
|
80
|
+
* statement def/use indices must stay IN RANGE of the table (an escaping
|
|
81
|
+
* index would fabricate `undefined`-keyed ids). Deliberately SEPARATE from
|
|
82
|
+
* {@link isEmitSafeCfg}: malformed facts must cost only the function's
|
|
83
|
+
* REACHING_DEF projection — degrading to M1 behavior (CFG emitted, no facts)
|
|
84
|
+
* — never the BasicBlock/CFG layer itself.
|
|
85
|
+
*/
|
|
86
|
+
export declare const hasEmitSafeFacts: (cfg: FunctionCfg) => boolean;
|
|
56
87
|
/**
|
|
57
88
|
* Emit BasicBlock nodes + CFG edges for every function CFG in `cfgs`.
|
|
58
89
|
*
|
|
@@ -62,3 +93,37 @@ export declare const isEmitSafeCfg: (cfg: FunctionCfg | undefined | null) => cfg
|
|
|
62
93
|
* count is bounded by the function's statement count); only edges are capped.
|
|
63
94
|
*/
|
|
64
95
|
export declare function emitFileCfgs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): CfgEmitResult;
|
|
96
|
+
export interface ReachingDefEmitResult {
|
|
97
|
+
/** Deduped (defBlock, useBlock, binding) edges persisted. */
|
|
98
|
+
edges: number;
|
|
99
|
+
/** Deduped edges dropped by the per-function edge cap. */
|
|
100
|
+
droppedEdges: number;
|
|
101
|
+
cappedFunctions: number;
|
|
102
|
+
/** Functions whose FACT materialization hit the solver's maxFacts limit. */
|
|
103
|
+
truncatedFunctions: number;
|
|
104
|
+
/** Functions whose facts failed {@link hasEmitSafeFacts} (CFG kept, facts skipped). */
|
|
105
|
+
malformedFactFunctions: number;
|
|
106
|
+
/** Total statement-level facts the solver produced (pre-dedup telemetry). */
|
|
107
|
+
facts: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compute reaching definitions per function and persist the bounded
|
|
111
|
+
* REACHING_DEF projection (#2082 M2 U4).
|
|
112
|
+
*
|
|
113
|
+
* Facts are DEDUPED to (defBlock, useBlock, binding) before budgeting — the
|
|
114
|
+
* persisted columns (`from,to,type,confidence,reason,step`; relationship ids
|
|
115
|
+
* are in-memory-only, the CodeRelation table has no id column) cannot
|
|
116
|
+
* distinguish finer rows, so statement-indexed ids would only manufacture
|
|
117
|
+
* byte-identical duplicate rows that burn budget. Statement granularity lives
|
|
118
|
+
* in the in-memory {@link computeReachingDefs} result, which the M3 taint
|
|
119
|
+
* engine recomputes on demand — the budget here governs only this projection
|
|
120
|
+
* and can never drop a taint fact.
|
|
121
|
+
*
|
|
122
|
+
* R7 (no silent truncation) covers BOTH layers: the per-function edge cap AND
|
|
123
|
+
* the solver's fact-materialization limit (which can fire without the edge
|
|
124
|
+
* cap ever being reached, since dedup is many-to-one) each produce one
|
|
125
|
+
* unconditional `onWarn`. The edge-cap warn names the top bindings by fact
|
|
126
|
+
* count — overflow is almost always one variable, which is exactly the datum
|
|
127
|
+
* M3 tuning wants.
|
|
128
|
+
*/
|
|
129
|
+
export declare function emitFileReachingDefs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): ReachingDefEmitResult;
|