gitnexus 1.6.8-rc.2 → 1.6.8-rc.21
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/README.md +20 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.js +1 -0
- package/dist/cli/analyze-config.js +40 -0
- package/dist/cli/analyze.d.ts +22 -0
- package/dist/cli/analyze.js +36 -6
- package/dist/cli/clean.d.ts +1 -0
- package/dist/cli/clean.js +43 -1
- package/dist/cli/eval-server.js +43 -0
- package/dist/cli/help-i18n.js +11 -0
- package/dist/cli/i18n/en.d.ts +13 -0
- package/dist/cli/i18n/en.js +13 -0
- package/dist/cli/i18n/resources.d.ts +26 -0
- package/dist/cli/i18n/zh-CN.d.ts +13 -0
- package/dist/cli/i18n/zh-CN.js +13 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/list.js +12 -0
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/skill-gen.js +1 -0
- package/dist/cli/status.js +26 -5
- package/dist/cli/tool.d.ts +12 -1
- package/dist/cli/tool.js +41 -1
- package/dist/core/embeddings/embedder.js +4 -0
- package/dist/core/embeddings/embedding-pipeline.js +27 -16
- package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/java.js +3 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/include-extractor.js +7 -7
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
- package/dist/core/ingestion/cfg/collect.d.ts +30 -0
- package/dist/core/ingestion/cfg/collect.js +34 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
- package/dist/core/ingestion/cfg/emit.d.ts +144 -0
- package/dist/core/ingestion/cfg/emit.js +315 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +231 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +5 -1
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.js +22 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +57 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/emit.d.ts +124 -0
- package/dist/core/ingestion/taint/emit.js +204 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +664 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
- package/dist/core/ingestion/taint/source-sink-config.js +11 -11
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +70 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +33 -16
- package/dist/core/lbug/lbug-adapter.d.ts +19 -0
- package/dist/core/lbug/lbug-adapter.js +56 -1
- package/dist/core/run-analyze.d.ts +87 -0
- package/dist/core/run-analyze.js +280 -25
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/mcp/core/embedder.js +4 -0
- package/dist/mcp/local/local-backend.d.ts +86 -2
- package/dist/mcp/local/local-backend.js +657 -27
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +109 -0
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -1
- package/dist/server/api.js +46 -140
- package/dist/server/git-clone.d.ts +1 -0
- package/dist/server/git-clone.js +1 -1
- package/dist/server/middleware.d.ts +11 -0
- package/dist/server/middleware.js +27 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +31 -0
- package/dist/server/upload-paths.js +51 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/branch-index.d.ts +52 -0
- package/dist/storage/branch-index.js +65 -0
- package/dist/storage/git.d.ts +11 -0
- package/dist/storage/git.js +28 -0
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +32 -10
- package/dist/storage/repo-manager.d.ts +122 -10
- package/dist/storage/repo-manager.js +162 -21
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
- package/hooks/claude/gitnexus-hook.cjs +36 -8
- package/hooks/claude/hook-db-lock-probe.cjs +123 -2
- package/package.json +4 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/skills/gitnexus-guide.md +11 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/index-COMMmbxW.css +2 -0
- package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
- package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +2 -2
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/index-BKWA-m7o.css +0 -2
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { generateId } from '../../../lib/utils.js';
|
|
2
|
+
import { computeReachingDefs } from './reaching-defs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default per-function CFG edge cap. A pathological generated function could
|
|
5
|
+
* otherwise emit an unbounded edge set; the cap bounds graph growth and is
|
|
6
|
+
* overridable via `--pdg` options. `0` (in options) means no cap (unlimited
|
|
7
|
+
* — see the `cap` mapping in {@link emitFileCfgs}); `undefined` means this
|
|
8
|
+
* default.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
|
|
11
|
+
/**
|
|
12
|
+
* Default per-function REACHING_DEF edge cap (#2082 M2 KTD9). 4000 mirrors
|
|
13
|
+
* Joern's per-method `maxNumberOfDefinitions` — the closest production prior
|
|
14
|
+
* art — but truncates-and-warns instead of silently skipping the function.
|
|
15
|
+
* Counts (defBlock, useBlock, binding) DEDUPED edges, not statement-level
|
|
16
|
+
* facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
|
|
19
|
+
/**
|
|
20
|
+
* Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
|
|
21
|
+
* O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
|
|
22
|
+
* GRAPH, not the per-function memory spike of materializing facts before
|
|
23
|
+
* dedup. {@link emitFileReachingDefs} hands `edgeCap × this` to
|
|
24
|
+
* `computeReachingDefs` as `maxFacts` (unlimited when the edge cap is 0) —
|
|
25
|
+
* single source of truth; the DEFAULT constant below is derived, never the
|
|
26
|
+
* mechanism.
|
|
27
|
+
*/
|
|
28
|
+
export const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
|
|
29
|
+
/** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
|
|
30
|
+
export const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION = REACHING_DEF_FACTS_PER_EDGE_CAP * DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
|
|
31
|
+
/**
|
|
32
|
+
* The single BasicBlock id template (module doc). Exported for the M3 taint
|
|
33
|
+
* emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
|
|
34
|
+
* SAME persisted block nodes — a re-derived copy of this template would
|
|
35
|
+
* silently dangle the moment either drifted.
|
|
36
|
+
*/
|
|
37
|
+
export const basicBlockId = (filePath, functionStartLine, functionStartColumn, blockIndex) => `BasicBlock:${filePath}:${functionStartLine}:${functionStartColumn}:${blockIndex}`;
|
|
38
|
+
/**
|
|
39
|
+
* Whether an untrusted `cfgSideChannel` element is safe to feed to
|
|
40
|
+
* {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
|
|
41
|
+
* checks exactly the fields whose corruption is SILENT given emit's
|
|
42
|
+
* mechanics: {@link basicBlockId} string-templates every id-anchor value
|
|
43
|
+
* (filePath, function start line/column, block index, edge endpoints) and
|
|
44
|
+
* the graph's addNode/addRelationship are no-throw Map inserts. Unchecked,
|
|
45
|
+
* a missing anchor field cross-wires same-`undefined`-id blocks across
|
|
46
|
+
* functions (addNode is first-writer-wins), and an edge endpoint that
|
|
47
|
+
* matches no block index becomes a dangling `BasicBlock:…:<n>` edge that
|
|
48
|
+
* detonates much later at DB bulk-load instead of throwing here — so
|
|
49
|
+
* endpoints are checked for MEMBERSHIP in the block-index set, not just
|
|
50
|
+
* integer-ness. Lives in this module so the guard evolves with the id
|
|
51
|
+
* templating it defends (#2099 F4; M2 fields that join the id path must
|
|
52
|
+
* join this check).
|
|
53
|
+
*/
|
|
54
|
+
export const isEmitSafeCfg = (cfg) => {
|
|
55
|
+
if (typeof cfg?.filePath !== 'string' ||
|
|
56
|
+
!Number.isInteger(cfg.functionStartLine) ||
|
|
57
|
+
!Number.isInteger(cfg.functionStartColumn) ||
|
|
58
|
+
!Array.isArray(cfg.blocks) ||
|
|
59
|
+
!Array.isArray(cfg.edges)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// Contiguity (index === position), not just integer-ness: every consumer —
|
|
63
|
+
// this module's id templating AND the reaching-defs solver's
|
|
64
|
+
// position-indexed adjacency arrays — assumes blocks[i].index === i. A
|
|
65
|
+
// membership-only check would admit a compacted channel ({index:0},{index:5})
|
|
66
|
+
// whose edge 0→5 passes membership but indexes past the arrays downstream.
|
|
67
|
+
for (let i = 0; i < cfg.blocks.length; i++) {
|
|
68
|
+
if (cfg.blocks[i]?.index !== i)
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const n = cfg.blocks.length;
|
|
72
|
+
// entry/exit must land on real blocks — the solver feeds entryIndex straight
|
|
73
|
+
// into its RPO walk, where an out-of-range index throws and (worse than this
|
|
74
|
+
// one element) costs the whole FILE's REACHING_DEF pass (tri-review P3).
|
|
75
|
+
if (!Number.isInteger(cfg.entryIndex) ||
|
|
76
|
+
cfg.entryIndex < 0 ||
|
|
77
|
+
cfg.entryIndex >= n ||
|
|
78
|
+
!Number.isInteger(cfg.exitIndex) ||
|
|
79
|
+
cfg.exitIndex < 0 ||
|
|
80
|
+
cfg.exitIndex >= n) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return cfg.edges.every((e) => Number.isInteger(e?.from) &&
|
|
84
|
+
Number.isInteger(e?.to) &&
|
|
85
|
+
e.from >= 0 &&
|
|
86
|
+
e.from < n &&
|
|
87
|
+
e.to >= 0 &&
|
|
88
|
+
e.to < n);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Whether a structurally-valid CFG's M2 statement facts are safe to feed to
|
|
92
|
+
* the reaching-defs solver + REACHING_DEF id templating (#2082 U1/U4): the
|
|
93
|
+
* binding table's name/declLine/declColumn template into edge ids, and
|
|
94
|
+
* statement def/use indices must stay IN RANGE of the table (an escaping
|
|
95
|
+
* index would fabricate `undefined`-keyed ids). Deliberately SEPARATE from
|
|
96
|
+
* {@link isEmitSafeCfg}: malformed facts must cost only the function's
|
|
97
|
+
* REACHING_DEF projection — degrading to M1 behavior (CFG emitted, no facts)
|
|
98
|
+
* — never the BasicBlock/CFG layer itself.
|
|
99
|
+
*/
|
|
100
|
+
export const hasEmitSafeFacts = (cfg) => {
|
|
101
|
+
const bindings = cfg.bindings;
|
|
102
|
+
if (bindings === undefined) {
|
|
103
|
+
// Pre-M2 channel — statements must be absent too.
|
|
104
|
+
return cfg.blocks.every((b) => b.statements === undefined);
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(bindings))
|
|
107
|
+
return false;
|
|
108
|
+
for (const b of bindings) {
|
|
109
|
+
if (typeof b?.name !== 'string' ||
|
|
110
|
+
!Number.isInteger(b.declLine) ||
|
|
111
|
+
!Number.isInteger(b.declColumn)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const bindingCount = bindings.length;
|
|
116
|
+
const inRange = (i) => Number.isInteger(i) && i >= 0 && i < bindingCount;
|
|
117
|
+
for (const b of cfg.blocks) {
|
|
118
|
+
const stmts = b.statements;
|
|
119
|
+
if (stmts === undefined)
|
|
120
|
+
continue;
|
|
121
|
+
if (!Array.isArray(stmts))
|
|
122
|
+
return false;
|
|
123
|
+
for (const s of stmts) {
|
|
124
|
+
if (!Number.isInteger(s?.line) || !Array.isArray(s.defs) || !Array.isArray(s.uses)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (!s.defs.every(inRange) || !s.uses.every(inRange))
|
|
128
|
+
return false;
|
|
129
|
+
if (s.mayDefs !== undefined) {
|
|
130
|
+
if (!Array.isArray(s.mayDefs) || !s.mayDefs.every(inRange))
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Emit BasicBlock nodes + CFG edges for every function CFG in `cfgs`.
|
|
139
|
+
*
|
|
140
|
+
* `maxEdgesPerFunction` caps edges per function. On overflow we stop emitting
|
|
141
|
+
* that function's remaining edges and call `onWarn` naming the dropped count —
|
|
142
|
+
* no silent truncation (KTD6/R6). Block nodes are always fully emitted (their
|
|
143
|
+
* count is bounded by the function's statement count); only edges are capped.
|
|
144
|
+
*/
|
|
145
|
+
export function emitFileCfgs(graph, cfgs, maxEdgesPerFunction = DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, onWarn) {
|
|
146
|
+
const result = { blocks: 0, edges: 0, droppedEdges: 0, cappedFunctions: 0 };
|
|
147
|
+
const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
|
|
148
|
+
for (const cfg of cfgs) {
|
|
149
|
+
const { filePath, functionStartLine, functionStartColumn } = cfg;
|
|
150
|
+
for (const b of cfg.blocks) {
|
|
151
|
+
graph.addNode({
|
|
152
|
+
id: basicBlockId(filePath, functionStartLine, functionStartColumn, b.index),
|
|
153
|
+
label: 'BasicBlock',
|
|
154
|
+
properties: {
|
|
155
|
+
name: '', // BasicBlock has no name column; identified by id + span
|
|
156
|
+
filePath,
|
|
157
|
+
startLine: b.startLine,
|
|
158
|
+
endLine: b.endLine,
|
|
159
|
+
text: b.text,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
result.blocks++;
|
|
163
|
+
}
|
|
164
|
+
let emittedForFn = 0;
|
|
165
|
+
for (const e of cfg.edges) {
|
|
166
|
+
if (emittedForFn >= cap) {
|
|
167
|
+
const dropped = cfg.edges.length - emittedForFn;
|
|
168
|
+
result.droppedEdges += dropped;
|
|
169
|
+
result.cappedFunctions++;
|
|
170
|
+
onWarn?.(`[cfg] ${filePath}:${functionStartLine}: per-function CFG edge cap ` +
|
|
171
|
+
`(${maxEdgesPerFunction}) reached — dropped ${dropped} of ${cfg.edges.length} edges`);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.from);
|
|
175
|
+
const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.to);
|
|
176
|
+
graph.addRelationship({
|
|
177
|
+
id: generateId('CFG', `${sourceId}->${targetId}:${e.kind}`),
|
|
178
|
+
type: 'CFG',
|
|
179
|
+
sourceId,
|
|
180
|
+
targetId,
|
|
181
|
+
confidence: 1.0,
|
|
182
|
+
reason: e.kind, // CfgEdgeKind (seq/cond-true/loop-back/…) — queryable
|
|
183
|
+
});
|
|
184
|
+
result.edges++;
|
|
185
|
+
emittedForFn++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
|
|
192
|
+
* `name:declLine:declCol` for declared bindings, `name@module` for synthetic
|
|
193
|
+
* ones. Distinct same-name bindings never share a key; identifier characters
|
|
194
|
+
* cannot contain the id separators. Exported for the M3 taint emit path —
|
|
195
|
+
* TAINTED/SANITIZES ids key bindings with the same discipline.
|
|
196
|
+
*/
|
|
197
|
+
export const bindingKey = (b) => b.synthetic ? `${b.name}@module` : `${b.name}:${b.declLine}:${b.declColumn}`;
|
|
198
|
+
/**
|
|
199
|
+
* Compute reaching definitions per function and persist the bounded
|
|
200
|
+
* REACHING_DEF projection (#2082 M2 U4).
|
|
201
|
+
*
|
|
202
|
+
* Facts are DEDUPED to (defBlock, useBlock, binding) before budgeting — the
|
|
203
|
+
* persisted columns (`from,to,type,confidence,reason,step`; relationship ids
|
|
204
|
+
* are in-memory-only, the CodeRelation table has no id column) cannot
|
|
205
|
+
* distinguish finer rows, so statement-indexed ids would only manufacture
|
|
206
|
+
* byte-identical duplicate rows that burn budget. Statement granularity lives
|
|
207
|
+
* in the in-memory {@link computeReachingDefs} result, which the M3 taint
|
|
208
|
+
* engine recomputes on demand — the budget here governs only this projection
|
|
209
|
+
* and can never drop a taint fact.
|
|
210
|
+
*
|
|
211
|
+
* R7 (no silent truncation) covers BOTH layers: the per-function edge cap AND
|
|
212
|
+
* the solver's fact-materialization limit (which can fire without the edge
|
|
213
|
+
* cap ever being reached, since dedup is many-to-one) each produce one
|
|
214
|
+
* unconditional `onWarn`. The edge-cap warn names the top bindings by fact
|
|
215
|
+
* count — overflow is almost always one variable, which is exactly the datum
|
|
216
|
+
* M3 tuning wants.
|
|
217
|
+
*/
|
|
218
|
+
export function emitFileReachingDefs(graph, cfgs, maxEdgesPerFunction = DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, onWarn) {
|
|
219
|
+
const result = {
|
|
220
|
+
edges: 0,
|
|
221
|
+
droppedEdges: 0,
|
|
222
|
+
cappedFunctions: 0,
|
|
223
|
+
truncatedFunctions: 0,
|
|
224
|
+
malformedFactFunctions: 0,
|
|
225
|
+
facts: 0,
|
|
226
|
+
};
|
|
227
|
+
const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
|
|
228
|
+
const maxFacts = Number.isFinite(cap) ? cap * REACHING_DEF_FACTS_PER_EDGE_CAP : 0; // 0 ⇒ unlimited
|
|
229
|
+
for (const cfg of cfgs) {
|
|
230
|
+
// Graceful degradation: malformed M2 facts cost only this function's
|
|
231
|
+
// REACHING_DEF projection — its BasicBlock/CFG layer was already emitted.
|
|
232
|
+
if (!hasEmitSafeFacts(cfg)) {
|
|
233
|
+
result.malformedFactFunctions++;
|
|
234
|
+
onWarn?.(`[reaching-defs] ${cfg.filePath}:${cfg.functionStartLine}: malformed ` +
|
|
235
|
+
`statement facts (bad binding table or out-of-range fact indices) — ` +
|
|
236
|
+
`REACHING_DEF skipped for this function; its CFG is unaffected`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const r = computeReachingDefs(cfg, { maxFacts });
|
|
240
|
+
if (r.status === 'no-facts')
|
|
241
|
+
continue;
|
|
242
|
+
result.facts += r.facts.length;
|
|
243
|
+
const { filePath, functionStartLine, functionStartColumn } = cfg;
|
|
244
|
+
if (r.status === 'truncated') {
|
|
245
|
+
result.truncatedFunctions++;
|
|
246
|
+
onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: fact materialization ` +
|
|
247
|
+
`limit (${maxFacts}) reached — facts beyond it were not computed; ` +
|
|
248
|
+
`the persisted REACHING_DEF projection for this function is sparse`);
|
|
249
|
+
}
|
|
250
|
+
else if (r.status === 'overflow') {
|
|
251
|
+
result.truncatedFunctions++;
|
|
252
|
+
onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: a basic block exceeds ` +
|
|
253
|
+
`the def-key stride (≥2^21 coalesced statements — minified/generated ` +
|
|
254
|
+
`code) — REACHING_DEF skipped for this function (computing any facts ` +
|
|
255
|
+
`would risk wrong-block aliasing); its CFG is unaffected`);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Dedup to (defBlock, useBlock, binding) — facts arrive sorted, so the
|
|
259
|
+
// deduped order (and therefore cap truncation) is deterministic.
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
const deduped = [];
|
|
262
|
+
for (const f of r.facts) {
|
|
263
|
+
const key = `${f.def.blockIndex}:${f.use.blockIndex}:${f.bindingIdx}`;
|
|
264
|
+
if (seen.has(key))
|
|
265
|
+
continue;
|
|
266
|
+
seen.add(key);
|
|
267
|
+
deduped.push({
|
|
268
|
+
defBlock: f.def.blockIndex,
|
|
269
|
+
useBlock: f.use.blockIndex,
|
|
270
|
+
bindingIdx: f.bindingIdx,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
let emittedForFn = 0;
|
|
274
|
+
for (const edge of deduped) {
|
|
275
|
+
if (emittedForFn >= cap) {
|
|
276
|
+
const dropped = deduped.length - emittedForFn;
|
|
277
|
+
result.droppedEdges += dropped;
|
|
278
|
+
result.cappedFunctions++;
|
|
279
|
+
// Tallied lazily — cap overflow is the rare path; the common uncapped
|
|
280
|
+
// case must not pay a per-fact counting pass.
|
|
281
|
+
const factsPerBinding = new Map();
|
|
282
|
+
for (const f of r.facts) {
|
|
283
|
+
factsPerBinding.set(f.bindingIdx, (factsPerBinding.get(f.bindingIdx) ?? 0) + 1);
|
|
284
|
+
}
|
|
285
|
+
const top = [...factsPerBinding.entries()]
|
|
286
|
+
.sort((a, b) => b[1] - a[1] || a[0] - b[0])
|
|
287
|
+
.slice(0, 2)
|
|
288
|
+
.map(([idx, count]) => `${r.bindings[idx]?.name ?? `#${idx}`}(${count} facts)`)
|
|
289
|
+
.join(', ');
|
|
290
|
+
onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: per-function ` +
|
|
291
|
+
`REACHING_DEF edge cap (${maxEdgesPerFunction}) reached — dropped ` +
|
|
292
|
+
`${dropped} of ${deduped.length} edges; top bindings: ${top}`);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
const binding = r.bindings[edge.bindingIdx];
|
|
296
|
+
const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.defBlock);
|
|
297
|
+
const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.useBlock);
|
|
298
|
+
graph.addRelationship({
|
|
299
|
+
// Single function anchor — the two block ids share it, so templating
|
|
300
|
+
// it once halves the id size (ids are in-memory-only but ~4000 of
|
|
301
|
+
// them per capped function is real transient heap).
|
|
302
|
+
id: generateId('REACHING_DEF', `${filePath}:${functionStartLine}:${functionStartColumn}:` +
|
|
303
|
+
`${edge.defBlock}->${edge.useBlock}:${bindingKey(binding)}`),
|
|
304
|
+
type: 'REACHING_DEF',
|
|
305
|
+
sourceId,
|
|
306
|
+
targetId,
|
|
307
|
+
confidence: 1.0,
|
|
308
|
+
reason: binding.name, // plain source-level name (M0/S1 verdict) — queryable
|
|
309
|
+
});
|
|
310
|
+
result.edges++;
|
|
311
|
+
emittedForFn++;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reaching definitions (#2082 M2 U3) — classic GEN/KILL monotone fixpoint over
|
|
3
|
+
* one function's CFG, plus the canonical intra-block statement sweep that
|
|
4
|
+
* recovers statement-granular def→use facts from M1's coalesced blocks
|
|
5
|
+
* WITHOUT re-splitting the CFG.
|
|
6
|
+
*
|
|
7
|
+
* PURE AND DETERMINISTIC (load-bearing contract):
|
|
8
|
+
* - Pure function of its inputs — no graph, no logger (warnings are the
|
|
9
|
+
* caller's job), importable outside the worker. The M3 taint engine calls
|
|
10
|
+
* this same function in-phase (facts are recomputed on demand, never
|
|
11
|
+
* retained run-wide — the persisted REACHING_DEF edges are a bounded
|
|
12
|
+
* projection, never the taint substrate).
|
|
13
|
+
* - Deterministic — predecessors merge in sorted block-index order,
|
|
14
|
+
* insertion-ordered Maps/Sets throughout, and the output fact array is
|
|
15
|
+
* explicitly sorted. Snapshot tests and content-derived edge ids rely on it.
|
|
16
|
+
*
|
|
17
|
+
* COMPLEXITY DISCIPLINE (the four-times-repeated repo bug shape is per-item
|
|
18
|
+
* re-derivation inside the loop): def-sets are SHARED BY REFERENCE, never
|
|
19
|
+
* deep-copied — a MUST def's kill is total per binding, so a transfer either
|
|
20
|
+
* aliases the incoming set or replaces it; a MAY def (conditional context —
|
|
21
|
+
* see StatementFacts.mayDefs) unions WITHOUT killing via a copy-on-extend.
|
|
22
|
+
* Single-predecessor blocks alias the predecessor's OUT map outright;
|
|
23
|
+
* multi-pred merges union only bindings whose incoming sets differ by
|
|
24
|
+
* reference. Iteration is reverse post-order, seeded with every block
|
|
25
|
+
* (unreachable blocks keep ⊥ IN — correct, their defs reach nothing).
|
|
26
|
+
* Convergence: sets grow monotonically within the finite def-site universe ⇒
|
|
27
|
+
* ≤ loop-depth+1 passes in practice.
|
|
28
|
+
*
|
|
29
|
+
* `limits.maxFacts` bounds materialization: facts are O(defs×uses) BY SPEC in
|
|
30
|
+
* merge-heavy code (N branch-arm defs × N later uses = N² facts), and a
|
|
31
|
+
* 2000-line function can spike 100k+ fact objects on the main thread. The
|
|
32
|
+
* emit path passes DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION (emit.ts);
|
|
33
|
+
* M3 passes its own large-but-finite limit and treats `status: 'truncated'`
|
|
34
|
+
* as a per-function taint-coverage gap.
|
|
35
|
+
*/
|
|
36
|
+
import type { BindingEntry, FunctionCfg } from './types.js';
|
|
37
|
+
/** A statement-granular program point within one function's CFG. */
|
|
38
|
+
export interface ProgramPoint {
|
|
39
|
+
readonly blockIndex: number;
|
|
40
|
+
/** Statement index within the block's `statements` array. */
|
|
41
|
+
readonly stmtIndex: number;
|
|
42
|
+
readonly line: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Canonical `block:stmt` string key for a program point. Colon-separated to
|
|
46
|
+
* match the codebase's `blockIndex:stmtIndex` id conventions. Shared by the
|
|
47
|
+
* taint propagation engine (dedup/state keys) and the taint emit path
|
|
48
|
+
* (persisted edge-id material) so the two never drift.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pointKey(p: ProgramPoint): string;
|
|
51
|
+
/** One def→use fact: the definition at `def` reaches the use at `use`. */
|
|
52
|
+
export interface DefUseFact {
|
|
53
|
+
/** Index into {@link FunctionDefUse.bindings}. */
|
|
54
|
+
readonly bindingIdx: number;
|
|
55
|
+
readonly def: ProgramPoint;
|
|
56
|
+
readonly use: ProgramPoint;
|
|
57
|
+
}
|
|
58
|
+
export interface ReachingDefsLimits {
|
|
59
|
+
/**
|
|
60
|
+
* Maximum number of facts to materialize; the sweep stops early and reports
|
|
61
|
+
* `status: 'truncated'`. `undefined`/0 ⇒ unlimited.
|
|
62
|
+
*/
|
|
63
|
+
readonly maxFacts?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface FunctionDefUse {
|
|
66
|
+
/**
|
|
67
|
+
* `computed` — full facts.
|
|
68
|
+
* `no-facts` — the CFG carries no statement facts (hand-built or pre-M2
|
|
69
|
+
* side channel); empty facts, NOT an error.
|
|
70
|
+
* `truncated` — `limits.maxFacts` hit; `facts` is a deterministic prefix.
|
|
71
|
+
* `overflow` — a block's statement count breaches the def-key stride; no
|
|
72
|
+
* facts at all (computing any would risk key aliasing —
|
|
73
|
+
* wrong-block facts are strictly worse than none). Distinct
|
|
74
|
+
* from `truncated` so the caller's diagnostic doesn't
|
|
75
|
+
* misname it as the fact-materialization limit.
|
|
76
|
+
*/
|
|
77
|
+
readonly status: 'computed' | 'no-facts' | 'truncated' | 'overflow';
|
|
78
|
+
/** Pass-through of the CFG's binding table (empty for `no-facts`). */
|
|
79
|
+
readonly bindings: readonly BindingEntry[];
|
|
80
|
+
/** Sorted by (def block, def stmt, use block, use stmt, binding). */
|
|
81
|
+
readonly facts: readonly DefUseFact[];
|
|
82
|
+
/** Total def / use sites seen (telemetry; independent of truncation). */
|
|
83
|
+
readonly defCount: number;
|
|
84
|
+
readonly useCount: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compute reaching definitions for one function. See the module doc for the
|
|
88
|
+
* purity/determinism/sharing contract.
|
|
89
|
+
*/
|
|
90
|
+
export declare function computeReachingDefs(cfg: FunctionCfg, limits?: ReachingDefsLimits): FunctionDefUse;
|