circle-ir 3.8.4 → 3.9.7

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.
Files changed (157) hide show
  1. package/README.md +82 -5
  2. package/dist/analysis/dfg-verifier.d.ts +3 -14
  3. package/dist/analysis/dfg-verifier.js +43 -74
  4. package/dist/analysis/dfg-verifier.js.map +1 -1
  5. package/dist/analysis/interprocedural.d.ts +5 -1
  6. package/dist/analysis/interprocedural.js +62 -60
  7. package/dist/analysis/interprocedural.js.map +1 -1
  8. package/dist/analysis/metrics/index.d.ts +2 -0
  9. package/dist/analysis/metrics/index.js +2 -0
  10. package/dist/analysis/metrics/index.js.map +1 -0
  11. package/dist/analysis/metrics/metric-pass.d.ts +27 -0
  12. package/dist/analysis/metrics/metric-pass.js +2 -0
  13. package/dist/analysis/metrics/metric-pass.js.map +1 -0
  14. package/dist/analysis/metrics/metric-runner.d.ts +21 -0
  15. package/dist/analysis/metrics/metric-runner.js +47 -0
  16. package/dist/analysis/metrics/metric-runner.js.map +1 -0
  17. package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
  18. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
  19. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
  20. package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
  21. package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
  22. package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
  23. package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
  24. package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
  25. package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
  26. package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
  27. package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
  28. package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
  29. package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
  30. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
  31. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
  32. package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
  33. package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
  34. package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
  35. package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
  36. package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
  37. package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
  38. package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
  39. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
  40. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
  41. package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
  42. package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
  43. package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
  44. package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
  45. package/dist/analysis/passes/circular-dependency-pass.js +39 -0
  46. package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
  47. package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
  48. package/dist/analysis/passes/constant-propagation-pass.js +44 -0
  49. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
  50. package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
  51. package/dist/analysis/passes/cross-file-pass.js +102 -0
  52. package/dist/analysis/passes/cross-file-pass.js.map +1 -0
  53. package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
  54. package/dist/analysis/passes/dead-code-pass.js +117 -0
  55. package/dist/analysis/passes/dead-code-pass.js.map +1 -0
  56. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  57. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  58. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  59. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  60. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  61. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  62. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  63. package/dist/analysis/passes/language-sources-pass.js +491 -0
  64. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  65. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  66. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  67. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  68. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  69. package/dist/analysis/passes/missing-await-pass.js +90 -0
  70. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  71. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  72. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  73. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  74. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  75. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  76. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  77. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  78. package/dist/analysis/passes/null-deref-pass.js +130 -0
  79. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  80. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  81. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  82. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  83. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  84. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  85. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  86. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  87. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  88. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  89. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  90. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  91. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  92. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  93. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  94. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  95. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  96. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  97. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  98. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  99. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  100. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  101. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  102. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  103. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  104. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  105. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  106. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  107. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  108. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  109. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  110. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  111. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  112. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  113. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  114. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  115. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  116. package/dist/analysis/path-finder.d.ts +3 -13
  117. package/dist/analysis/path-finder.js +48 -63
  118. package/dist/analysis/path-finder.js.map +1 -1
  119. package/dist/analysis/taint-matcher.js +8 -1
  120. package/dist/analysis/taint-matcher.js.map +1 -1
  121. package/dist/analysis/taint-propagation.d.ts +5 -1
  122. package/dist/analysis/taint-propagation.js +44 -41
  123. package/dist/analysis/taint-propagation.js.map +1 -1
  124. package/dist/analyzer.d.ts +42 -1
  125. package/dist/analyzer.js +234 -1476
  126. package/dist/analyzer.js.map +1 -1
  127. package/dist/browser/circle-ir.js +3413 -1271
  128. package/dist/core/circle-ir-core.cjs +360 -106
  129. package/dist/core/circle-ir-core.js +360 -106
  130. package/dist/core/extractors/imports.js +18 -0
  131. package/dist/core/extractors/imports.js.map +1 -1
  132. package/dist/graph/analysis-pass.d.ts +68 -0
  133. package/dist/graph/analysis-pass.js +51 -0
  134. package/dist/graph/analysis-pass.js.map +1 -0
  135. package/dist/graph/code-graph.d.ts +92 -0
  136. package/dist/graph/code-graph.js +262 -0
  137. package/dist/graph/code-graph.js.map +1 -0
  138. package/dist/graph/import-graph.d.ts +33 -0
  139. package/dist/graph/import-graph.js +170 -0
  140. package/dist/graph/import-graph.js.map +1 -0
  141. package/dist/graph/index.d.ts +4 -0
  142. package/dist/graph/index.js +5 -0
  143. package/dist/graph/index.js.map +1 -0
  144. package/dist/graph/project-graph.d.ts +43 -0
  145. package/dist/graph/project-graph.js +80 -0
  146. package/dist/graph/project-graph.js.map +1 -0
  147. package/dist/graph/scope-graph.d.ts +63 -0
  148. package/dist/graph/scope-graph.js +89 -0
  149. package/dist/graph/scope-graph.js.map +1 -0
  150. package/dist/index.d.ts +2 -2
  151. package/dist/index.js +1 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/resolution/cross-file.js +52 -19
  154. package/dist/resolution/cross-file.js.map +1 -1
  155. package/dist/types/index.d.ts +151 -0
  156. package/docs/SPEC.md +10 -6
  157. package/package.json +1 -1
@@ -0,0 +1,170 @@
1
+ /**
2
+ * ImportGraph
3
+ *
4
+ * Builds a directed file→file import graph from a ProjectGraph.
5
+ * Only relative imports (from_package starting with '.') are resolved to edges —
6
+ * stdlib and npm package imports are ignored.
7
+ *
8
+ * Used by CircularDependencyPass and OrphanModulePass.
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // Path helpers (no Node.js 'path' module — must run in browser too)
12
+ // ---------------------------------------------------------------------------
13
+ function dirname(filePath) {
14
+ const idx = filePath.lastIndexOf('/');
15
+ return idx >= 0 ? filePath.slice(0, idx) : '';
16
+ }
17
+ function normalizePath(p) {
18
+ const isAbsolute = p.startsWith('/');
19
+ const parts = p.split('/');
20
+ const result = [];
21
+ for (const part of parts) {
22
+ if (part === '' || part === '.')
23
+ continue;
24
+ if (part === '..') {
25
+ result.pop();
26
+ }
27
+ else {
28
+ result.push(part);
29
+ }
30
+ }
31
+ return (isAbsolute ? '/' : '') + result.join('/');
32
+ }
33
+ const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.java', '.rs', ''];
34
+ // Entry-point filenames (base without extension) — not treated as orphans
35
+ const ENTRY_POINT_NAMES = /^(index|main|app|server|mod|cli|bin|start|run|entry|init)$/i;
36
+ // ---------------------------------------------------------------------------
37
+ // ImportGraph
38
+ // ---------------------------------------------------------------------------
39
+ export class ImportGraph {
40
+ /** file → set of files it imports */
41
+ outEdges;
42
+ /** file → set of files that import it */
43
+ inEdges;
44
+ /** All known file paths */
45
+ allFiles;
46
+ constructor(projectGraph) {
47
+ this.outEdges = new Map();
48
+ this.inEdges = new Map();
49
+ this.allFiles = new Set(projectGraph.filePaths);
50
+ // Initialize edge sets for all files
51
+ for (const file of this.allFiles) {
52
+ this.outEdges.set(file, new Set());
53
+ this.inEdges.set(file, new Set());
54
+ }
55
+ // Build edges from imports
56
+ for (const filePath of projectGraph.filePaths) {
57
+ const ir = projectGraph.getIR(filePath);
58
+ if (!ir)
59
+ continue;
60
+ const dir = dirname(filePath);
61
+ for (const imp of ir.imports) {
62
+ const pkg = imp.from_package;
63
+ if (!pkg || !pkg.startsWith('.'))
64
+ continue; // skip stdlib/npm
65
+ // Resolve relative path
66
+ const rawCandidate = dir ? `${dir}/${pkg}` : pkg;
67
+ const base = normalizePath(rawCandidate);
68
+ // Try with each extension
69
+ let resolved = null;
70
+ for (const ext of EXTENSIONS) {
71
+ const candidate = base + ext;
72
+ if (this.allFiles.has(candidate)) {
73
+ resolved = candidate;
74
+ break;
75
+ }
76
+ }
77
+ // TypeScript ESM convention: `import './foo.js'` refers to `./foo.ts` on disk.
78
+ // Strip the .js suffix and retry with TypeScript extensions.
79
+ if (!resolved && base.endsWith('.js')) {
80
+ const stripped = base.slice(0, -3);
81
+ for (const ext of ['.ts', '.tsx', '.js']) {
82
+ const candidate = stripped + ext;
83
+ if (this.allFiles.has(candidate)) {
84
+ resolved = candidate;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ if (resolved && resolved !== filePath) {
90
+ this.outEdges.get(filePath).add(resolved);
91
+ this.inEdges.get(resolved).add(filePath);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ /** Files directly imported by `filePath`. */
97
+ edgesFrom(filePath) {
98
+ return [...(this.outEdges.get(filePath) ?? [])];
99
+ }
100
+ /** Files that directly import `filePath`. */
101
+ edgesTo(filePath) {
102
+ return [...(this.inEdges.get(filePath) ?? [])];
103
+ }
104
+ /**
105
+ * Tarjan's SCC — returns groups of files that form import cycles.
106
+ * Each returned Set has size ≥ 2 (only actual cycles).
107
+ */
108
+ findCycles() {
109
+ const index = new Map();
110
+ const lowlink = new Map();
111
+ const onStack = new Map();
112
+ const stack = [];
113
+ const cycles = [];
114
+ let counter = 0;
115
+ const strongConnect = (v) => {
116
+ index.set(v, counter);
117
+ lowlink.set(v, counter);
118
+ counter++;
119
+ stack.push(v);
120
+ onStack.set(v, true);
121
+ for (const w of (this.outEdges.get(v) ?? [])) {
122
+ if (!index.has(w)) {
123
+ strongConnect(w);
124
+ lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
125
+ }
126
+ else if (onStack.get(w)) {
127
+ lowlink.set(v, Math.min(lowlink.get(v), index.get(w)));
128
+ }
129
+ }
130
+ if (lowlink.get(v) === index.get(v)) {
131
+ // Root of SCC — pop the component
132
+ const scc = new Set();
133
+ let w;
134
+ do {
135
+ w = stack.pop();
136
+ onStack.set(w, false);
137
+ scc.add(w);
138
+ } while (w !== v);
139
+ if (scc.size > 1) {
140
+ cycles.push(scc);
141
+ }
142
+ }
143
+ };
144
+ for (const v of this.allFiles) {
145
+ if (!index.has(v)) {
146
+ strongConnect(v);
147
+ }
148
+ }
149
+ return cycles;
150
+ }
151
+ /**
152
+ * Returns file paths with zero incoming import edges that are not entry points.
153
+ * Entry points: filename base (without extension) matches /^(index|main|app|server|mod)$/i
154
+ */
155
+ findOrphans() {
156
+ const orphans = [];
157
+ for (const file of this.allFiles) {
158
+ if ((this.inEdges.get(file)?.size ?? 0) > 0)
159
+ continue;
160
+ // Check if entry point
161
+ const base = file.split('/').pop() ?? '';
162
+ const baseName = base.includes('.') ? base.slice(0, base.lastIndexOf('.')) : base;
163
+ if (ENTRY_POINT_NAMES.test(baseName))
164
+ continue;
165
+ orphans.push(file);
166
+ }
167
+ return orphans.sort();
168
+ }
169
+ }
170
+ //# sourceMappingURL=import-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph.js","sourceRoot":"","sources":["../../src/graph/import-graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { CodeGraph } from './code-graph.js';
2
+ export { ProjectGraph } from './project-graph.js';
3
+ export { ImportGraph } from './import-graph.js';
4
+ export { AnalysisPipeline, type AnalysisPass, type PassContext, type PipelineRunResult, } from './analysis-pass.js';
@@ -0,0 +1,5 @@
1
+ export { CodeGraph } from './code-graph.js';
2
+ export { ProjectGraph } from './project-graph.js';
3
+ export { ImportGraph } from './import-graph.js';
4
+ export { AnalysisPipeline, } from './analysis-pass.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,gBAAgB,GAIjB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ProjectGraph
3
+ *
4
+ * Wraps multiple per-file CodeGraph instances with lazy cross-file resolution
5
+ * infrastructure (SymbolTable, TypeHierarchyResolver, CrossFileResolver).
6
+ *
7
+ * Usage:
8
+ * const pg = new ProjectGraph();
9
+ * pg.addFile('/src/A.java', graphA);
10
+ * pg.addFile('/src/B.java', graphB);
11
+ * const flows = pg.resolver.findCrossFileTaintFlows();
12
+ */
13
+ import type { CircleIR } from '../types/index.js';
14
+ import { CodeGraph } from './code-graph.js';
15
+ import { SymbolTable, TypeHierarchyResolver, CrossFileResolver } from '../resolution/index.js';
16
+ export declare class ProjectGraph {
17
+ private readonly files;
18
+ private _symbolTable;
19
+ private _typeHierarchy;
20
+ private _resolver;
21
+ /**
22
+ * Register a file's CodeGraph. Invalidates all lazy caches.
23
+ */
24
+ addFile(filePath: string, graph: CodeGraph): void;
25
+ /** Registered file paths in insertion order. */
26
+ get filePaths(): string[];
27
+ /** Total number of registered files. */
28
+ get fileCount(): number;
29
+ /** Retrieve a file's CodeGraph, or undefined if not registered. */
30
+ getGraph(filePath: string): CodeGraph | undefined;
31
+ /** Retrieve a file's CircleIR, or undefined if not registered. */
32
+ getIR(filePath: string): CircleIR | undefined;
33
+ /** Lazily-built SymbolTable covering all registered files. */
34
+ get symbolTable(): SymbolTable;
35
+ /** Lazily-built TypeHierarchyResolver covering all registered files. */
36
+ get typeHierarchy(): TypeHierarchyResolver;
37
+ /**
38
+ * Lazily-built CrossFileResolver.
39
+ * Accesses `this.symbolTable` and `this.typeHierarchy` (also lazy) so all
40
+ * three are computed together on the first call after any `addFile()`.
41
+ */
42
+ get resolver(): CrossFileResolver;
43
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * ProjectGraph
3
+ *
4
+ * Wraps multiple per-file CodeGraph instances with lazy cross-file resolution
5
+ * infrastructure (SymbolTable, TypeHierarchyResolver, CrossFileResolver).
6
+ *
7
+ * Usage:
8
+ * const pg = new ProjectGraph();
9
+ * pg.addFile('/src/A.java', graphA);
10
+ * pg.addFile('/src/B.java', graphB);
11
+ * const flows = pg.resolver.findCrossFileTaintFlows();
12
+ */
13
+ import { SymbolTable, TypeHierarchyResolver, CrossFileResolver, } from '../resolution/index.js';
14
+ export class ProjectGraph {
15
+ files = new Map();
16
+ // Lazy caches — nulled whenever a new file is added
17
+ _symbolTable = null;
18
+ _typeHierarchy = null;
19
+ _resolver = null;
20
+ /**
21
+ * Register a file's CodeGraph. Invalidates all lazy caches.
22
+ */
23
+ addFile(filePath, graph) {
24
+ this.files.set(filePath, { graph, ir: graph.ir });
25
+ this._symbolTable = null;
26
+ this._typeHierarchy = null;
27
+ this._resolver = null;
28
+ }
29
+ /** Registered file paths in insertion order. */
30
+ get filePaths() {
31
+ return [...this.files.keys()];
32
+ }
33
+ /** Total number of registered files. */
34
+ get fileCount() {
35
+ return this.files.size;
36
+ }
37
+ /** Retrieve a file's CodeGraph, or undefined if not registered. */
38
+ getGraph(filePath) {
39
+ return this.files.get(filePath)?.graph;
40
+ }
41
+ /** Retrieve a file's CircleIR, or undefined if not registered. */
42
+ getIR(filePath) {
43
+ return this.files.get(filePath)?.ir;
44
+ }
45
+ /** Lazily-built SymbolTable covering all registered files. */
46
+ get symbolTable() {
47
+ if (!this._symbolTable) {
48
+ const st = new SymbolTable();
49
+ for (const [path, { ir }] of this.files)
50
+ st.addFromIR(ir, path);
51
+ this._symbolTable = st;
52
+ }
53
+ return this._symbolTable;
54
+ }
55
+ /** Lazily-built TypeHierarchyResolver covering all registered files. */
56
+ get typeHierarchy() {
57
+ if (!this._typeHierarchy) {
58
+ const th = new TypeHierarchyResolver();
59
+ for (const [path, { ir }] of this.files)
60
+ th.addFromIR(ir, path);
61
+ this._typeHierarchy = th;
62
+ }
63
+ return this._typeHierarchy;
64
+ }
65
+ /**
66
+ * Lazily-built CrossFileResolver.
67
+ * Accesses `this.symbolTable` and `this.typeHierarchy` (also lazy) so all
68
+ * three are computed together on the first call after any `addFile()`.
69
+ */
70
+ get resolver() {
71
+ if (!this._resolver) {
72
+ const r = new CrossFileResolver(this.symbolTable, this.typeHierarchy);
73
+ for (const [path, { ir }] of this.files)
74
+ r.addFile(path, ir);
75
+ this._resolver = r;
76
+ }
77
+ return this._resolver;
78
+ }
79
+ }
80
+ //# sourceMappingURL=project-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-graph.js","sourceRoot":"","sources":["../../src/graph/project-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,OAAO,YAAY;IACN,KAAK,GAAG,IAAI,GAAG,EAA8C,CAAC;IAE/E,oDAAoD;IAC5C,YAAY,GAAyB,IAAI,CAAC;IAC1C,cAAc,GAAiC,IAAI,CAAC;IACpD,SAAS,GAAkC,IAAI,CAAC;IAExD;;OAEG;IACH,OAAO,CAAC,QAAgB,EAAE,KAAgB;QACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,YAAY,GAAK,IAAI,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAQ,IAAI,CAAC;IAC7B,CAAC;IAED,gDAAgD;IAChD,IAAI,SAAS;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,wCAAwC;IACxC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,mEAAmE;IACnE,QAAQ,CAAC,QAAgB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IACzC,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,QAAgB;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;IACtC,CAAC;IAED,8DAA8D;IAC9D,IAAI,WAAW;QACb,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK;gBAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,wEAAwE;IACxE,IAAI,aAAa;QACf,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,IAAI,qBAAqB,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK;gBAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,IAAI,QAAQ;QACV,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YACtE,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK;gBAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * ScopeGraph
3
+ *
4
+ * Thin wrapper over CodeGraph that enriches each DFGDef with declaration-keyword
5
+ * awareness. Needed by the three Group-3 passes (variable-shadowing, leaked-global,
6
+ * unused-variable) which must distinguish between a "real" variable declaration
7
+ * (`let x = 5`) and a bare reassignment (`x = 5`).
8
+ *
9
+ * The DFG extractor treats both as `kind='local'` because reassignments also
10
+ * create new reaching definitions. ScopeGraph recovers the distinction by
11
+ * scanning the source line for language-specific declaration keywords.
12
+ *
13
+ * Design:
14
+ * - Built once per pass via `new ScopeGraph(graph, code, language)`
15
+ * - O(n) construction where n = number of DFG defs
16
+ * - No mutations — all state is computed in the constructor
17
+ * - Browser + Node.js safe (no platform APIs)
18
+ */
19
+ import type { DFGDef } from '../types/index.js';
20
+ import type { CodeGraph } from './code-graph.js';
21
+ /** Enriched view of a single DFGDef with scope metadata. */
22
+ export interface ScopeEntry {
23
+ /** The underlying DFG definition. */
24
+ readonly def: DFGDef;
25
+ /**
26
+ * True when the source line that created this def contains a declaration
27
+ * keyword appropriate for the language (e.g. `let`/`const`/`var` for JS,
28
+ * type keyword for Java, `let` for Rust). False for bare reassignments.
29
+ */
30
+ readonly hasDeclKeyword: boolean;
31
+ /**
32
+ * `start_line` of the enclosing method, or -1 if the def is at the
33
+ * top/module level (no enclosing method found).
34
+ */
35
+ readonly methodStart: number;
36
+ /**
37
+ * `end_line` of the enclosing method, or -1 if top-level.
38
+ */
39
+ readonly methodEnd: number;
40
+ }
41
+ export declare class ScopeGraph {
42
+ /** One entry per DFGDef in the IR, in original order. */
43
+ readonly entries: ScopeEntry[];
44
+ constructor(graph: CodeGraph, code: string, language: string);
45
+ /**
46
+ * Returns all entries whose def falls within the inclusive range
47
+ * [start, end] (both are 1-based source line numbers).
48
+ */
49
+ defsInMethod(start: number, end: number): ScopeEntry[];
50
+ /**
51
+ * Returns true if the given variable has at least one def with
52
+ * `hasDeclKeyword === true` inside the method whose start line is
53
+ * `methodStart` OR at module level (methodStart === -1).
54
+ *
55
+ * Module-level declarations are included because JavaScript/TypeScript
56
+ * module-level `let`/`const`/`var` variables are legitimately reassigned
57
+ * inside functions — that is not a global leak.
58
+ *
59
+ * Used by `leaked-global` to determine whether a bare assignment is truly
60
+ * undeclared within its enclosing function.
61
+ */
62
+ hasDeclaredDef(variable: string, methodStart: number): boolean;
63
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ScopeGraph
3
+ *
4
+ * Thin wrapper over CodeGraph that enriches each DFGDef with declaration-keyword
5
+ * awareness. Needed by the three Group-3 passes (variable-shadowing, leaked-global,
6
+ * unused-variable) which must distinguish between a "real" variable declaration
7
+ * (`let x = 5`) and a bare reassignment (`x = 5`).
8
+ *
9
+ * The DFG extractor treats both as `kind='local'` because reassignments also
10
+ * create new reaching definitions. ScopeGraph recovers the distinction by
11
+ * scanning the source line for language-specific declaration keywords.
12
+ *
13
+ * Design:
14
+ * - Built once per pass via `new ScopeGraph(graph, code, language)`
15
+ * - O(n) construction where n = number of DFG defs
16
+ * - No mutations — all state is computed in the constructor
17
+ * - Browser + Node.js safe (no platform APIs)
18
+ */
19
+ // ---------------------------------------------------------------------------
20
+ // Per-language declaration keyword patterns
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Returns true when the source line text contains a variable-declaration keyword
24
+ * for the given language.
25
+ *
26
+ * Java/Rust/JS/TS: explicit keywords precede the variable name.
27
+ * Python: no declaration keyword exists → always returns false.
28
+ */
29
+ function hasDeclKeyword(lineText, language) {
30
+ switch (language) {
31
+ case 'java':
32
+ // Primitive types, `var`, `final`, generic types (upper-case first letter)
33
+ return /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText)
34
+ || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText);
35
+ case 'javascript':
36
+ case 'typescript':
37
+ return /\b(?:let|const|var)\s+[\w{[]/.test(lineText);
38
+ case 'rust':
39
+ return /\blet\s+(?:mut\s+)?\w/.test(lineText);
40
+ default:
41
+ // Python and other languages: no reliable declaration keyword
42
+ return false;
43
+ }
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // ScopeGraph
47
+ // ---------------------------------------------------------------------------
48
+ export class ScopeGraph {
49
+ /** One entry per DFGDef in the IR, in original order. */
50
+ entries;
51
+ constructor(graph, code, language) {
52
+ const codeLines = code.split('\n');
53
+ this.entries = graph.ir.dfg.defs.map((def) => {
54
+ const m = graph.methodAtLine(def.line);
55
+ const lineText = codeLines[def.line - 1] ?? '';
56
+ return {
57
+ def,
58
+ hasDeclKeyword: hasDeclKeyword(lineText, language),
59
+ methodStart: m?.method.start_line ?? -1,
60
+ methodEnd: m?.method.end_line ?? -1,
61
+ };
62
+ });
63
+ }
64
+ /**
65
+ * Returns all entries whose def falls within the inclusive range
66
+ * [start, end] (both are 1-based source line numbers).
67
+ */
68
+ defsInMethod(start, end) {
69
+ return this.entries.filter(e => e.def.line >= start && e.def.line <= end);
70
+ }
71
+ /**
72
+ * Returns true if the given variable has at least one def with
73
+ * `hasDeclKeyword === true` inside the method whose start line is
74
+ * `methodStart` OR at module level (methodStart === -1).
75
+ *
76
+ * Module-level declarations are included because JavaScript/TypeScript
77
+ * module-level `let`/`const`/`var` variables are legitimately reassigned
78
+ * inside functions — that is not a global leak.
79
+ *
80
+ * Used by `leaked-global` to determine whether a bare assignment is truly
81
+ * undeclared within its enclosing function.
82
+ */
83
+ hasDeclaredDef(variable, methodStart) {
84
+ return this.entries.some(e => e.def.variable === variable
85
+ && e.hasDeclKeyword
86
+ && (e.methodStart === methodStart || e.methodStart === -1));
87
+ }
88
+ }
89
+ //# sourceMappingURL=scope-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-graph.js","sourceRoot":"","sources":["../../src/graph/scope-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,8EAA8E;AAC9E,4CAA4C;AAC5C,8EAA8E;AAE9E;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,QAAgB,EAAE,QAAgB;IACxD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,2EAA2E;YAC3E,OAAO,iEAAiE,CAAC,IAAI,CAAC,QAAQ,CAAC;mBAChF,6BAA6B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEtD,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY;YACf,OAAO,8BAA8B,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEvD,KAAK,MAAM;YACT,OAAO,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEhD;YACE,8DAA8D;YAC9D,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AA2BD,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,MAAM,OAAO,UAAU;IACrB,yDAAyD;IAChD,OAAO,CAAe;IAE/B,YAAY,KAAgB,EAAE,IAAY,EAAE,QAAgB;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAc,EAAE;YACvD,MAAM,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/C,OAAO;gBACL,GAAG;gBACH,cAAc,EAAE,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC;gBAClD,WAAW,EAAE,CAAC,EAAE,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;gBACvC,SAAS,EAAI,CAAC,EAAE,MAAM,CAAC,QAAQ,IAAM,CAAC,CAAC;aACxC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,KAAa,EAAE,GAAW;QACrC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IAC5E,CAAC;IAED;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,QAAgB,EAAE,WAAmB;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CACtB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ;eAC3B,CAAC,CAAC,cAAc;eAChB,CAAC,CAAC,CAAC,WAAW,KAAK,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAC7D,CAAC;IACJ,CAAC;CACF"}
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * A universal library for static analysis and taint tracking.
5
5
  */
6
- export { initAnalyzer, analyze, analyzeForAPI, isAnalyzerInitialized, resetAnalyzer, type AnalyzerOptions, } from './analyzer.js';
7
- export type { CircleIR, Meta, TypeInfo, MethodInfo, ParameterInfo, FieldInfo, CallInfo, ArgumentInfo, CallResolution, CFG, CFGBlock, CFGEdge, DFG, DFGDef, DFGUse, DFGChain, Taint, TaintSource, TaintSink, TaintSanitizer, ImportInfo, ExportInfo, UnresolvedItem, Enriched, EnrichedFunction, ResolvedCall, Finding, TaintHop, Vulnerability, AnalysisResponse, SourceType, SinkType, Severity, ProjectAnalysis, ProjectMeta, FileAnalysis, TypeHierarchy, ClassHierarchyInfo, InterfaceHierarchyInfo, CrossFileCall, ArgMapping, TaintPath, } from './types/index.js';
6
+ export { initAnalyzer, analyze, analyzeForAPI, analyzeProject, isAnalyzerInitialized, resetAnalyzer, type AnalyzerOptions, } from './analyzer.js';
7
+ export type { CircleIR, Meta, TypeInfo, MethodInfo, ParameterInfo, FieldInfo, CallInfo, ArgumentInfo, CallResolution, CFG, CFGBlock, CFGEdge, DFG, DFGDef, DFGUse, DFGChain, Taint, TaintSource, TaintSink, TaintSanitizer, ImportInfo, ExportInfo, UnresolvedItem, Enriched, EnrichedFunction, ResolvedCall, Finding, TaintHop, Vulnerability, AnalysisResponse, SourceType, SinkType, Severity, PassCategory, SarifLevel, SastFinding, MetricCategory, MetricValue, FileMetrics, ProjectAnalysis, ProjectMeta, FileAnalysis, TypeHierarchy, ClassHierarchyInfo, InterfaceHierarchyInfo, CrossFileCall, ArgMapping, TaintPath, } from './types/index.js';
8
8
  export type { SourceConfig, SinkConfig, TaintConfig, SourcePattern, SinkPattern, SanitizerPattern, } from './types/config.js';
9
9
  export { initParser, parse, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, type SupportedLanguage, type SyntaxNode, type Node, type NodeCache, type Tree, } from './core/index.js';
10
10
  export { extractMeta, extractTypes, extractCalls, extractImports, extractExports, buildCFG, buildDFG, } from './core/index.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * A universal library for static analysis and taint tracking.
5
5
  */
6
6
  // Main analyzer
7
- export { initAnalyzer, analyze, analyzeForAPI, isAnalyzerInitialized, resetAnalyzer, } from './analyzer.js';
7
+ export { initAnalyzer, analyze, analyzeForAPI, analyzeProject, isAnalyzerInitialized, resetAnalyzer, } from './analyzer.js';
8
8
  // Core utilities (for advanced usage)
9
9
  export { initParser, parse, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, } from './core/index.js';
10
10
  // Core extractors
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,gBAAgB;AAChB,OAAO,EACL,YAAY,EACZ,OAAO,EACP,aAAa,EACb,qBAAqB,EACrB,aAAa,GAEd,MAAM,eAAe,CAAC;AA+DvB,sCAAsC;AACtC,OAAO,EACL,UAAU,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,GAMhB,MAAM,iBAAiB,CAAC;AAEzB,kBAAkB;AAClB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,iBAAiB,CAAC;AAEzB,qBAAqB;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,EACb,kBAAkB,GAMnB,MAAM,qBAAqB,CAAC;AAE7B,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,gBAAgB,GAEjB,MAAM,qBAAqB,CAAC;AAE7B,uBAAuB;AACvB,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,mBAAmB;AACnB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAY9B,gCAAgC;AAChC,OAAO,EACL,MAAM,EACN,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,GAIZ,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,gBAAgB;AAChB,OAAO,EACL,YAAY,EACZ,OAAO,EACP,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,aAAa,GAEd,MAAM,eAAe,CAAC;AAuEvB,sCAAsC;AACtC,OAAO,EACL,UAAU,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,GAMhB,MAAM,iBAAiB,CAAC;AAEzB,kBAAkB;AAClB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,iBAAiB,CAAC;AAEzB,qBAAqB;AACrB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,EACb,kBAAkB,GAMnB,MAAM,qBAAqB,CAAC;AAE7B,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,gBAAgB,GAEjB,MAAM,qBAAqB,CAAC;AAE7B,uBAAuB;AACvB,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,mBAAmB;AACnB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAY9B,gCAAgC;AAChC,OAAO,EACL,MAAM,EACN,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,GAIZ,MAAM,mBAAmB,CAAC"}
@@ -336,35 +336,68 @@ export class CrossFileResolver {
336
336
  */
337
337
  findCrossFileTaintFlows() {
338
338
  const flows = [];
339
+ // Deduplicate: same source + target sink should only emit one flow
340
+ const seen = new Set();
339
341
  for (const [filePath, ir] of this.fileIRs) {
340
342
  // Check each source in the file
341
343
  for (const source of ir.taint.sources) {
344
+ // `interprocedural_param` sources represent "this method's parameter MIGHT be
345
+ // tainted when called by tainted code" — they are a per-file meta-analysis result
346
+ // and are not confirmed external inputs suitable for cross-file taint flows.
347
+ // Using them here causes false positives on every internal library function that
348
+ // has typed parameters and happens to call another file that contains any sink.
349
+ if (source.type === 'interprocedural_param')
350
+ continue;
342
351
  // Find calls at or after the source line
343
352
  for (const call of ir.calls) {
344
353
  if (call.location.line < source.line)
345
354
  continue;
346
355
  const resolved = this.resolveCall(call, filePath);
347
- if (resolved && resolved.targetFile !== filePath) {
348
- // This is a cross-file call
349
- // Check if any argument might be tainted
350
- const taintedArgs = [];
351
- for (let i = 0; i < call.arguments.length; i++) {
352
- // Simple heuristic: if variable was defined at or after source, might be tainted
353
- taintedArgs.push(i);
354
- }
355
- if (taintedArgs.length > 0) {
356
- flows.push({
357
- sourceFile: filePath,
358
- sourceLine: source.line,
359
- sourceType: source.type,
360
- targetFile: resolved.targetFile,
361
- targetLine: call.location.line,
362
- targetMethod: resolved.targetMethod,
363
- flowType: 'call_arg',
364
- taintedArgPositions: taintedArgs,
365
- });
356
+ if (!resolved || resolved.targetFile === filePath)
357
+ continue;
358
+ // Only proceed if the target file has any YAML-matched sinks at all.
359
+ // Skipping sink-free files prevents the cartesian explosion of flows into
360
+ // utility modules (AST helpers, string utilities, etc.) that have no
361
+ // dangerous operations.
362
+ const targetIR = this.fileIRs.get(resolved.targetFile);
363
+ if (!targetIR || targetIR.taint.sinks.length === 0)
364
+ continue;
365
+ // Find the target method in the target file so we can locate sinks within it.
366
+ // `resolved.targetMethod` is a FQN like "ClassName.methodName"; we match on
367
+ // the suffix after the last dot.
368
+ const shortName = resolved.targetMethod.split('.').pop() ?? resolved.targetMethod;
369
+ let targetMethod;
370
+ for (const type of targetIR.types) {
371
+ const m = type.methods.find(m => m.name === shortName);
372
+ if (m) {
373
+ targetMethod = m;
374
+ break;
366
375
  }
367
376
  }
377
+ if (!targetMethod)
378
+ continue;
379
+ // Only emit a flow when at least one known sink falls inside the target method.
380
+ // This means `targetLine` now correctly points to an actual dangerous operation
381
+ // in the target file (not the caller's line in the source file).
382
+ const sinksInMethod = targetIR.taint.sinks.filter(s => s.line >= targetMethod.start_line && s.line <= targetMethod.end_line);
383
+ if (sinksInMethod.length === 0)
384
+ continue;
385
+ for (const sink of sinksInMethod) {
386
+ const key = `${filePath}:${source.line}→${resolved.targetFile}:${sink.line}`;
387
+ if (seen.has(key))
388
+ continue;
389
+ seen.add(key);
390
+ flows.push({
391
+ sourceFile: filePath,
392
+ sourceLine: source.line,
393
+ sourceType: source.type,
394
+ targetFile: resolved.targetFile,
395
+ targetLine: sink.line, // actual sink line in target file
396
+ targetMethod: resolved.targetMethod,
397
+ flowType: 'call_arg',
398
+ taintedArgPositions: call.arguments.map((_, i) => i),
399
+ });
400
+ }
368
401
  }
369
402
  }
370
403
  }