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
|
@@ -285,6 +285,12 @@ export const scopeResolutionPhase = {
|
|
|
285
285
|
prebuiltNodeLookup: sharedNodeLookup,
|
|
286
286
|
preExtractedParsedFiles: preExtractedByPath,
|
|
287
287
|
scopeIndexStorePath: parsedFileStorePath,
|
|
288
|
+
// CFG/PDG emission (#2081 M1) — opt-in; off ⇒ byte-identical graph.
|
|
289
|
+
pdg: ctx.options?.pdg === true,
|
|
290
|
+
pdgMaxEdgesPerFunction: ctx.options?.pdgMaxEdgesPerFunction,
|
|
291
|
+
pdgMaxReachingDefEdgesPerFunction: ctx.options?.pdgMaxReachingDefEdgesPerFunction,
|
|
292
|
+
pdgMaxTaintFindingsPerFunction: ctx.options?.pdgMaxTaintFindingsPerFunction,
|
|
293
|
+
pdgMaxTaintHops: ctx.options?.pdgMaxTaintHops,
|
|
288
294
|
recordResolutionOutcome: (outcome) => {
|
|
289
295
|
resolutionOutcomes.push(outcome);
|
|
290
296
|
},
|
|
@@ -64,14 +64,39 @@ export function reconcileOwnership(parsedFiles, model) {
|
|
|
64
64
|
for (const parsed of parsedFiles) {
|
|
65
65
|
for (const def of parsed.localDefs) {
|
|
66
66
|
const ownerId = def.ownerId;
|
|
67
|
-
if (ownerId === undefined)
|
|
68
|
-
continue;
|
|
69
67
|
const simple = simpleQualifiedName(def);
|
|
70
68
|
if (simple === undefined)
|
|
71
69
|
continue;
|
|
72
70
|
if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
|
|
71
|
+
if (ownerId === undefined) {
|
|
72
|
+
if (def.isDeleted !== true)
|
|
73
|
+
continue;
|
|
74
|
+
const existingDef = model.symbols
|
|
75
|
+
.lookupExactAll(def.filePath, simple)
|
|
76
|
+
.find((candidate) => callableSignatureMatches(candidate, def));
|
|
77
|
+
if (existingDef !== undefined) {
|
|
78
|
+
existingDef.isDeleted = true;
|
|
79
|
+
skippedAlreadyPresent++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
model.symbols.add(def.filePath, simple, def.nodeId, def.type, {
|
|
83
|
+
parameterCount: def.parameterCount,
|
|
84
|
+
requiredParameterCount: def.requiredParameterCount,
|
|
85
|
+
parameterTypes: def.parameterTypes,
|
|
86
|
+
parameterTypeClasses: def.parameterTypeClasses,
|
|
87
|
+
returnType: def.returnType,
|
|
88
|
+
qualifiedName: def.qualifiedName,
|
|
89
|
+
isDeleted: true,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
73
93
|
const existing = model.methods.lookupAllByOwner(ownerId, simple);
|
|
74
|
-
|
|
94
|
+
const existingDef = existing.find((candidate) => candidate.nodeId === def.nodeId ||
|
|
95
|
+
(def.isDeleted === true && callableSignatureMatches(candidate, def)));
|
|
96
|
+
if (existingDef !== undefined) {
|
|
97
|
+
if (def.isDeleted === true) {
|
|
98
|
+
existingDef.isDeleted = true;
|
|
99
|
+
}
|
|
75
100
|
skippedAlreadyPresent++;
|
|
76
101
|
continue;
|
|
77
102
|
}
|
|
@@ -103,6 +128,21 @@ export function reconcileOwnership(parsedFiles, model) {
|
|
|
103
128
|
}
|
|
104
129
|
return { methodsRegistered, fieldsRegistered, nestedTypesRegistered, skippedAlreadyPresent };
|
|
105
130
|
}
|
|
131
|
+
function callableSignatureMatches(left, right) {
|
|
132
|
+
if (left.filePath !== right.filePath)
|
|
133
|
+
return false;
|
|
134
|
+
if (left.parameterCount !== right.parameterCount)
|
|
135
|
+
return false;
|
|
136
|
+
if (left.requiredParameterCount !== right.requiredParameterCount)
|
|
137
|
+
return false;
|
|
138
|
+
const leftTypes = left.parameterTypes;
|
|
139
|
+
const rightTypes = right.parameterTypes;
|
|
140
|
+
if (leftTypes === undefined || rightTypes === undefined) {
|
|
141
|
+
return leftTypes === rightTypes;
|
|
142
|
+
}
|
|
143
|
+
return (leftTypes.length === rightTypes.length &&
|
|
144
|
+
leftTypes.every((parameterType, index) => parameterType === rightTypes[index]));
|
|
145
|
+
}
|
|
106
146
|
/**
|
|
107
147
|
* Debug-mode parity validator. Runs only when
|
|
108
148
|
* `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
|
|
@@ -58,6 +58,27 @@ interface RunScopeResolutionInput {
|
|
|
58
58
|
readonly treeCache?: {
|
|
59
59
|
get(filePath: string): unknown;
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* CFG/PDG opt-in (#2081 M1). When true, emit BasicBlock nodes + CFG edges
|
|
63
|
+
* from each ParsedFile's worker-built `cfgSideChannel` during Phase-4 graph
|
|
64
|
+
* emission (while the disk store is still live). Default/false ⇒ no CFG
|
|
65
|
+
* nodes or edges and a byte-identical graph.
|
|
66
|
+
*/
|
|
67
|
+
readonly pdg?: boolean;
|
|
68
|
+
/** Per-function CFG edge cap. `undefined` ⇒ {@link DEFAULT_MAX_CFG_EDGES_PER_FUNCTION};
|
|
69
|
+
* `0` ⇒ no cap (unlimited). */
|
|
70
|
+
readonly pdgMaxEdgesPerFunction?: number;
|
|
71
|
+
/** Per-function REACHING_DEF edge cap (#2082 M2). `undefined` ⇒
|
|
72
|
+
* {@link DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION}; `0` ⇒ no cap. */
|
|
73
|
+
readonly pdgMaxReachingDefEdgesPerFunction?: number;
|
|
74
|
+
/** Per-function taint findings cap (#2083 M3, consumed by the U4 taint
|
|
75
|
+
* emit step in the pdg window). `undefined` ⇒
|
|
76
|
+
* `DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION` (200); `0` ⇒ no cap. */
|
|
77
|
+
readonly pdgMaxTaintFindingsPerFunction?: number;
|
|
78
|
+
/** Per-finding taint hop cap (#2083 M3 KTD6 — bounds the hop-encoded
|
|
79
|
+
* `reason`; consumed by the U4 taint emit step). `undefined` ⇒
|
|
80
|
+
* `DEFAULT_PDG_MAX_TAINT_HOPS` (32); `0` ⇒ no cap. */
|
|
81
|
+
readonly pdgMaxTaintHops?: number;
|
|
61
82
|
/**
|
|
62
83
|
* Optional graph-node lookup built ONCE by the caller and shared across
|
|
63
84
|
* every language pass. `buildGraphNodeLookup` scans the whole graph and is
|
|
@@ -30,6 +30,10 @@ import { extractParsedFile } from '../../scope-extractor-bridge.js';
|
|
|
30
30
|
import { finalizeScopeModel } from '../../finalize-orchestrator.js';
|
|
31
31
|
import { resolveReferenceSites } from '../../resolve-references.js';
|
|
32
32
|
import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
33
|
+
import { emitFileCfgs, emitFileReachingDefs, isEmitSafeCfg, DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, REACHING_DEF_FACTS_PER_EDGE_CAP, } from '../../cfg/emit.js';
|
|
34
|
+
import { emitFileTaint, DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION, DEFAULT_PDG_MAX_TAINT_HOPS, } from '../../taint/emit.js';
|
|
35
|
+
import { registerBuiltinTaintModels } from '../../taint/typescript-model.js';
|
|
36
|
+
import { getSourceSinkConfig } from '../../taint/source-sink-registry.js';
|
|
33
37
|
import { resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
34
38
|
import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js';
|
|
35
39
|
import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
|
|
@@ -454,6 +458,206 @@ export function runScopeResolution(input, provider) {
|
|
|
454
458
|
resolutionConfig,
|
|
455
459
|
});
|
|
456
460
|
}
|
|
461
|
+
// ── CFG/PDG emission (#2081 M1, opt-in via `--pdg`) ──────────────────────
|
|
462
|
+
// Emit BasicBlock nodes + CFG edges from each ParsedFile's worker-built
|
|
463
|
+
// `cfgSideChannel`, HERE — the last point inside scope-resolution where the
|
|
464
|
+
// ParsedFiles are still loaded (`emitParsedFiles` carries the channel; the
|
|
465
|
+
// disk store is cleared right after this orchestrator returns, see phase.ts).
|
|
466
|
+
// A post-`mro` phase would read empty data (KTD1). Off by default ⇒ zero
|
|
467
|
+
// BasicBlock/CFG nodes/edges and a byte-identical graph.
|
|
468
|
+
// Accumulated M2 reaching-defs time (solve + dedup + REACHING_DEF emit),
|
|
469
|
+
// reported as the PROF `pdg=` segment. It is a SUBSET of `emit=` — the M1
|
|
470
|
+
// CFG emit and the M2 solve interleave per file, so a separate checkpoint
|
|
471
|
+
// pair can't bracket them; without this accumulator the M2 cost would
|
|
472
|
+
// silently disappear into `emit=` and field regressions would be invisible.
|
|
473
|
+
let pdgMs = 0;
|
|
474
|
+
// M3 (#2083 U4): accumulated taint time (match + taint-side solve +
|
|
475
|
+
// propagate + TAINTED/SANITIZES emit), a sibling of `pdgMs` for the same
|
|
476
|
+
// reason — it interleaves per file inside `emit=`, so only an accumulator
|
|
477
|
+
// can bracket it. Printed as the PROF `taint=` segment.
|
|
478
|
+
let taintMs = 0;
|
|
479
|
+
if (input.pdg === true) {
|
|
480
|
+
let cfgBlocks = 0;
|
|
481
|
+
let cfgEdges = 0;
|
|
482
|
+
let cfgDroppedEdges = 0;
|
|
483
|
+
let rdEdges = 0;
|
|
484
|
+
let rdDropped = 0;
|
|
485
|
+
let rdFacts = 0;
|
|
486
|
+
let rdTruncated = 0;
|
|
487
|
+
// ── M3 taint setup (#2083 U4) ────────────────────────────────────────
|
|
488
|
+
// Explicit model-registration seam (idempotent, cheap) — the registry
|
|
489
|
+
// stays empty on non-pdg runs, preserving default-run parity. The
|
|
490
|
+
// registry is keyed by `SupportedLanguages` enum VALUES ('typescript' /
|
|
491
|
+
// 'javascript'), and `ScopeResolver.language` IS a `SupportedLanguages`
|
|
492
|
+
// member registered under those same constants — the join is direct
|
|
493
|
+
// equality, no mapping table. A language without a registered spec
|
|
494
|
+
// (python, go, …) skips taint entirely: no work, no warn spam (KTD8).
|
|
495
|
+
registerBuiltinTaintModels();
|
|
496
|
+
const taintSpec = getSourceSinkConfig(provider.language);
|
|
497
|
+
// Taint-side solver fact cap: the SAME derivation emitFileReachingDefs
|
|
498
|
+
// uses for the RD projection (edge cap × headroom factor, 0 ⇒ unlimited),
|
|
499
|
+
// so taint coverage and RD coverage truncate together — a function is
|
|
500
|
+
// never a taint coverage gap while its RD projection computed, and the
|
|
501
|
+
// RD layer's per-function truncation warn already names it.
|
|
502
|
+
const rdEdgeCap = input.pdgMaxReachingDefEdgesPerFunction ?? DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
|
|
503
|
+
const taintLimits = {
|
|
504
|
+
maxFindingsPerFunction: input.pdgMaxTaintFindingsPerFunction ?? DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION,
|
|
505
|
+
maxHops: input.pdgMaxTaintHops ?? DEFAULT_PDG_MAX_TAINT_HOPS,
|
|
506
|
+
maxFacts: rdEdgeCap > 0 ? rdEdgeCap * REACHING_DEF_FACTS_PER_EDGE_CAP : 0,
|
|
507
|
+
};
|
|
508
|
+
// Cross-file aggregate of EVERY TaintEmitResult counter (the M2 emit
|
|
509
|
+
// result shipped with two fields dropped on the floor — R4 forbids that
|
|
510
|
+
// here; gaps/drops feed the unconditional warn below, volume feeds the
|
|
511
|
+
// per-language debug line).
|
|
512
|
+
const taintTotals = {
|
|
513
|
+
analyzed: 0,
|
|
514
|
+
noMatch: 0,
|
|
515
|
+
unsafeSites: 0,
|
|
516
|
+
gapTruncated: 0,
|
|
517
|
+
gapOverflow: 0,
|
|
518
|
+
gapNoFacts: 0,
|
|
519
|
+
findings: 0,
|
|
520
|
+
kills: 0,
|
|
521
|
+
dropped: 0,
|
|
522
|
+
hopsTruncated: 0,
|
|
523
|
+
gapExamples: [],
|
|
524
|
+
dropExamples: [],
|
|
525
|
+
};
|
|
526
|
+
for (const pf of emitParsedFiles) {
|
|
527
|
+
const cfgs = pf.cfgSideChannel;
|
|
528
|
+
// Defensive: cfgSideChannel is opaque (`unknown`) and crosses the cache /
|
|
529
|
+
// durable store. A stale or wrong-shape value (e.g. a pre-SCHEMA_BUMP
|
|
530
|
+
// shard that slipped the version gate) must skip emission, not throw a
|
|
531
|
+
// TypeError mid-graph-build and abort scope-resolution for the language.
|
|
532
|
+
if (!Array.isArray(cfgs) || cfgs.length === 0)
|
|
533
|
+
continue;
|
|
534
|
+
try {
|
|
535
|
+
// Per-element emit-safety filter (mirrors the parsedfile-store
|
|
536
|
+
// reviver's POLICY: valid elements in a mixed array still emit; junk
|
|
537
|
+
// is warned and skipped). isEmitSafeCfg lives in cfg/emit.ts next to
|
|
538
|
+
// the id templating it defends — see its doc for why anchor-field and
|
|
539
|
+
// endpoint-membership checks are load-bearing. Runs INSIDE the try so
|
|
540
|
+
// even a predicate-time throw (e.g. a hostile getter) is isolated.
|
|
541
|
+
const wellFormed = cfgs.filter(isEmitSafeCfg);
|
|
542
|
+
if (wellFormed.length < cfgs.length) {
|
|
543
|
+
logger.warn(`[cfg] ${pf.filePath}: skipped ${cfgs.length - wellFormed.length} malformed ` +
|
|
544
|
+
`cfgSideChannel element(s) (bad shape, missing id-anchor fields, or edge ` +
|
|
545
|
+
`endpoints matching no block) — CFG for those functions omitted`);
|
|
546
|
+
}
|
|
547
|
+
if (wellFormed.length === 0)
|
|
548
|
+
continue;
|
|
549
|
+
const emitted = emitFileCfgs(graph, wellFormed, input.pdgMaxEdgesPerFunction ?? DEFAULT_MAX_CFG_EDGES_PER_FUNCTION,
|
|
550
|
+
// Log cap-overflow drops UNCONDITIONALLY (not via input.onWarn, which is
|
|
551
|
+
// gated behind the semantic-model validator and silent in production) so
|
|
552
|
+
// the per-function edge cap never truncates the CFG silently (R6/KTD6).
|
|
553
|
+
(message) => logger.warn(message));
|
|
554
|
+
cfgBlocks += emitted.blocks;
|
|
555
|
+
cfgEdges += emitted.edges;
|
|
556
|
+
cfgDroppedEdges += emitted.droppedEdges;
|
|
557
|
+
// M2 (#2082 U4): reaching definitions over the same validated CFGs.
|
|
558
|
+
// In-memory facts are computed per function and dropped after the
|
|
559
|
+
// bounded (defBlock, useBlock, binding) projection is persisted —
|
|
560
|
+
// M3 recomputes via the same pure solver in-phase (KTD8). Timing is
|
|
561
|
+
// PROF-gated like every other checkpoint here (zero cost when off).
|
|
562
|
+
const t0 = PROF ? performance.now() : 0;
|
|
563
|
+
const rd = emitFileReachingDefs(graph, wellFormed, input.pdgMaxReachingDefEdgesPerFunction ??
|
|
564
|
+
DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, (message) => logger.warn(message));
|
|
565
|
+
if (PROF)
|
|
566
|
+
pdgMs += performance.now() - t0;
|
|
567
|
+
rdEdges += rd.edges;
|
|
568
|
+
rdDropped += rd.droppedEdges;
|
|
569
|
+
rdFacts += rd.facts;
|
|
570
|
+
rdTruncated += rd.truncatedFunctions;
|
|
571
|
+
// M3 (#2083 U4): taint over the SAME validated CFGs, inside the SAME
|
|
572
|
+
// per-file try (a taint throw costs this file's taint layer only —
|
|
573
|
+
// its CFG/REACHING_DEF edges above are already in the graph). Skipped
|
|
574
|
+
// entirely when the language has no registered model.
|
|
575
|
+
if (taintSpec !== undefined) {
|
|
576
|
+
const t1 = PROF ? performance.now() : 0;
|
|
577
|
+
const taint = emitFileTaint(graph, wellFormed, pf.parsedImports, taintSpec, taintLimits, (message) => logger.warn(message));
|
|
578
|
+
if (PROF)
|
|
579
|
+
taintMs += performance.now() - t1;
|
|
580
|
+
taintTotals.analyzed += taint.functionsAnalyzed;
|
|
581
|
+
taintTotals.noMatch += taint.functionsSkippedNoMatch;
|
|
582
|
+
taintTotals.unsafeSites += taint.functionsSkippedUnsafeSites;
|
|
583
|
+
taintTotals.gapTruncated += taint.functionsCoverageGap.truncated;
|
|
584
|
+
taintTotals.gapOverflow += taint.functionsCoverageGap.overflow;
|
|
585
|
+
taintTotals.gapNoFacts += taint.functionsCoverageGap['no-facts'];
|
|
586
|
+
taintTotals.findings += taint.findingsEmitted;
|
|
587
|
+
taintTotals.kills += taint.killsEmitted;
|
|
588
|
+
taintTotals.dropped += taint.findingsDropped;
|
|
589
|
+
taintTotals.hopsTruncated += taint.hopsTruncatedFindings;
|
|
590
|
+
for (const ex of taint.coverageGapExamples) {
|
|
591
|
+
if (taintTotals.gapExamples.length < 5)
|
|
592
|
+
taintTotals.gapExamples.push(ex);
|
|
593
|
+
}
|
|
594
|
+
for (const ex of taint.droppedExamples) {
|
|
595
|
+
if (taintTotals.dropExamples.length < 5)
|
|
596
|
+
taintTotals.dropExamples.push(ex);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
// Last-resort isolation, mirroring the worker-side per-file try/catch:
|
|
602
|
+
// a shape the predicate misses must cost this one file's CFG, not
|
|
603
|
+
// abort the language's whole scope-resolution pass mid-graph-build.
|
|
604
|
+
// NOTE a mid-emit throw can leave this file's already-inserted
|
|
605
|
+
// BasicBlock nodes in the graph (addNode is not transactional) —
|
|
606
|
+
// orphaned but inert; the predicate keeps every JSON-representable
|
|
607
|
+
// bad shape from reaching this path at all.
|
|
608
|
+
logger.warn(`[cfg] ${pf.filePath}: CFG emission failed (${err instanceof Error ? err.message : String(err)}) — ` +
|
|
609
|
+
`this file's CFG is partial or absent`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (cfgBlocks > 0) {
|
|
613
|
+
logger.debug(`[scope-resolution] CFG emit (lang=${provider.language}): ` +
|
|
614
|
+
`${cfgBlocks} BasicBlock nodes, ${cfgEdges} CFG edges` +
|
|
615
|
+
(cfgDroppedEdges > 0 ? `, ${cfgDroppedEdges} edges dropped (per-function cap)` : '') +
|
|
616
|
+
`; ${rdEdges} REACHING_DEF edges (${rdFacts} facts)` +
|
|
617
|
+
(rdDropped > 0 ? `, ${rdDropped} REACHING_DEF edges dropped (per-function cap)` : '') +
|
|
618
|
+
(rdTruncated > 0 ? `, ${rdTruncated} function(s) hit the fact limit` : '') +
|
|
619
|
+
// M3 volume telemetry — only for languages with a registered model.
|
|
620
|
+
(taintSpec !== undefined
|
|
621
|
+
? `; taint: ${taintTotals.findings} TAINTED, ${taintTotals.kills} SANITIZES ` +
|
|
622
|
+
`(${taintTotals.analyzed} function(s) analyzed, ` +
|
|
623
|
+
`${taintTotals.noMatch} skipped: no source/sink match` +
|
|
624
|
+
(taintTotals.hopsTruncated > 0
|
|
625
|
+
? `, ${taintTotals.hopsTruncated} finding(s) with truncated hop paths`
|
|
626
|
+
: '') +
|
|
627
|
+
`)`
|
|
628
|
+
: ''));
|
|
629
|
+
}
|
|
630
|
+
// R4: taint coverage gaps and cap drops surface UNCONDITIONALLY (never
|
|
631
|
+
// logger.debug, never input.onWarn) at the per-language aggregate, with
|
|
632
|
+
// counts and up to 5 example functions. Per-function warns above cover
|
|
633
|
+
// the rare/actionable cases (unsafe sites, cap drops); solver-status gaps
|
|
634
|
+
// were already per-function-warned by the RD layer (same solver, same
|
|
635
|
+
// fact cap), so this aggregate is their single taint-side surface.
|
|
636
|
+
if (taintSpec !== undefined) {
|
|
637
|
+
const gapCount = taintTotals.unsafeSites +
|
|
638
|
+
taintTotals.gapTruncated +
|
|
639
|
+
taintTotals.gapOverflow +
|
|
640
|
+
taintTotals.gapNoFacts;
|
|
641
|
+
if (gapCount > 0 || taintTotals.dropped > 0) {
|
|
642
|
+
const parts = [];
|
|
643
|
+
if (gapCount > 0) {
|
|
644
|
+
parts.push(`${gapCount} function(s) skipped for taint ` +
|
|
645
|
+
`(${taintTotals.gapTruncated} fact-limit, ${taintTotals.gapOverflow} overflow, ` +
|
|
646
|
+
`${taintTotals.gapNoFacts} no-facts, ${taintTotals.unsafeSites} malformed sites)` +
|
|
647
|
+
(taintTotals.gapExamples.length > 0
|
|
648
|
+
? ` — e.g. ${taintTotals.gapExamples.join(', ')}`
|
|
649
|
+
: ''));
|
|
650
|
+
}
|
|
651
|
+
if (taintTotals.dropped > 0) {
|
|
652
|
+
parts.push(`${taintTotals.dropped} finding(s) dropped by the per-function cap` +
|
|
653
|
+
(taintTotals.dropExamples.length > 0
|
|
654
|
+
? ` — e.g. ${taintTotals.dropExamples.join(', ')}`
|
|
655
|
+
: ''));
|
|
656
|
+
}
|
|
657
|
+
logger.warn(`[taint] lang=${provider.language}: ${parts.join('; ')}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
457
661
|
if (PROF) {
|
|
458
662
|
const tEnd = process.hrtime.bigint();
|
|
459
663
|
const ns = (a, b) => Number(b - a) / 1_000_000;
|
|
@@ -462,6 +666,9 @@ export function runScopeResolution(input, provider) {
|
|
|
462
666
|
` propagate=${ns(tFinalize, tPropagate).toFixed(0)}ms` +
|
|
463
667
|
` resolve=${ns(tPropagate, tResolve).toFixed(0)}ms` +
|
|
464
668
|
` emit=${ns(tResolve, tEnd).toFixed(0)}ms` +
|
|
669
|
+
// pdg ⊆ emit: the M2 reaching-defs share of the emit bucket (#2082 U4).
|
|
670
|
+
// taint ⊆ emit likewise: the M3 match+solve+propagate+emit share (#2083 U4).
|
|
671
|
+
(input.pdg === true ? ` pdg=${pdgMs.toFixed(0)}ms taint=${taintMs.toFixed(0)}ms` : '') +
|
|
465
672
|
` total=${ns(tStart, tEnd).toFixed(0)}ms` +
|
|
466
673
|
` (${parsedFiles.length} files)`);
|
|
467
674
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Range } from '../../../_shared/index.js';
|
|
2
|
-
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
|
|
2
|
+
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'selected-callable-deleted' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
|
|
3
3
|
export type ResolutionOutcome = {
|
|
4
4
|
readonly kind: 'resolved';
|
|
5
5
|
readonly targetId: string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-phase taint emission (#2083 M3 U4, plan KTD1/KTD6).
|
|
3
|
+
*
|
|
4
|
+
* Per-file driver for the M3 taint pass: gate → match → solve → propagate →
|
|
5
|
+
* persist sparse `TAINTED` + `SANITIZES` edges. Invoked from the pdg window in
|
|
6
|
+
* scope-resolution (`pipeline/run.ts`), immediately after `emitFileReachingDefs`
|
|
7
|
+
* inside the SAME per-file try — per-file isolation for free (KTD1). Mirrors
|
|
8
|
+
* `emitFileReachingDefs` (cfg/emit.ts) for the budget/dedup/warn discipline and
|
|
9
|
+
* the telemetry-result shape.
|
|
10
|
+
*
|
|
11
|
+
* ## Per-function pipeline (ordering is load-bearing)
|
|
12
|
+
*
|
|
13
|
+
* 1. `hasTaintSafeSites` — a corrupted-store site annotation degrades to
|
|
14
|
+
* SKIP-TAINT-KEEP-RD for this function (counted + warned), never a crash
|
|
15
|
+
* (KTD2; the matcher/propagator dereference indices unvalidated).
|
|
16
|
+
* 2. `matchFunctionSites` against the language spec (the import index is
|
|
17
|
+
* built ONCE per file — imports are a file-level fact).
|
|
18
|
+
* 3. ZERO-MATCH FAST PATH: the solver runs only when the function has at
|
|
19
|
+
* least one matched source AND one matched sink. In a typical repo almost
|
|
20
|
+
* no function has both; an unconditional second `computeReachingDefs` per
|
|
21
|
+
* function would ship a near-2× solve cost to every `--pdg` user.
|
|
22
|
+
* 4. `computeReachingDefs` with the taint `maxFacts` — by DEFAULT the M2
|
|
23
|
+
* derived `DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION` (deliberate
|
|
24
|
+
* reuse, not a new constant: the fact-materialization envelope is a
|
|
25
|
+
* memory question, O(defs×uses), orthogonal to the findings cap, and M2
|
|
26
|
+
* already validated exactly this envelope on the same solver in the same
|
|
27
|
+
* window). The run.ts caller derives `limits.maxFacts` from the SAME
|
|
28
|
+
* RD-edge-cap formula `emitFileReachingDefs` uses, so taint coverage and
|
|
29
|
+
* RD coverage truncate together — a function is never `truncated` for one
|
|
30
|
+
* layer and `computed` for the other.
|
|
31
|
+
* 5. `computeTaintFlows` — a non-`computed` status is a per-function
|
|
32
|
+
* COVERAGE GAP (R4: counted by `gapReason`, function skipped entirely,
|
|
33
|
+
* never partially analyzed).
|
|
34
|
+
* 6. Emit one `TAINTED` edge per finding and one `SANITIZES` edge per kill.
|
|
35
|
+
* Kills are emitted even when findings are zero — a fully-sanitized
|
|
36
|
+
* function's kills are exactly its evidence of safety.
|
|
37
|
+
*
|
|
38
|
+
* ## Identity, dedup, budget (KTD6)
|
|
39
|
+
*
|
|
40
|
+
* Findings carry STATEMENT-LEVEL identity — function anchor + sink kind +
|
|
41
|
+
* source occurrence (point/site/object-binding/property) + sink occurrence
|
|
42
|
+
* (point/site/arg/binding) — NOT the REACHING_DEF block-level key (block-pair
|
|
43
|
+
* conflation would drop `exec(req.body, req.query)`'s second finding). The
|
|
44
|
+
* propagation engine dedups by this exact key BEFORE its deterministic cap
|
|
45
|
+
* (`maxFindingsPerFunction`) and counts the overflow; this module templates
|
|
46
|
+
* the same coordinates into the edge id (binding identity via the shared
|
|
47
|
+
* `bindingKey`; the free-text `property` rides LAST so it can never collide
|
|
48
|
+
* into another component) and warns with the drop count on truncation.
|
|
49
|
+
*
|
|
50
|
+
* `reason` carries the versioned hop encoding (`taint/path-codec.ts` — U6's
|
|
51
|
+
* `explain` decodes the same module) for `TAINTED`, and the killed binding's
|
|
52
|
+
* plain name for `SANITIZES` (M0/S1 queryability verdict, like REACHING_DEF).
|
|
53
|
+
*
|
|
54
|
+
* ## Warn split (R4 vs noise)
|
|
55
|
+
*
|
|
56
|
+
* Unsafe-site skips and cap drops warn PER FUNCTION here (rare, actionable —
|
|
57
|
+
* mirrors `emitFileReachingDefs`' malformed/cap warns). Solver coverage gaps
|
|
58
|
+
* (`truncated`/`overflow`) do NOT re-warn per function: the RD layer already
|
|
59
|
+
* warned for the same function with the same solver status (same `maxFacts`
|
|
60
|
+
* derivation — see step 4), and a duplicate `[taint]` line per mega-function
|
|
61
|
+
* would be pure spam. They are counted (+ exampled) in the result and the
|
|
62
|
+
* run.ts caller aggregates them into ONE unconditional `logger.warn` per
|
|
63
|
+
* language (R4) — never dropped on the floor (the M2 lesson).
|
|
64
|
+
*/
|
|
65
|
+
import type { ParsedImport } from '../../../_shared/index.js';
|
|
66
|
+
import type { KnowledgeGraph } from '../../graph/types.js';
|
|
67
|
+
import type { FunctionCfg } from '../cfg/types.js';
|
|
68
|
+
export { DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION, DEFAULT_PDG_MAX_TAINT_HOPS, } from './propagate.js';
|
|
69
|
+
import type { SourceSinkSanitizerSpec } from './source-sink-config.js';
|
|
70
|
+
export interface TaintEmitLimits {
|
|
71
|
+
/** Per-function findings cap (post-dedup). `undefined` ⇒
|
|
72
|
+
* {@link DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION}; `0` ⇒ unlimited. */
|
|
73
|
+
readonly maxFindingsPerFunction?: number;
|
|
74
|
+
/** Per-finding hop cap (source-side prefix kept). `undefined` ⇒
|
|
75
|
+
* {@link DEFAULT_PDG_MAX_TAINT_HOPS}; `0` ⇒ unlimited. */
|
|
76
|
+
readonly maxHops?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Solver fact-materialization cap for the taint-side `computeReachingDefs`
|
|
79
|
+
* call. `undefined` ⇒ {@link DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION}
|
|
80
|
+
* (the M2 derived default — see the module doc for why it is REUSED rather
|
|
81
|
+
* than derived from the findings cap); `0` ⇒ unlimited.
|
|
82
|
+
*/
|
|
83
|
+
readonly maxFacts?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Full taint-emit telemetry for one file. EVERY counter is surfaced by the
|
|
87
|
+
* run.ts aggregate (the M2 emit result had two fields dropped on the floor —
|
|
88
|
+
* the plan names that mistake; don't repeat it).
|
|
89
|
+
*/
|
|
90
|
+
export interface TaintEmitResult {
|
|
91
|
+
/** Functions fully propagated (`computeTaintFlows` returned `computed`). */
|
|
92
|
+
functionsAnalyzed: number;
|
|
93
|
+
/** Functions skipped by the zero-match fast path (no solver call). */
|
|
94
|
+
functionsSkippedNoMatch: number;
|
|
95
|
+
/** Functions whose `sites` failed {@link hasTaintSafeSites} (skip-taint-keep-RD). */
|
|
96
|
+
functionsSkippedUnsafeSites: number;
|
|
97
|
+
/** Source+sink functions skipped on a non-`computed` solver status (R4). */
|
|
98
|
+
functionsCoverageGap: {
|
|
99
|
+
truncated: number;
|
|
100
|
+
overflow: number;
|
|
101
|
+
'no-facts': number;
|
|
102
|
+
};
|
|
103
|
+
/** TAINTED edges persisted. */
|
|
104
|
+
findingsEmitted: number;
|
|
105
|
+
/** SANITIZES edges persisted (emitted even when findings are zero). */
|
|
106
|
+
killsEmitted: number;
|
|
107
|
+
/** Findings dropped by the per-function cap (post-dedup), summed. */
|
|
108
|
+
findingsDropped: number;
|
|
109
|
+
/** Findings whose persisted hop path is a truncated prefix (hop/byte cap). */
|
|
110
|
+
hopsTruncatedFindings: number;
|
|
111
|
+
/** ≤{@link MAX_EXAMPLES} `file:line` anchors of gap/unsafe-site functions. */
|
|
112
|
+
coverageGapExamples: string[];
|
|
113
|
+
/** ≤{@link MAX_EXAMPLES} `file:line` anchors of cap-dropped functions. */
|
|
114
|
+
droppedExamples: string[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Run the taint pass over one file's emit-safe CFGs and persist TAINTED +
|
|
118
|
+
* SANITIZES edges. `cfgs` MUST already be `isEmitSafeCfg`-filtered (the same
|
|
119
|
+
* `wellFormed` array the caller fed `emitFileCfgs`/`emitFileReachingDefs`) —
|
|
120
|
+
* block/edge anchors are trusted here; only the M3 `sites` layer is
|
|
121
|
+
* re-validated (`hasTaintSafeSites`). Never throws on well-formed input;
|
|
122
|
+
* the caller's per-file try isolates the rest.
|
|
123
|
+
*/
|
|
124
|
+
export declare function emitFileTaint(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], parsedImports: readonly ParsedImport[], spec: SourceSinkSanitizerSpec, limits?: TaintEmitLimits, onWarn?: (message: string) => void): TaintEmitResult;
|