@grafema/util 0.3.0-beta
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/LICENSE +190 -0
- package/dist/api/GraphAPI.d.ts +87 -0
- package/dist/api/GraphAPI.d.ts.map +1 -0
- package/dist/api/GraphAPI.js +212 -0
- package/dist/api/GraphAPI.js.map +1 -0
- package/dist/api/GuaranteeAPI.d.ts +147 -0
- package/dist/api/GuaranteeAPI.d.ts.map +1 -0
- package/dist/api/GuaranteeAPI.js +290 -0
- package/dist/api/GuaranteeAPI.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +214 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +441 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +199 -0
- package/dist/core/CoverageAnalyzer.js.map +1 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +140 -0
- package/dist/core/FileExplainer.js.map +1 -0
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +279 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GrafemaUri.d.ts +66 -0
- package/dist/core/GrafemaUri.d.ts.map +1 -0
- package/dist/core/GrafemaUri.js +191 -0
- package/dist/core/GrafemaUri.js.map +1 -0
- package/dist/core/GraphBackend.d.ts +158 -0
- package/dist/core/GraphBackend.d.ts.map +1 -0
- package/dist/core/GraphBackend.js +85 -0
- package/dist/core/GraphBackend.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +104 -0
- package/dist/core/GraphFreshnessChecker.js.map +1 -0
- package/dist/core/GuaranteeManager.d.ts +254 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -0
- package/dist/core/GuaranteeManager.js +447 -0
- package/dist/core/GuaranteeManager.js.map +1 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +46 -0
- package/dist/core/HashUtils.js.map +1 -0
- package/dist/core/IncrementalReanalyzer.d.ts +33 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +67 -0
- package/dist/core/IncrementalReanalyzer.js.map +1 -0
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/SemanticId.d.ts +159 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +291 -0
- package/dist/core/SemanticId.js.map +1 -0
- package/dist/core/VersionManager.d.ts +166 -0
- package/dist/core/VersionManager.d.ts.map +1 -0
- package/dist/core/VersionManager.js +239 -0
- package/dist/core/VersionManager.js.map +1 -0
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
- package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
- package/dist/core/nodes/GuaranteeNode.js +118 -0
- package/dist/core/nodes/GuaranteeNode.js.map +1 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +130 -0
- package/dist/core/nodes/IssueNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +104 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -0
- package/dist/core/nodes/NodeKind.js +166 -0
- package/dist/core/nodes/NodeKind.js.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +133 -0
- package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +300 -0
- package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +44 -0
- package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
- package/dist/diagnostics/categories.d.ts +57 -0
- package/dist/diagnostics/categories.d.ts.map +1 -0
- package/dist/diagnostics/categories.js +71 -0
- package/dist/diagnostics/categories.js.map +1 -0
- package/dist/diagnostics/index.d.ts +17 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +15 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/errors/GrafemaError.d.ts +200 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +209 -0
- package/dist/errors/GrafemaError.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +8 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +20 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/onboarding.md +133 -0
- package/dist/knowledge/KnowledgeBase.d.ts +113 -0
- package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeBase.js +420 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.js +160 -0
- package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
- package/dist/knowledge/git-ingest.d.ts +58 -0
- package/dist/knowledge/git-ingest.d.ts.map +1 -0
- package/dist/knowledge/git-ingest.js +301 -0
- package/dist/knowledge/git-ingest.js.map +1 -0
- package/dist/knowledge/git-queries.d.ts +86 -0
- package/dist/knowledge/git-queries.d.ts.map +1 -0
- package/dist/knowledge/git-queries.js +177 -0
- package/dist/knowledge/git-queries.js.map +1 -0
- package/dist/knowledge/index.d.ts +14 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +10 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/parser.d.ts +39 -0
- package/dist/knowledge/parser.d.ts.map +1 -0
- package/dist/knowledge/parser.js +292 -0
- package/dist/knowledge/parser.js.map +1 -0
- package/dist/knowledge/types.d.ts +133 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +8 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/logging/Logger.d.ts +98 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +274 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/notation/archetypes.d.ts +36 -0
- package/dist/notation/archetypes.d.ts.map +1 -0
- package/dist/notation/archetypes.js +173 -0
- package/dist/notation/archetypes.js.map +1 -0
- package/dist/notation/fold.d.ts +25 -0
- package/dist/notation/fold.d.ts.map +1 -0
- package/dist/notation/fold.js +598 -0
- package/dist/notation/fold.js.map +1 -0
- package/dist/notation/index.d.ts +18 -0
- package/dist/notation/index.d.ts.map +1 -0
- package/dist/notation/index.js +16 -0
- package/dist/notation/index.js.map +1 -0
- package/dist/notation/lodExtractor.d.ts +32 -0
- package/dist/notation/lodExtractor.d.ts.map +1 -0
- package/dist/notation/lodExtractor.js +149 -0
- package/dist/notation/lodExtractor.js.map +1 -0
- package/dist/notation/nameShortener.d.ts +22 -0
- package/dist/notation/nameShortener.d.ts.map +1 -0
- package/dist/notation/nameShortener.js +24 -0
- package/dist/notation/nameShortener.js.map +1 -0
- package/dist/notation/perspectives.d.ts +11 -0
- package/dist/notation/perspectives.d.ts.map +1 -0
- package/dist/notation/perspectives.js +16 -0
- package/dist/notation/perspectives.js.map +1 -0
- package/dist/notation/renderer.d.ts +31 -0
- package/dist/notation/renderer.d.ts.map +1 -0
- package/dist/notation/renderer.js +315 -0
- package/dist/notation/renderer.js.map +1 -0
- package/dist/notation/traceRenderer.d.ts +39 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -0
- package/dist/notation/traceRenderer.js +358 -0
- package/dist/notation/traceRenderer.js.map +1 -0
- package/dist/notation/types.d.ts +66 -0
- package/dist/notation/types.d.ts.map +1 -0
- package/dist/notation/types.js +10 -0
- package/dist/notation/types.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +196 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts +62 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +169 -0
- package/dist/queries/findCallsInFunction.js.map +1 -0
- package/dist/queries/findContainingFunction.d.ts +57 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +91 -0
- package/dist/queries/findContainingFunction.js.map +1 -0
- package/dist/queries/index.d.ts +18 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +14 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/traceDataflow.d.ts +65 -0
- package/dist/queries/traceDataflow.d.ts.map +1 -0
- package/dist/queries/traceDataflow.js +754 -0
- package/dist/queries/traceDataflow.js.map +1 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +373 -0
- package/dist/queries/traceValues.js.map +1 -0
- package/dist/queries/types.d.ts +166 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +10 -0
- package/dist/queries/types.js.map +1 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +125 -0
- package/dist/schema/GraphSchemaExtractor.js.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +113 -0
- package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.js +748 -0
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
- package/dist/storage/backends/typeValidation.d.ts +47 -0
- package/dist/storage/backends/typeValidation.d.ts.map +1 -0
- package/dist/storage/backends/typeValidation.js +141 -0
- package/dist/storage/backends/typeValidation.js.map +1 -0
- package/dist/utils/findRfdbBinary.d.ts +67 -0
- package/dist/utils/findRfdbBinary.d.ts.map +1 -0
- package/dist/utils/findRfdbBinary.js +261 -0
- package/dist/utils/findRfdbBinary.js.map +1 -0
- package/dist/utils/lazyDownload.d.ts +43 -0
- package/dist/utils/lazyDownload.d.ts.map +1 -0
- package/dist/utils/lazyDownload.js +175 -0
- package/dist/utils/lazyDownload.js.map +1 -0
- package/dist/utils/moduleResolution.d.ts +134 -0
- package/dist/utils/moduleResolution.d.ts.map +1 -0
- package/dist/utils/moduleResolution.js +189 -0
- package/dist/utils/moduleResolution.js.map +1 -0
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/utils/startRfdbServer.d.ts +63 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +142 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/dist/validation/PathValidator.d.ts +80 -0
- package/dist/validation/PathValidator.d.ts.map +1 -0
- package/dist/validation/PathValidator.js +252 -0
- package/dist/validation/PathValidator.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
- package/src/api/GraphAPI.ts +307 -0
- package/src/api/GuaranteeAPI.ts +402 -0
- package/src/config/ConfigLoader.ts +653 -0
- package/src/config/index.ts +13 -0
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileOverview.ts +397 -0
- package/src/core/GrafemaUri.ts +216 -0
- package/src/core/GraphBackend.ts +266 -0
- package/src/core/GraphFreshnessChecker.ts +145 -0
- package/src/core/GuaranteeManager.ts +684 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +106 -0
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/SemanticId.ts +423 -0
- package/src/core/VersionManager.ts +405 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/nodes/GuaranteeNode.ts +162 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/NodeKind.ts +192 -0
- package/src/diagnostics/DiagnosticCollector.ts +170 -0
- package/src/diagnostics/DiagnosticReporter.ts +395 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/categories.ts +104 -0
- package/src/diagnostics/index.ts +30 -0
- package/src/errors/GrafemaError.ts +297 -0
- package/src/index.ts +261 -0
- package/src/instructions/index.ts +21 -0
- package/src/instructions/onboarding.md +133 -0
- package/src/knowledge/KnowledgeBase.ts +486 -0
- package/src/knowledge/SemanticAddressResolver.ts +191 -0
- package/src/knowledge/git-ingest.ts +402 -0
- package/src/knowledge/git-queries.ts +269 -0
- package/src/knowledge/index.ts +29 -0
- package/src/knowledge/parser.ts +294 -0
- package/src/knowledge/types.ts +146 -0
- package/src/logging/Logger.ts +303 -0
- package/src/notation/archetypes.ts +189 -0
- package/src/notation/fold.ts +696 -0
- package/src/notation/index.ts +27 -0
- package/src/notation/lodExtractor.ts +177 -0
- package/src/notation/nameShortener.ts +24 -0
- package/src/notation/perspectives.ts +18 -0
- package/src/notation/renderer.ts +394 -0
- package/src/notation/traceRenderer.ts +458 -0
- package/src/notation/types.ts +79 -0
- package/src/queries/NodeContext.ts +280 -0
- package/src/queries/findCallsInFunction.ts +249 -0
- package/src/queries/findContainingFunction.ts +124 -0
- package/src/queries/index.ts +44 -0
- package/src/queries/traceDataflow.ts +838 -0
- package/src/queries/traceValues.ts +531 -0
- package/src/queries/types.ts +191 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +895 -0
- package/src/storage/backends/typeValidation.ts +154 -0
- package/src/utils/findRfdbBinary.ts +288 -0
- package/src/utils/lazyDownload.ts +206 -0
- package/src/utils/moduleResolution.ts +271 -0
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/utils/startRfdbServer.ts +197 -0
- package/src/validation/PathValidator.ts +334 -0
- package/src/version.ts +28 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataflow BFS Tracing
|
|
3
|
+
*
|
|
4
|
+
* Full BFS-based dataflow analysis with 100% reachability.
|
|
5
|
+
* Forward: 8 heuristics for complete data flow propagation.
|
|
6
|
+
* Backward: structural descent + PA matching + THROWS propagation.
|
|
7
|
+
*
|
|
8
|
+
* Shared by MCP handler and CLI trace command.
|
|
9
|
+
*
|
|
10
|
+
* @module queries/traceDataflow
|
|
11
|
+
*/
|
|
12
|
+
// === CONSTANTS ===
|
|
13
|
+
const MUTATION_METHODS = new Set([
|
|
14
|
+
'push', 'unshift', 'splice', 'set', 'add', 'append', 'insert', 'enqueue', 'prepend',
|
|
15
|
+
]);
|
|
16
|
+
const STRUCTURAL_EDGE_TYPES = ['HAS_PROPERTY', 'HAS_ELEMENT', 'HAS_CONSEQUENT', 'HAS_ALTERNATE'];
|
|
17
|
+
// === SHARED HELPERS ===
|
|
18
|
+
/** Resolve a node ID through REFERENCE → READS_FROM to reach the declaration. */
|
|
19
|
+
async function resolveRef(db, nodeId) {
|
|
20
|
+
const node = await db.getNode(nodeId);
|
|
21
|
+
if (!node)
|
|
22
|
+
return null;
|
|
23
|
+
if (node.type === 'REFERENCE') {
|
|
24
|
+
const edges = await db.getOutgoingEdges(nodeId, ['READS_FROM']);
|
|
25
|
+
return edges.length > 0 ? edges[0].dst : null;
|
|
26
|
+
}
|
|
27
|
+
return nodeId;
|
|
28
|
+
}
|
|
29
|
+
/** Extract .index from edge metadata (PASSES_ARGUMENT/RECEIVES_ARGUMENT). */
|
|
30
|
+
function edgeIndex(edge) {
|
|
31
|
+
if (edge.metadata && typeof edge.metadata.index === 'number')
|
|
32
|
+
return edge.metadata.index;
|
|
33
|
+
if (typeof edge.index === 'number')
|
|
34
|
+
return edge.index;
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/** Check if two argument-index values match (undefined = wildcard). */
|
|
38
|
+
function indexMatch(paIdx, raIdx) {
|
|
39
|
+
if (paIdx === undefined || raIdx === undefined)
|
|
40
|
+
return true;
|
|
41
|
+
return paIdx === raIdx;
|
|
42
|
+
}
|
|
43
|
+
/** Resolve a PROPERTY_ACCESS receiver chain: walk READS_FROM to find base var + dot-path. */
|
|
44
|
+
async function resolveReceiverChain(db, paId) {
|
|
45
|
+
const pathParts = [];
|
|
46
|
+
let cur = paId;
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
while (cur && !seen.has(cur)) {
|
|
49
|
+
seen.add(cur);
|
|
50
|
+
const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
|
|
51
|
+
if (!rf.length)
|
|
52
|
+
break;
|
|
53
|
+
const t = await db.getNode(rf[0].dst);
|
|
54
|
+
if (!t)
|
|
55
|
+
break;
|
|
56
|
+
cur = rf[0].dst;
|
|
57
|
+
if (t.type === 'PROPERTY_ACCESS') {
|
|
58
|
+
if (t.name)
|
|
59
|
+
pathParts.push(t.name);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (t.type === 'REFERENCE') {
|
|
63
|
+
const re = await db.getOutgoingEdges(t.id, ['READS_FROM']);
|
|
64
|
+
if (re.length) {
|
|
65
|
+
const r = await db.getNode(re[0].dst);
|
|
66
|
+
if (r && (r.type === 'CONSTANT' || r.type === 'VARIABLE')) {
|
|
67
|
+
return { base: r.id, path: pathParts.join('.') };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (t.type === 'CONSTANT' || t.type === 'VARIABLE') {
|
|
73
|
+
return { base: t.id, path: pathParts.join('.') };
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/** Resolve dynamic callee: follow ASSIGNED_FROM/READS_FROM chains to find the actual FUNCTION. */
|
|
80
|
+
async function resolveDynamicCallee(db, nodeId) {
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
let cur = nodeId;
|
|
83
|
+
while (cur && !seen.has(cur)) {
|
|
84
|
+
seen.add(cur);
|
|
85
|
+
const n = await db.getNode(cur);
|
|
86
|
+
if (!n)
|
|
87
|
+
return null;
|
|
88
|
+
if (n.type === 'FUNCTION')
|
|
89
|
+
return cur;
|
|
90
|
+
if (n.type === 'REFERENCE') {
|
|
91
|
+
const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
|
|
92
|
+
if (rf.length) {
|
|
93
|
+
cur = rf[0].dst;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (n.type === 'PARAMETER' || n.type === 'VARIABLE' || n.type === 'CONSTANT') {
|
|
99
|
+
const afs = await db.getOutgoingEdges(cur, ['ASSIGNED_FROM']);
|
|
100
|
+
for (const af of afs) {
|
|
101
|
+
const afN = await db.getNode(af.dst);
|
|
102
|
+
if (afN?.type === 'FUNCTION')
|
|
103
|
+
return af.dst;
|
|
104
|
+
if (afN?.type === 'REFERENCE') {
|
|
105
|
+
const result = await resolveDynamicCallee(db, af.dst);
|
|
106
|
+
if (result)
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (n.type === 'PARAMETER') {
|
|
111
|
+
const raIn = await db.getIncomingEdges(cur, ['RECEIVES_ARGUMENT']);
|
|
112
|
+
for (const ra of raIn) {
|
|
113
|
+
const fnId = ra.src;
|
|
114
|
+
const raIdx = edgeIndex(ra);
|
|
115
|
+
for (const ci of await db.getIncomingEdges(fnId, ['CALLS'])) {
|
|
116
|
+
const pas = await db.getOutgoingEdges(ci.src, ['PASSES_ARGUMENT']);
|
|
117
|
+
for (const pa of pas) {
|
|
118
|
+
if (!indexMatch(raIdx, edgeIndex(pa)))
|
|
119
|
+
continue;
|
|
120
|
+
const paN = await db.getNode(pa.dst);
|
|
121
|
+
if (paN?.type === 'FUNCTION')
|
|
122
|
+
return pa.dst;
|
|
123
|
+
if (paN?.type === 'REFERENCE') {
|
|
124
|
+
const result = await resolveDynamicCallee(db, pa.dst);
|
|
125
|
+
if (result)
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (n.type === 'PROPERTY_ACCESS') {
|
|
135
|
+
const rf = await db.getOutgoingEdges(cur, ['READS_FROM']);
|
|
136
|
+
if (rf.length) {
|
|
137
|
+
cur = rf[0].dst;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// === FORWARD BFS ===
|
|
147
|
+
/**
|
|
148
|
+
* Forward BFS dataflow trace.
|
|
149
|
+
* Starting from a declaration node, finds all nodes where data flows TO.
|
|
150
|
+
* Returns all reached nodes (including the start node at index 0).
|
|
151
|
+
*/
|
|
152
|
+
export async function traceForwardBFS(db, startId, maxIterations) {
|
|
153
|
+
const visited = new Set();
|
|
154
|
+
const queue = [];
|
|
155
|
+
const reachedNodes = [];
|
|
156
|
+
function enq(id) {
|
|
157
|
+
if (id && !visited.has(id)) {
|
|
158
|
+
visited.add(id);
|
|
159
|
+
queue.push(id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Guard sets for helper functions
|
|
163
|
+
const processedCalls = new Set();
|
|
164
|
+
const processedFns = new Set();
|
|
165
|
+
const climbProcessed = new Set();
|
|
166
|
+
// Lazy indexes
|
|
167
|
+
let paReadByName = null;
|
|
168
|
+
let catchParamIds = null;
|
|
169
|
+
/** Lazy index: "file::receiverName" → CALL node IDs for method calls on that receiver. */
|
|
170
|
+
let callsByReceiver = null;
|
|
171
|
+
async function getPAReadByName() {
|
|
172
|
+
if (paReadByName)
|
|
173
|
+
return paReadByName;
|
|
174
|
+
paReadByName = new Map();
|
|
175
|
+
for await (const n of db.queryNodes({ type: 'PROPERTY_ACCESS' })) {
|
|
176
|
+
const wt = await db.getOutgoingEdges(n.id, ['WRITES_TO']);
|
|
177
|
+
if (wt.length === 0 && n.name) {
|
|
178
|
+
const chain = await resolveReceiverChain(db, n.id);
|
|
179
|
+
if (chain) {
|
|
180
|
+
let list = paReadByName.get(n.name);
|
|
181
|
+
if (!list) {
|
|
182
|
+
list = [];
|
|
183
|
+
paReadByName.set(n.name, list);
|
|
184
|
+
}
|
|
185
|
+
list.push({ id: n.id, base: chain.base, path: chain.path });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return paReadByName;
|
|
190
|
+
}
|
|
191
|
+
async function getCatchParams() {
|
|
192
|
+
if (catchParamIds)
|
|
193
|
+
return catchParamIds;
|
|
194
|
+
catchParamIds = [];
|
|
195
|
+
for await (const p of db.queryNodes({ type: 'PARAMETER' })) {
|
|
196
|
+
const decls = await db.getIncomingEdges(p.id, ['DECLARES']);
|
|
197
|
+
for (const d of decls) {
|
|
198
|
+
const scope = await db.getNode(d.src);
|
|
199
|
+
if (scope?.name === 'catch') {
|
|
200
|
+
catchParamIds.push(p.id);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return catchParamIds;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build index of CALL nodes keyed by "file::receiverName".
|
|
209
|
+
* Used to find method calls on a declaration when CALL→DERIVED_FROM→PA→READS_FROM→REF
|
|
210
|
+
* edges are missing. CALL names encode the receiver: "db.getNode" → receiver "db".
|
|
211
|
+
*/
|
|
212
|
+
async function getCallsByReceiver() {
|
|
213
|
+
if (callsByReceiver)
|
|
214
|
+
return callsByReceiver;
|
|
215
|
+
callsByReceiver = new Map();
|
|
216
|
+
for await (const c of db.queryNodes({ type: 'CALL' })) {
|
|
217
|
+
if (c.name?.includes('.') && c.file) {
|
|
218
|
+
const dotIdx = c.name.indexOf('.');
|
|
219
|
+
const receiver = c.name.substring(0, dotIdx);
|
|
220
|
+
// Skip computed receivers like "<obj>.method"
|
|
221
|
+
if (receiver.startsWith('<'))
|
|
222
|
+
continue;
|
|
223
|
+
const key = `${c.file}::${receiver}`;
|
|
224
|
+
let list = callsByReceiver.get(key);
|
|
225
|
+
if (!list) {
|
|
226
|
+
list = [];
|
|
227
|
+
callsByReceiver.set(key, list);
|
|
228
|
+
}
|
|
229
|
+
list.push(c.id);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return callsByReceiver;
|
|
233
|
+
}
|
|
234
|
+
// --- Helper: enqueue call result consumers ---
|
|
235
|
+
async function enqueueCallConsumers(callId) {
|
|
236
|
+
if (processedCalls.has(callId))
|
|
237
|
+
return;
|
|
238
|
+
processedCalls.add(callId);
|
|
239
|
+
for (const af of await db.getIncomingEdges(callId, ['ASSIGNED_FROM']))
|
|
240
|
+
enq(af.src);
|
|
241
|
+
// Structural climb
|
|
242
|
+
for (const se of await db.getIncomingEdges(callId, [...STRUCTURAL_EDGE_TYPES])) {
|
|
243
|
+
await enqueueClimb(se.src, 3);
|
|
244
|
+
}
|
|
245
|
+
// CALL result passed as arg to another CALL
|
|
246
|
+
for (const pa of await db.getIncomingEdges(callId, ['PASSES_ARGUMENT'])) {
|
|
247
|
+
for (const ce of await db.getOutgoingEdges(pa.src, ['CALLS'])) {
|
|
248
|
+
for (const ra of await db.getOutgoingEdges(ce.dst, ['RECEIVES_ARGUMENT']))
|
|
249
|
+
enq(ra.dst);
|
|
250
|
+
}
|
|
251
|
+
// Mutation on call result
|
|
252
|
+
for (const df of await db.getOutgoingEdges(pa.src, ['DERIVED_FROM'])) {
|
|
253
|
+
const dfN = await db.getNode(df.dst);
|
|
254
|
+
if (dfN?.type === 'PROPERTY_ACCESS' && dfN.name && MUTATION_METHODS.has(dfN.name)) {
|
|
255
|
+
for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
|
|
256
|
+
enq(await resolveRef(db, rf.dst));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Outer call result
|
|
261
|
+
await enqueueCallConsumers(pa.src);
|
|
262
|
+
}
|
|
263
|
+
// Chained calls: another CALL uses this as callee (fn()())
|
|
264
|
+
for (const df of await db.getIncomingEdges(callId, ['DERIVED_FROM'])) {
|
|
265
|
+
const dfN = await db.getNode(df.src);
|
|
266
|
+
if (dfN?.type === 'CALL')
|
|
267
|
+
await enqueueCallConsumers(df.src);
|
|
268
|
+
}
|
|
269
|
+
// CALL result returned by FUNCTION
|
|
270
|
+
for (const ret of await db.getIncomingEdges(callId, ['RETURNS'])) {
|
|
271
|
+
await enqueueFnCallers(ret.src);
|
|
272
|
+
}
|
|
273
|
+
// CALL result read via PA (call().value)
|
|
274
|
+
for (const rf of await db.getIncomingEdges(callId, ['READS_FROM'])) {
|
|
275
|
+
const rfN = await db.getNode(rf.src);
|
|
276
|
+
if (rfN?.type === 'PROPERTY_ACCESS')
|
|
277
|
+
enq(rf.src);
|
|
278
|
+
}
|
|
279
|
+
// Dynamic callee resolution
|
|
280
|
+
const callsEdges = await db.getOutgoingEdges(callId, ['CALLS']);
|
|
281
|
+
let hasStaticCallee = false;
|
|
282
|
+
for (const ce of callsEdges) {
|
|
283
|
+
const ceN = await db.getNode(ce.dst);
|
|
284
|
+
if (ceN?.type === 'FUNCTION') {
|
|
285
|
+
hasStaticCallee = true;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!hasStaticCallee) {
|
|
290
|
+
for (const df of await db.getOutgoingEdges(callId, ['DERIVED_FROM'])) {
|
|
291
|
+
const resolved = await resolveDynamicCallee(db, df.dst);
|
|
292
|
+
if (resolved) {
|
|
293
|
+
for (const ra of await db.getOutgoingEdges(resolved, ['RECEIVES_ARGUMENT']))
|
|
294
|
+
enq(ra.dst);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const ce of callsEdges) {
|
|
298
|
+
const ceN = await db.getNode(ce.dst);
|
|
299
|
+
if (ceN?.type === 'PARAMETER') {
|
|
300
|
+
const resolved = await resolveDynamicCallee(db, ce.dst);
|
|
301
|
+
if (resolved) {
|
|
302
|
+
for (const ra of await db.getOutgoingEdges(resolved, ['RECEIVES_ARGUMENT']))
|
|
303
|
+
enq(ra.dst);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Callback injection: if CALL passes a FUNCTION as argument, trace into callback params
|
|
309
|
+
for (const pa of await db.getOutgoingEdges(callId, ['PASSES_ARGUMENT'])) {
|
|
310
|
+
const paN = await db.getNode(pa.dst);
|
|
311
|
+
if (paN?.type === 'FUNCTION') {
|
|
312
|
+
for (const ra of await db.getOutgoingEdges(pa.dst, ['RECEIVES_ARGUMENT']))
|
|
313
|
+
enq(ra.dst);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// --- Helper: enqueue callers of a function ---
|
|
318
|
+
async function enqueueFnCallers(fnId) {
|
|
319
|
+
if (processedFns.has(fnId))
|
|
320
|
+
return;
|
|
321
|
+
processedFns.add(fnId);
|
|
322
|
+
for (const ci of await db.getIncomingEdges(fnId, ['CALLS'])) {
|
|
323
|
+
await enqueueCallConsumers(ci.src);
|
|
324
|
+
}
|
|
325
|
+
for (const fpa of await db.getIncomingEdges(fnId, ['PASSES_ARGUMENT'])) {
|
|
326
|
+
await enqueueCallConsumers(fpa.src);
|
|
327
|
+
}
|
|
328
|
+
for (const faf of await db.getIncomingEdges(fnId, ['ASSIGNED_FROM'])) {
|
|
329
|
+
for (const vc of await db.getIncomingEdges(faf.src, ['CALLS'])) {
|
|
330
|
+
await enqueueCallConsumers(vc.src);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const df of await db.getIncomingEdges(fnId, ['DERIVED_FROM'])) {
|
|
334
|
+
const dfN = await db.getNode(df.src);
|
|
335
|
+
if (dfN?.type === 'CALL')
|
|
336
|
+
await enqueueCallConsumers(df.src);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// --- Helper: climb structural containers ---
|
|
340
|
+
async function enqueueClimb(nodeId, maxClimb) {
|
|
341
|
+
if (maxClimb <= 0 || climbProcessed.has(nodeId))
|
|
342
|
+
return;
|
|
343
|
+
climbProcessed.add(nodeId);
|
|
344
|
+
enq(nodeId);
|
|
345
|
+
for (const af of await db.getIncomingEdges(nodeId, ['ASSIGNED_FROM']))
|
|
346
|
+
enq(af.src);
|
|
347
|
+
for (const ret of await db.getIncomingEdges(nodeId, ['RETURNS']))
|
|
348
|
+
await enqueueFnCallers(ret.src);
|
|
349
|
+
for (const se of await db.getIncomingEdges(nodeId, [...STRUCTURAL_EDGE_TYPES, 'PASSES_ARGUMENT'])) {
|
|
350
|
+
await enqueueClimb(se.src, maxClimb - 1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// --- Helper: follow PASSES_ARGUMENT to callee params + mutation + call result ---
|
|
354
|
+
async function followPassesArgument(pa) {
|
|
355
|
+
const callId = pa.src;
|
|
356
|
+
const paIdx = edgeIndex(pa);
|
|
357
|
+
// A. To params (with index matching)
|
|
358
|
+
for (const ce of await db.getOutgoingEdges(callId, ['CALLS'])) {
|
|
359
|
+
const raEdges = await db.getOutgoingEdges(ce.dst, ['RECEIVES_ARGUMENT']);
|
|
360
|
+
for (const ra of raEdges) {
|
|
361
|
+
if (!indexMatch(paIdx, edgeIndex(ra)))
|
|
362
|
+
continue;
|
|
363
|
+
enq(ra.dst);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// B. Mutation detection
|
|
367
|
+
for (const df of await db.getOutgoingEdges(callId, ['DERIVED_FROM'])) {
|
|
368
|
+
const dfN = await db.getNode(df.dst);
|
|
369
|
+
if (dfN?.type === 'PROPERTY_ACCESS' && dfN.name && MUTATION_METHODS.has(dfN.name)) {
|
|
370
|
+
for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
|
|
371
|
+
enq(await resolveRef(db, rf.dst));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// C. Call result consumers
|
|
376
|
+
await enqueueCallConsumers(callId);
|
|
377
|
+
}
|
|
378
|
+
// === Main BFS loop ===
|
|
379
|
+
enq(startId);
|
|
380
|
+
let iterations = 0;
|
|
381
|
+
while (queue.length > 0) {
|
|
382
|
+
const declId = queue.shift();
|
|
383
|
+
iterations++;
|
|
384
|
+
if (iterations > maxIterations)
|
|
385
|
+
break;
|
|
386
|
+
const node = await db.getNode(declId);
|
|
387
|
+
if (!node)
|
|
388
|
+
continue;
|
|
389
|
+
reachedNodes.push(node);
|
|
390
|
+
// --- 1. Follow refs that read this declaration ---
|
|
391
|
+
const refsToDecl = await db.getIncomingEdges(declId, ['READS_FROM']);
|
|
392
|
+
for (const refEdge of refsToDecl) {
|
|
393
|
+
const refId = refEdge.src;
|
|
394
|
+
// 1a. ASSIGNED_FROM incoming on ref
|
|
395
|
+
for (const af of await db.getIncomingEdges(refId, ['ASSIGNED_FROM']))
|
|
396
|
+
enq(af.src);
|
|
397
|
+
// 1b. WRITES_TO incoming on ref
|
|
398
|
+
for (const wt of await db.getIncomingEdges(refId, ['WRITES_TO'])) {
|
|
399
|
+
enq(await resolveRef(db, wt.src));
|
|
400
|
+
}
|
|
401
|
+
// 1c. PASSES_ARGUMENT incoming on ref
|
|
402
|
+
for (const pa of await db.getIncomingEdges(refId, ['PASSES_ARGUMENT'])) {
|
|
403
|
+
await followPassesArgument(pa);
|
|
404
|
+
}
|
|
405
|
+
// 1d. Structural climb
|
|
406
|
+
for (const se of await db.getIncomingEdges(refId, [...STRUCTURAL_EDGE_TYPES])) {
|
|
407
|
+
await enqueueClimb(se.src, 4);
|
|
408
|
+
}
|
|
409
|
+
// 1e. DERIVED_FROM incoming on ref: EXPRESSION/CALL uses this ref
|
|
410
|
+
for (const df of await db.getIncomingEdges(refId, ['DERIVED_FROM'])) {
|
|
411
|
+
const dfN = await db.getNode(df.src);
|
|
412
|
+
if (dfN) {
|
|
413
|
+
if (dfN.type === 'EXPRESSION')
|
|
414
|
+
enq(df.src);
|
|
415
|
+
else if (dfN.type === 'CALL')
|
|
416
|
+
await enqueueCallConsumers(df.src);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// 1f. RETURNS / YIELDS — enqueue function callers + function itself
|
|
420
|
+
for (const ret of await db.getIncomingEdges(refId, ['RETURNS'])) {
|
|
421
|
+
await enqueueFnCallers(ret.src);
|
|
422
|
+
enq(ret.src);
|
|
423
|
+
}
|
|
424
|
+
for (const y of await db.getIncomingEdges(refId, ['YIELDS'])) {
|
|
425
|
+
await enqueueFnCallers(y.src);
|
|
426
|
+
enq(y.src);
|
|
427
|
+
}
|
|
428
|
+
// 1g. THROWS → all catch PARAMETERs
|
|
429
|
+
const throwsEdges = await db.getIncomingEdges(refId, ['THROWS']);
|
|
430
|
+
if (throwsEdges.length > 0) {
|
|
431
|
+
const cps = await getCatchParams();
|
|
432
|
+
for (const cpId of cps)
|
|
433
|
+
enq(cpId);
|
|
434
|
+
}
|
|
435
|
+
// 1h. ITERATES_OVER incoming on ref
|
|
436
|
+
for (const io of await db.getIncomingEdges(refId, ['ITERATES_OVER']))
|
|
437
|
+
enq(io.src);
|
|
438
|
+
// 1i. PA reads from ref (receiver chain)
|
|
439
|
+
for (const paRead of await db.getIncomingEdges(refId, ['READS_FROM'])) {
|
|
440
|
+
const paN = await db.getNode(paRead.src);
|
|
441
|
+
if (paN?.type === 'PROPERTY_ACCESS')
|
|
442
|
+
enq(paRead.src);
|
|
443
|
+
}
|
|
444
|
+
// 1j. DERIVED_FROM incoming (CALL consumers)
|
|
445
|
+
for (const df of await db.getIncomingEdges(refId, ['DERIVED_FROM'])) {
|
|
446
|
+
const dfN = await db.getNode(df.src);
|
|
447
|
+
if (dfN?.type === 'CALL')
|
|
448
|
+
await enqueueCallConsumers(df.src);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// --- 2. Direct edges on declaration ---
|
|
452
|
+
for (const hel of await db.getIncomingEdges(declId, ['HAS_ELEMENT']))
|
|
453
|
+
enq(hel.src);
|
|
454
|
+
for (const hp of await db.getIncomingEdges(declId, ['HAS_PROPERTY']))
|
|
455
|
+
enq(hp.src);
|
|
456
|
+
for (const ret of await db.getIncomingEdges(declId, ['RETURNS']))
|
|
457
|
+
await enqueueFnCallers(ret.src);
|
|
458
|
+
for (const y of await db.getIncomingEdges(declId, ['YIELDS']))
|
|
459
|
+
await enqueueFnCallers(y.src);
|
|
460
|
+
for (const io of await db.getIncomingEdges(declId, ['ITERATES_OVER']))
|
|
461
|
+
enq(io.src);
|
|
462
|
+
for (const af of await db.getIncomingEdges(declId, ['ASSIGNED_FROM']))
|
|
463
|
+
enq(af.src);
|
|
464
|
+
// WRITES_TO incoming on declaration
|
|
465
|
+
for (const wt of await db.getIncomingEdges(declId, ['WRITES_TO'])) {
|
|
466
|
+
enq(await resolveRef(db, wt.src));
|
|
467
|
+
}
|
|
468
|
+
// PASSES_ARGUMENT incoming on declaration
|
|
469
|
+
for (const pa of await db.getIncomingEdges(declId, ['PASSES_ARGUMENT'])) {
|
|
470
|
+
await followPassesArgument(pa);
|
|
471
|
+
}
|
|
472
|
+
// --- 2b. Method call receiver heuristic ---
|
|
473
|
+
// When graph lacks CALL→DERIVED_FROM→PA→READS_FROM→REF edges for method calls,
|
|
474
|
+
// use CALL naming convention ("db.getNode" → receiver "db") to find method calls
|
|
475
|
+
// on this declaration and trace their consumers.
|
|
476
|
+
if ((node.type === 'CONSTANT' || node.type === 'VARIABLE' || node.type === 'PARAMETER') && node.name && node.file) {
|
|
477
|
+
const idx = await getCallsByReceiver();
|
|
478
|
+
const key = `${node.file}::${node.name}`;
|
|
479
|
+
const methodCalls = idx.get(key);
|
|
480
|
+
if (methodCalls) {
|
|
481
|
+
for (const callId of methodCalls)
|
|
482
|
+
enq(callId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// --- 3. PA write→read propagation via receiver chain matching ---
|
|
486
|
+
if (node.type === 'PROPERTY_ACCESS') {
|
|
487
|
+
const myChain = await resolveReceiverChain(db, declId);
|
|
488
|
+
if (myChain) {
|
|
489
|
+
const wt = await db.getOutgoingEdges(declId, ['WRITES_TO']);
|
|
490
|
+
if (wt.length > 0 && node.name) {
|
|
491
|
+
const idx = await getPAReadByName();
|
|
492
|
+
const readers = idx.get(node.name) || [];
|
|
493
|
+
for (const r of readers) {
|
|
494
|
+
if (r.base === myChain.base && r.path === myChain.path)
|
|
495
|
+
enq(r.id);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// --- 4. DERIVED_FROM incoming on declaration ---
|
|
501
|
+
for (const df of await db.getIncomingEdges(declId, ['DERIVED_FROM'])) {
|
|
502
|
+
const dfN = await db.getNode(df.src);
|
|
503
|
+
if (dfN?.type === 'CALL')
|
|
504
|
+
await enqueueCallConsumers(df.src);
|
|
505
|
+
else if (dfN?.type === 'EXPRESSION')
|
|
506
|
+
enq(df.src);
|
|
507
|
+
}
|
|
508
|
+
// --- 5. FUNCTION type → enqueueFnCallers ---
|
|
509
|
+
if (node.type === 'FUNCTION')
|
|
510
|
+
await enqueueFnCallers(declId);
|
|
511
|
+
// --- 6. CALL type → enqueueCallConsumers ---
|
|
512
|
+
if (node.type === 'CALL')
|
|
513
|
+
await enqueueCallConsumers(declId);
|
|
514
|
+
}
|
|
515
|
+
return reachedNodes;
|
|
516
|
+
}
|
|
517
|
+
// === BACKWARD BFS ===
|
|
518
|
+
/**
|
|
519
|
+
* Backward BFS dataflow trace.
|
|
520
|
+
* Starting from a declaration node, finds all nodes where data comes FROM.
|
|
521
|
+
* Returns all reached nodes (including the start node at index 0).
|
|
522
|
+
*/
|
|
523
|
+
export async function traceBackwardBFS(db, startId, maxIterations) {
|
|
524
|
+
const visited = new Set();
|
|
525
|
+
const queue = [];
|
|
526
|
+
const reachedNodes = [];
|
|
527
|
+
function enq(id) {
|
|
528
|
+
if (id && !visited.has(id)) {
|
|
529
|
+
visited.add(id);
|
|
530
|
+
queue.push(id);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Lazy PA writer index
|
|
534
|
+
let paWriterByName = null;
|
|
535
|
+
async function getPAWriterByName() {
|
|
536
|
+
if (paWriterByName)
|
|
537
|
+
return paWriterByName;
|
|
538
|
+
paWriterByName = new Map();
|
|
539
|
+
for await (const n of db.queryNodes({ type: 'PROPERTY_ACCESS' })) {
|
|
540
|
+
const wt = await db.getOutgoingEdges(n.id, ['WRITES_TO']);
|
|
541
|
+
if (wt.length > 0 && n.name) {
|
|
542
|
+
const chain = await resolveReceiverChain(db, n.id);
|
|
543
|
+
if (chain) {
|
|
544
|
+
let list = paWriterByName.get(n.name);
|
|
545
|
+
if (!list) {
|
|
546
|
+
list = [];
|
|
547
|
+
paWriterByName.set(n.name, list);
|
|
548
|
+
}
|
|
549
|
+
list.push({ id: n.id, base: chain.base, path: chain.path });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return paWriterByName;
|
|
554
|
+
}
|
|
555
|
+
enq(startId);
|
|
556
|
+
let iterations = 0;
|
|
557
|
+
while (queue.length > 0) {
|
|
558
|
+
const nodeId = queue.shift();
|
|
559
|
+
iterations++;
|
|
560
|
+
if (iterations > maxIterations)
|
|
561
|
+
break;
|
|
562
|
+
const node = await db.getNode(nodeId);
|
|
563
|
+
if (!node)
|
|
564
|
+
continue;
|
|
565
|
+
reachedNodes.push(node);
|
|
566
|
+
// 1. ASSIGNED_FROM outgoing → resolve ref → enqueue source
|
|
567
|
+
for (const af of await db.getOutgoingEdges(nodeId, ['ASSIGNED_FROM'])) {
|
|
568
|
+
const src = await resolveRef(db, af.dst);
|
|
569
|
+
enq(src);
|
|
570
|
+
const afNode = await db.getNode(af.dst);
|
|
571
|
+
if (afNode && !['CONSTANT', 'VARIABLE', 'PARAMETER', 'REFERENCE'].includes(afNode.type)) {
|
|
572
|
+
for (const ch of await db.getOutgoingEdges(af.dst, [...STRUCTURAL_EDGE_TYPES])) {
|
|
573
|
+
const resolved = await resolveRef(db, ch.dst);
|
|
574
|
+
enq(resolved);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// 2. Refs that write to this node → trace back to what was written
|
|
579
|
+
const refsToNode = await db.getIncomingEdges(nodeId, ['READS_FROM']);
|
|
580
|
+
for (const refEdge of refsToNode) {
|
|
581
|
+
const wtEdges = await db.getOutgoingEdges(refEdge.src, ['WRITES_TO']);
|
|
582
|
+
for (const wt of wtEdges) {
|
|
583
|
+
const resolved = await resolveRef(db, wt.dst);
|
|
584
|
+
enq(resolved);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// 3. PARAMETER: incoming RECEIVES_ARGUMENT → FUNCTION → incoming CALLS → CALL → PASSES_ARGUMENT
|
|
588
|
+
if (node.type === 'PARAMETER') {
|
|
589
|
+
const raEdges = await db.getIncomingEdges(nodeId, ['RECEIVES_ARGUMENT']);
|
|
590
|
+
for (const ra of raEdges) {
|
|
591
|
+
const fnId = ra.src;
|
|
592
|
+
const raIdx = edgeIndex(ra);
|
|
593
|
+
const callsIn = await db.getIncomingEdges(fnId, ['CALLS']);
|
|
594
|
+
for (const callEdge of callsIn) {
|
|
595
|
+
const paEdges = await db.getOutgoingEdges(callEdge.src, ['PASSES_ARGUMENT']);
|
|
596
|
+
for (const pa of paEdges) {
|
|
597
|
+
if (!indexMatch(raIdx, edgeIndex(pa)))
|
|
598
|
+
continue;
|
|
599
|
+
const argSrc = await resolveRef(db, pa.dst);
|
|
600
|
+
enq(argSrc);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// 4. Structural descent: outgoing HAS_ELEMENT, HAS_PROPERTY → descend, resolveRef
|
|
606
|
+
for (const ch of await db.getOutgoingEdges(nodeId, ['HAS_ELEMENT', 'HAS_PROPERTY'])) {
|
|
607
|
+
const resolved = await resolveRef(db, ch.dst);
|
|
608
|
+
enq(resolved);
|
|
609
|
+
}
|
|
610
|
+
// 5. CALL node: trace arguments and receiver chain
|
|
611
|
+
if (node.type === 'CALL') {
|
|
612
|
+
for (const pa of await db.getOutgoingEdges(nodeId, ['PASSES_ARGUMENT'])) {
|
|
613
|
+
const argSrc = await resolveRef(db, pa.dst);
|
|
614
|
+
enq(argSrc);
|
|
615
|
+
}
|
|
616
|
+
for (const df of await db.getOutgoingEdges(nodeId, ['DERIVED_FROM'])) {
|
|
617
|
+
const dfN = await db.getNode(df.dst);
|
|
618
|
+
if (dfN?.type === 'PROPERTY_ACCESS') {
|
|
619
|
+
for (const rf of await db.getOutgoingEdges(df.dst, ['READS_FROM'])) {
|
|
620
|
+
const resolved = await resolveRef(db, rf.dst);
|
|
621
|
+
enq(resolved);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
// 5b. Receiver heuristic: when CALL→DERIVED_FROM→PA→READS_FROM chain is missing,
|
|
626
|
+
// parse receiver name from CALL name ("db.getNode" → "db") and find its declaration.
|
|
627
|
+
if (node.name?.includes('.')) {
|
|
628
|
+
const dotIdx = node.name.indexOf('.');
|
|
629
|
+
const receiverName = node.name.substring(0, dotIdx);
|
|
630
|
+
if (!receiverName.startsWith('<')) {
|
|
631
|
+
for (const declType of ['CONSTANT', 'VARIABLE', 'PARAMETER']) {
|
|
632
|
+
for await (const decl of db.queryNodes({ type: declType, name: receiverName })) {
|
|
633
|
+
if (decl.file === node.file)
|
|
634
|
+
enq(decl.id);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// 6. PA node: READS_FROM → resolveRef (the receiver's data source)
|
|
641
|
+
if (node.type === 'PROPERTY_ACCESS') {
|
|
642
|
+
for (const rf of await db.getOutgoingEdges(nodeId, ['READS_FROM'])) {
|
|
643
|
+
const resolved = await resolveRef(db, rf.dst);
|
|
644
|
+
enq(resolved);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// 7. ITERATES_OVER outgoing → resolveRef → enqueue (the iterable)
|
|
648
|
+
for (const io of await db.getOutgoingEdges(nodeId, ['ITERATES_OVER'])) {
|
|
649
|
+
const resolved = await resolveRef(db, io.dst);
|
|
650
|
+
enq(resolved);
|
|
651
|
+
}
|
|
652
|
+
// 8. Property read→write propagation: if this is a PA reader, find matching writers
|
|
653
|
+
if (node.type === 'PROPERTY_ACCESS' && node.name) {
|
|
654
|
+
const wt = await db.getOutgoingEdges(nodeId, ['WRITES_TO']);
|
|
655
|
+
if (wt.length === 0) {
|
|
656
|
+
const myChain = await resolveReceiverChain(db, nodeId);
|
|
657
|
+
if (myChain) {
|
|
658
|
+
const idx = await getPAWriterByName();
|
|
659
|
+
const writers = idx.get(node.name) || [];
|
|
660
|
+
for (const w of writers) {
|
|
661
|
+
if (w.base === myChain.base && w.path === myChain.path)
|
|
662
|
+
enq(w.id);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// 9. THROWS: if this is a catch PARAMETER, find thrown values
|
|
668
|
+
if (node.type === 'PARAMETER') {
|
|
669
|
+
const decls = await db.getIncomingEdges(nodeId, ['DECLARES']);
|
|
670
|
+
let isCatch = false;
|
|
671
|
+
for (const d of decls) {
|
|
672
|
+
const scope = await db.getNode(d.src);
|
|
673
|
+
if (scope?.name === 'catch') {
|
|
674
|
+
isCatch = true;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (isCatch) {
|
|
679
|
+
for await (const fn of db.queryNodes({ type: 'FUNCTION' })) {
|
|
680
|
+
for (const t of await db.getOutgoingEdges(fn.id, ['THROWS'])) {
|
|
681
|
+
const resolved = await resolveRef(db, t.dst);
|
|
682
|
+
enq(resolved);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for await (const mod of db.queryNodes({ type: 'MODULE' })) {
|
|
686
|
+
for (const t of await db.getOutgoingEdges(mod.id, ['THROWS'])) {
|
|
687
|
+
const resolved = await resolveRef(db, t.dst);
|
|
688
|
+
enq(resolved);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// 10. DERIVED_FROM outgoing: trace expression operands
|
|
694
|
+
for (const df of await db.getOutgoingEdges(nodeId, ['DERIVED_FROM'])) {
|
|
695
|
+
const resolved = await resolveRef(db, df.dst);
|
|
696
|
+
enq(resolved);
|
|
697
|
+
}
|
|
698
|
+
// 11. RETURNS incoming → if someone returns this node, trace the function's callers' arguments
|
|
699
|
+
for (const ret of await db.getIncomingEdges(nodeId, ['RETURNS'])) {
|
|
700
|
+
const callsIn = await db.getIncomingEdges(ret.src, ['CALLS']);
|
|
701
|
+
for (const callEdge of callsIn) {
|
|
702
|
+
for (const pa of await db.getOutgoingEdges(callEdge.src, ['PASSES_ARGUMENT'])) {
|
|
703
|
+
const argSrc = await resolveRef(db, pa.dst);
|
|
704
|
+
enq(argSrc);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return reachedNodes;
|
|
710
|
+
}
|
|
711
|
+
// === HIGH-LEVEL API ===
|
|
712
|
+
/**
|
|
713
|
+
* Trace dataflow from a starting node.
|
|
714
|
+
* Handles REFERENCE resolution, runs BFS in requested direction(s).
|
|
715
|
+
* Returns results with start node excluded from reached lists.
|
|
716
|
+
*/
|
|
717
|
+
export async function traceDataflow(db, sourceId, options = {}) {
|
|
718
|
+
const { direction = 'forward', maxDepth = 10, limit } = options;
|
|
719
|
+
const maxIterations = Math.min((maxDepth || 10) * 100, 5000);
|
|
720
|
+
// Resolve REFERENCE to declaration
|
|
721
|
+
let startId = sourceId;
|
|
722
|
+
const sourceNode = await db.getNode(sourceId);
|
|
723
|
+
if (sourceNode?.type === 'REFERENCE') {
|
|
724
|
+
const edges = await db.getOutgoingEdges(sourceId, ['READS_FROM']);
|
|
725
|
+
if (edges.length > 0)
|
|
726
|
+
startId = edges[0].dst;
|
|
727
|
+
}
|
|
728
|
+
const resolvedStart = await db.getNode(startId);
|
|
729
|
+
if (!resolvedStart)
|
|
730
|
+
return [];
|
|
731
|
+
const results = [];
|
|
732
|
+
if (direction === 'forward' || direction === 'both') {
|
|
733
|
+
const nodes = await traceForwardBFS(db, startId, maxIterations);
|
|
734
|
+
const reached = nodes.slice(1); // skip start node
|
|
735
|
+
results.push({
|
|
736
|
+
direction: 'forward',
|
|
737
|
+
startNode: resolvedStart,
|
|
738
|
+
reached: limit ? reached.slice(0, limit) : reached,
|
|
739
|
+
totalReached: reached.length,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
if (direction === 'backward' || direction === 'both') {
|
|
743
|
+
const nodes = await traceBackwardBFS(db, startId, maxIterations);
|
|
744
|
+
const reached = nodes.slice(1);
|
|
745
|
+
results.push({
|
|
746
|
+
direction: 'backward',
|
|
747
|
+
startNode: resolvedStart,
|
|
748
|
+
reached: limit ? reached.slice(0, limit) : reached,
|
|
749
|
+
totalReached: reached.length,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return results;
|
|
753
|
+
}
|
|
754
|
+
//# sourceMappingURL=traceDataflow.js.map
|