circle-ir 3.8.4 → 3.9.8

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 (178) 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/deep-inheritance-pass.d.ts +30 -0
  57. package/dist/analysis/passes/deep-inheritance-pass.js +82 -0
  58. package/dist/analysis/passes/deep-inheritance-pass.js.map +1 -0
  59. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  60. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  61. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  62. package/dist/analysis/passes/infinite-loop-pass.d.ts +31 -0
  63. package/dist/analysis/passes/infinite-loop-pass.js +126 -0
  64. package/dist/analysis/passes/infinite-loop-pass.js.map +1 -0
  65. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  66. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  67. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  68. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  69. package/dist/analysis/passes/language-sources-pass.js +491 -0
  70. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  71. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  72. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  73. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  74. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  75. package/dist/analysis/passes/missing-await-pass.js +90 -0
  76. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  77. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  78. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  79. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  80. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  81. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  82. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  83. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  84. package/dist/analysis/passes/null-deref-pass.js +130 -0
  85. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  86. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  87. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  88. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  89. package/dist/analysis/passes/react-inline-jsx-pass.d.ts +36 -0
  90. package/dist/analysis/passes/react-inline-jsx-pass.js +140 -0
  91. package/dist/analysis/passes/react-inline-jsx-pass.js.map +1 -0
  92. package/dist/analysis/passes/redundant-loop-pass.d.ts +30 -0
  93. package/dist/analysis/passes/redundant-loop-pass.js +146 -0
  94. package/dist/analysis/passes/redundant-loop-pass.js.map +1 -0
  95. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  96. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  97. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  98. package/dist/analysis/passes/serial-await-pass.d.ts +36 -0
  99. package/dist/analysis/passes/serial-await-pass.js +132 -0
  100. package/dist/analysis/passes/serial-await-pass.js.map +1 -0
  101. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  102. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  103. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  104. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  105. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  106. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  107. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  108. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  109. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  110. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  111. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  112. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  113. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  114. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  115. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  116. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  117. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  118. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  119. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  120. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  121. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  122. package/dist/analysis/passes/unbounded-collection-pass.d.ts +32 -0
  123. package/dist/analysis/passes/unbounded-collection-pass.js +128 -0
  124. package/dist/analysis/passes/unbounded-collection-pass.js.map +1 -0
  125. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  126. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  127. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  128. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  129. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  130. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  131. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  132. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  133. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  134. package/dist/analysis/path-finder.d.ts +3 -13
  135. package/dist/analysis/path-finder.js +48 -63
  136. package/dist/analysis/path-finder.js.map +1 -1
  137. package/dist/analysis/taint-matcher.js +8 -1
  138. package/dist/analysis/taint-matcher.js.map +1 -1
  139. package/dist/analysis/taint-propagation.d.ts +5 -1
  140. package/dist/analysis/taint-propagation.js +44 -41
  141. package/dist/analysis/taint-propagation.js.map +1 -1
  142. package/dist/analyzer.d.ts +48 -1
  143. package/dist/analyzer.js +252 -1476
  144. package/dist/analyzer.js.map +1 -1
  145. package/dist/browser/circle-ir.js +3952 -1270
  146. package/dist/core/circle-ir-core.cjs +360 -106
  147. package/dist/core/circle-ir-core.js +360 -106
  148. package/dist/core/extractors/imports.js +18 -0
  149. package/dist/core/extractors/imports.js.map +1 -1
  150. package/dist/graph/analysis-pass.d.ts +68 -0
  151. package/dist/graph/analysis-pass.js +51 -0
  152. package/dist/graph/analysis-pass.js.map +1 -0
  153. package/dist/graph/code-graph.d.ts +92 -0
  154. package/dist/graph/code-graph.js +262 -0
  155. package/dist/graph/code-graph.js.map +1 -0
  156. package/dist/graph/dominator-graph.d.ts +53 -0
  157. package/dist/graph/dominator-graph.js +256 -0
  158. package/dist/graph/dominator-graph.js.map +1 -0
  159. package/dist/graph/import-graph.d.ts +33 -0
  160. package/dist/graph/import-graph.js +170 -0
  161. package/dist/graph/import-graph.js.map +1 -0
  162. package/dist/graph/index.d.ts +5 -0
  163. package/dist/graph/index.js +6 -0
  164. package/dist/graph/index.js.map +1 -0
  165. package/dist/graph/project-graph.d.ts +43 -0
  166. package/dist/graph/project-graph.js +80 -0
  167. package/dist/graph/project-graph.js.map +1 -0
  168. package/dist/graph/scope-graph.d.ts +63 -0
  169. package/dist/graph/scope-graph.js +89 -0
  170. package/dist/graph/scope-graph.js.map +1 -0
  171. package/dist/index.d.ts +3 -2
  172. package/dist/index.js +3 -1
  173. package/dist/index.js.map +1 -1
  174. package/dist/resolution/cross-file.js +52 -19
  175. package/dist/resolution/cross-file.js.map +1 -1
  176. package/dist/types/index.d.ts +151 -0
  177. package/docs/SPEC.md +10 -6
  178. package/package.json +1 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Pass #21: resource-leak (CWE-772, category: reliability)
3
+ *
4
+ * Detects I/O resources (streams, connections, sockets) that are opened but
5
+ * not closed on all exit paths. Unclosed resources exhaust file descriptors
6
+ * or connection pools and cause subtle failures under load.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Find resource-opening calls: known constructors (FileInputStream, etc.)
10
+ * or factory methods (open, createReadStream, etc.).
11
+ * 2. Get the variable bound to the resource from DFG defs at the open line.
12
+ * 3. Within the enclosing method, look for a close()/dispose() call whose
13
+ * receiver matches the resource variable.
14
+ * 4a. No close call found → definite leak (high, error).
15
+ * 4b. Close found but no `finally` keyword in the method after the open
16
+ * → potential leak (medium, warning): an exception skips the close.
17
+ *
18
+ * Note: Java try-with-resources generates no explicit close() in the source;
19
+ * the pass treats the absence of both an explicit close AND a finally as a
20
+ * definite leak.
21
+ */
22
+ /** Constructors that produce closeable resources. */
23
+ const RESOURCE_CTORS = new Set([
24
+ // Java IO
25
+ 'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
26
+ 'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
27
+ 'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
28
+ 'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
29
+ 'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
30
+ // Java NIO
31
+ 'FileChannel',
32
+ // Java Net
33
+ 'Socket', 'ServerSocket', 'DatagramSocket',
34
+ ]);
35
+ /** Factory / open methods that return closeable resources. */
36
+ const RESOURCE_FACTORY_METHODS = new Set([
37
+ // Java NIO/IO
38
+ 'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
39
+ 'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
40
+ // Python built-in
41
+ 'open',
42
+ // Node.js streams
43
+ 'createReadStream', 'createWriteStream', 'createConnection',
44
+ ]);
45
+ /** Methods that properly release a resource. */
46
+ const CLOSE_METHODS = new Set([
47
+ 'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
48
+ 'shutdownNow', 'terminate',
49
+ ]);
50
+ export class ResourceLeakPass {
51
+ name = 'resource-leak';
52
+ category = 'reliability';
53
+ run(ctx) {
54
+ const { graph, code } = ctx;
55
+ const file = graph.ir.meta.file;
56
+ const codeLines = code.split('\n');
57
+ const leaks = [];
58
+ for (const call of graph.ir.calls) {
59
+ const name = call.method_name;
60
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
61
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
62
+ if (!isConstructor && !isFactory)
63
+ continue;
64
+ const openLine = call.location.line;
65
+ // Resource must be captured in a variable to be trackable
66
+ const defs = graph.defsAtLine(openLine);
67
+ if (defs.length === 0)
68
+ continue;
69
+ const resourceVar = defs[0].variable;
70
+ // Limit search to the enclosing method
71
+ const methodInfo = graph.methodAtLine(openLine);
72
+ if (!methodInfo)
73
+ continue;
74
+ const methodEnd = methodInfo.method.end_line;
75
+ // Look for a close() call on this resource within the method
76
+ const closeCall = graph.ir.calls.find(c => CLOSE_METHODS.has(c.method_name) &&
77
+ c.receiver === resourceVar &&
78
+ c.location.line > openLine &&
79
+ c.location.line <= methodEnd);
80
+ const snippet = (codeLines[openLine - 1] ?? '').trim();
81
+ if (!closeCall) {
82
+ // Also accept try-with-resources or with-statement as implicit close
83
+ if (this.hasTryWithResources(codeLines, openLine, methodEnd))
84
+ continue;
85
+ // Definite leak: resource is never explicitly released
86
+ leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'definite' });
87
+ ctx.addFinding({
88
+ id: `resource-leak-${file}-${openLine}`,
89
+ pass: this.name,
90
+ category: this.category,
91
+ rule_id: this.name,
92
+ cwe: 'CWE-772',
93
+ severity: 'high',
94
+ level: 'error',
95
+ message: `Resource leak: \`${name}\` assigned to '${resourceVar}' at line ${openLine} ` +
96
+ `is never closed — file descriptors or connections may be exhausted`,
97
+ file,
98
+ line: openLine,
99
+ snippet,
100
+ fix: `Use try-with-resources (Java 7+): \`try (${name} ${resourceVar} = ...) { ... }\`, ` +
101
+ `or call \`${resourceVar}.close()\` in a finally block`,
102
+ evidence: { resource: name, variable: resourceVar },
103
+ });
104
+ continue;
105
+ }
106
+ // Close found — check if it is protected by a finally block
107
+ if (this.hasFinallyBlock(codeLines, openLine, methodEnd))
108
+ continue;
109
+ // Potential leak: close() exists but may be skipped on exception
110
+ leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'potential' });
111
+ ctx.addFinding({
112
+ id: `resource-leak-${file}-${openLine}`,
113
+ pass: this.name,
114
+ category: this.category,
115
+ rule_id: this.name,
116
+ cwe: 'CWE-772',
117
+ severity: 'medium',
118
+ level: 'warning',
119
+ message: `Potential resource leak: \`${name}\` ('${resourceVar}') is closed at ` +
120
+ `line ${closeCall.location.line} but not inside a finally block — ` +
121
+ `an exception could skip the close`,
122
+ file,
123
+ line: openLine,
124
+ snippet,
125
+ fix: `Move \`${resourceVar}.close()\` into a finally block, or use try-with-resources`,
126
+ evidence: {
127
+ resource: name,
128
+ variable: resourceVar,
129
+ close_line: closeCall.location.line,
130
+ },
131
+ });
132
+ }
133
+ return { leaks };
134
+ }
135
+ /** True if a `finally` keyword appears in the method body after the open line. */
136
+ hasFinallyBlock(lines, fromLine, toLine) {
137
+ for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
138
+ if (/\bfinally\b/.test(lines[l - 1] ?? ''))
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+ /**
144
+ * True if a try-with-resources or Python `with` statement wraps the resource,
145
+ * indicating implicit close. Detects `try (` or `with open(` patterns.
146
+ */
147
+ hasTryWithResources(lines, fromLine, toLine) {
148
+ for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
149
+ const text = lines[l - 1] ?? '';
150
+ if (/\btry\s*\(/.test(text) || /\bwith\b.*\bopen\b/.test(text))
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+ }
156
+ //# sourceMappingURL=resource-leak-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-leak-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/resource-leak-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pass #32: serial-await (category: performance)
3
+ *
4
+ * Detects sequential `await` expressions in JavaScript/TypeScript where the
5
+ * two awaited operations have no data dependency — they could be parallelised
6
+ * with `Promise.all()`.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Per function (group lines by enclosing method via `graph.methodAtLine()`),
10
+ * scan lines in order for `await` patterns.
11
+ * 2. For consecutive pairs `(line1, line2)`:
12
+ * a. Find the DFG def created at `line1` (if any) — this is the result
13
+ * variable bound to the first await.
14
+ * b. Check whether that variable name appears verbatim in the source
15
+ * line at `line2`.
16
+ * c. Also check whether any def on `line2` appears in `line1`'s source.
17
+ * d. If neither direction has a textual dependency: the two awaits are
18
+ * independent.
19
+ * 3. If a function has ≥ 2 independent consecutive awaits: emit one finding
20
+ * per function at the first independent pair's line.
21
+ *
22
+ * Languages: JavaScript and TypeScript only.
23
+ */
24
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
25
+ export interface SerialAwaitResult {
26
+ serialAwaits: Array<{
27
+ functionLine: number;
28
+ firstAwaitLine: number;
29
+ secondAwaitLine: number;
30
+ }>;
31
+ }
32
+ export declare class SerialAwaitPass implements AnalysisPass<SerialAwaitResult> {
33
+ readonly name = "serial-await";
34
+ readonly category: "performance";
35
+ run(ctx: PassContext): SerialAwaitResult;
36
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Pass #32: serial-await (category: performance)
3
+ *
4
+ * Detects sequential `await` expressions in JavaScript/TypeScript where the
5
+ * two awaited operations have no data dependency — they could be parallelised
6
+ * with `Promise.all()`.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Per function (group lines by enclosing method via `graph.methodAtLine()`),
10
+ * scan lines in order for `await` patterns.
11
+ * 2. For consecutive pairs `(line1, line2)`:
12
+ * a. Find the DFG def created at `line1` (if any) — this is the result
13
+ * variable bound to the first await.
14
+ * b. Check whether that variable name appears verbatim in the source
15
+ * line at `line2`.
16
+ * c. Also check whether any def on `line2` appears in `line1`'s source.
17
+ * d. If neither direction has a textual dependency: the two awaits are
18
+ * independent.
19
+ * 3. If a function has ≥ 2 independent consecutive awaits: emit one finding
20
+ * per function at the first independent pair's line.
21
+ *
22
+ * Languages: JavaScript and TypeScript only.
23
+ */
24
+ /** Matches: const/let/var? varName = await or bare await */
25
+ const AWAIT_ASSIGN_RE = /(?:const|let|var)?\s*(\w+)\s*=\s*await\s/;
26
+ const AWAIT_RE = /\bawait\s/;
27
+ export class SerialAwaitPass {
28
+ name = 'serial-await';
29
+ category = 'performance';
30
+ run(ctx) {
31
+ const { graph, code, language } = ctx;
32
+ if (language !== 'javascript' && language !== 'typescript') {
33
+ return { serialAwaits: [] };
34
+ }
35
+ const file = graph.ir.meta.file;
36
+ const codeLines = code.split('\n');
37
+ const totalLines = codeLines.length;
38
+ const serialAwaits = [];
39
+ const reportedFunctions = new Set();
40
+ // Collect all await lines
41
+ const awaitLines = [];
42
+ for (let i = 0; i < totalLines; i++) {
43
+ const lineText = codeLines[i];
44
+ if (!AWAIT_RE.test(lineText))
45
+ continue;
46
+ const m = AWAIT_ASSIGN_RE.exec(lineText);
47
+ const boundVar = m ? m[1] : null;
48
+ awaitLines.push({ line: i + 1, boundVar });
49
+ }
50
+ if (awaitLines.length < 2)
51
+ return { serialAwaits: [] };
52
+ // Check consecutive pairs
53
+ for (let i = 0; i + 1 < awaitLines.length; i++) {
54
+ const a1 = awaitLines[i];
55
+ const a2 = awaitLines[i + 1];
56
+ // Must be in the same function
57
+ const method1 = graph.methodAtLine(a1.line);
58
+ const method2 = graph.methodAtLine(a2.line);
59
+ const methodKey1 = method1
60
+ ? `${method1.type.name}.${method1.method.name}.${method1.method.start_line}`
61
+ : `top.${a1.line}`;
62
+ const methodKey2 = method2
63
+ ? `${method2.type.name}.${method2.method.name}.${method2.method.start_line}`
64
+ : `top.${a2.line}`;
65
+ if (methodKey1 !== methodKey2)
66
+ continue;
67
+ // Skip if lines are not consecutive (allow up to 3 lines apart for formatting)
68
+ if (a2.line - a1.line > 4)
69
+ continue;
70
+ // Check dependency: does line2 reference the variable bound by line1?
71
+ const line2Text = codeLines[a2.line - 1] ?? '';
72
+ const line1Text = codeLines[a1.line - 1] ?? '';
73
+ let dependent = false;
74
+ // Forward dependency: var from line1 used in line2
75
+ if (a1.boundVar && new RegExp(`\\b${a1.boundVar}\\b`).test(line2Text)) {
76
+ dependent = true;
77
+ }
78
+ // Reverse dependency: var from line2's def used in line1 (rare but possible)
79
+ if (!dependent && a2.boundVar && new RegExp(`\\b${a2.boundVar}\\b`).test(line1Text)) {
80
+ dependent = true;
81
+ }
82
+ // DFG-level check: any def at line2 whose variable appears in defs of line1 args
83
+ if (!dependent) {
84
+ const defs1 = graph.defsAtLine(a1.line);
85
+ const defs2 = graph.defsAtLine(a2.line);
86
+ for (const d1 of defs1) {
87
+ for (const d2 of defs2) {
88
+ if (d1.variable === d2.variable) {
89
+ dependent = true;
90
+ break;
91
+ }
92
+ }
93
+ if (dependent)
94
+ break;
95
+ }
96
+ }
97
+ if (dependent)
98
+ continue;
99
+ // Skip if already reported for this function
100
+ if (reportedFunctions.has(methodKey1))
101
+ continue;
102
+ reportedFunctions.add(methodKey1);
103
+ const funcLine = method1?.method.start_line ?? a1.line;
104
+ serialAwaits.push({ functionLine: funcLine, firstAwaitLine: a1.line, secondAwaitLine: a2.line });
105
+ // Extract readable names from the await expressions
106
+ const expr1 = line1Text.trim().replace(/^(?:const|let|var)\s+/, '');
107
+ const expr2 = line2Text.trim().replace(/^(?:const|let|var)\s+/, '');
108
+ ctx.addFinding({
109
+ id: `serial-await-${file}-${a1.line}`,
110
+ pass: this.name,
111
+ category: this.category,
112
+ rule_id: this.name,
113
+ cwe: undefined,
114
+ severity: 'low',
115
+ level: 'note',
116
+ message: `Serial awaits: \`${expr1}\` (line ${a1.line}) and \`${expr2}\` (line ${a2.line}) ` +
117
+ `have no data dependency; consider using Promise.all()`,
118
+ file,
119
+ line: a1.line,
120
+ end_line: a2.line,
121
+ fix: `const [result1, result2] = await Promise.all([operation1, operation2]);`,
122
+ evidence: {
123
+ first_await_line: a1.line,
124
+ second_await_line: a2.line,
125
+ function_line: funcLine,
126
+ },
127
+ });
128
+ }
129
+ return { serialAwaits };
130
+ }
131
+ }
132
+ //# sourceMappingURL=serial-await-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serial-await-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/serial-await-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SinkFilterPass
3
+ *
4
+ * Applies the four-stage sink filtering pipeline to eliminate false positives,
5
+ * followed by language-specific XPath/XSS suppression.
6
+ *
7
+ * Filter stages (applied in order):
8
+ * 1. Dead code — remove sinks on unreachable lines
9
+ * 2. Clean array elements — strong updates via constant propagation
10
+ * 3. Clean variables — arguments proven non-tainted by constant propagation
11
+ * 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
12
+ * 5. Python XPath FP reduction
13
+ * 6. JavaScript XSS FP reduction
14
+ *
15
+ * Depends on: taint-matcher, constant-propagation, language-sources
16
+ */
17
+ import type { TaintSource, TaintSink, TaintSanitizer } from '../../types/index.js';
18
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
19
+ export interface SinkFilterResult {
20
+ /** Merged sources: taint-matcher + language-sources. */
21
+ sources: TaintSource[];
22
+ /** Filtered sinks. */
23
+ sinks: TaintSink[];
24
+ sanitizers: TaintSanitizer[];
25
+ }
26
+ export declare class SinkFilterPass implements AnalysisPass<SinkFilterResult> {
27
+ readonly name = "sink-filter";
28
+ readonly category: "security";
29
+ run(ctx: PassContext): SinkFilterResult;
30
+ }
31
+ import type { CircleIR } from '../../types/index.js';
32
+ type Symbols = Map<string, {
33
+ value: string | number | boolean | null;
34
+ type: string;
35
+ sourceLine: number;
36
+ }>;
37
+ export declare function filterCleanVariableSinks(sinks: CircleIR['taint']['sinks'], calls: CircleIR['calls'], taintedVars: Set<string>, symbols: Symbols, dfg?: CircleIR['dfg'], sanitizedVars?: Set<string>, synchronizedLines?: Set<number>): CircleIR['taint']['sinks'];
38
+ export declare function filterSanitizedSinks(sinks: CircleIR['taint']['sinks'], sanitizers: CircleIR['taint']['sanitizers'], calls: CircleIR['calls']): CircleIR['taint']['sinks'];
39
+ export {};
@@ -0,0 +1,231 @@
1
+ /**
2
+ * SinkFilterPass
3
+ *
4
+ * Applies the four-stage sink filtering pipeline to eliminate false positives,
5
+ * followed by language-specific XPath/XSS suppression.
6
+ *
7
+ * Filter stages (applied in order):
8
+ * 1. Dead code — remove sinks on unreachable lines
9
+ * 2. Clean array elements — strong updates via constant propagation
10
+ * 3. Clean variables — arguments proven non-tainted by constant propagation
11
+ * 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
12
+ * 5. Python XPath FP reduction
13
+ * 6. JavaScript XSS FP reduction
14
+ *
15
+ * Depends on: taint-matcher, constant-propagation, language-sources
16
+ */
17
+ import { JS_TAINTED_PATTERNS } from './language-sources-pass.js';
18
+ export class SinkFilterPass {
19
+ name = 'sink-filter';
20
+ category = 'security';
21
+ run(ctx) {
22
+ const { graph, language } = ctx;
23
+ const { calls, dfg } = graph.ir;
24
+ const taintMatcher = ctx.getResult('taint-matcher');
25
+ const constProp = ctx.getResult('constant-propagation');
26
+ const langSources = ctx.getResult('language-sources');
27
+ // Merge sources and sinks from both upstream passes.
28
+ const sources = [...taintMatcher.sources, ...langSources.additionalSources];
29
+ // Build merged sinks, deduplicating JS DOM sinks that may overlap with config sinks.
30
+ const sinks = [...taintMatcher.sinks];
31
+ for (const s of langSources.additionalSinks) {
32
+ if (!sinks.some(x => x.line === s.line && x.cwe === s.cwe && x.type === s.type)) {
33
+ sinks.push(s);
34
+ }
35
+ }
36
+ const sanitizers = taintMatcher.sanitizers;
37
+ // Stage 1 — dead code
38
+ let filtered = sinks.filter(sink => !constProp.unreachableLines.has(sink.line));
39
+ // Stage 2 — clean array elements
40
+ filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
41
+ // Stage 3 — clean variables
42
+ filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines);
43
+ // Stage 4 — sanitized sinks
44
+ filtered = filterSanitizedSinks(filtered, sanitizers, calls);
45
+ // Stage 5 — Python XPath FP reduction
46
+ if (language === 'python') {
47
+ const { pyTaintedVars, pySanitizedVars } = langSources;
48
+ const sourceLines = ctx.code.split('\n');
49
+ filtered = filtered.filter(sink => {
50
+ if (sink.type !== 'xpath_injection')
51
+ return true;
52
+ const sinkLineText = sourceLines[sink.line - 1] ?? '';
53
+ const taintedVarOnLine = [...pyTaintedVars.keys()].find(v => new RegExp(`\\b${v}\\b`).test(sinkLineText));
54
+ if (!taintedVarOnLine)
55
+ return false;
56
+ if (pySanitizedVars.has(taintedVarOnLine))
57
+ return false;
58
+ if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText))
59
+ return false;
60
+ return true;
61
+ });
62
+ }
63
+ // Stage 6 — JavaScript XSS FP reduction
64
+ if (['javascript', 'typescript'].includes(language)) {
65
+ const { jsTaintedVars } = langSources;
66
+ if (jsTaintedVars.size > 0) {
67
+ const sourceLines = ctx.code.split('\n');
68
+ filtered = filtered.filter(sink => {
69
+ if (sink.type !== 'xss')
70
+ return true;
71
+ const sinkLineText = sourceLines[sink.line - 1] ?? '';
72
+ if ([...jsTaintedVars.keys()].some(v => new RegExp(`\\b${v}\\b`).test(sinkLineText)))
73
+ return true;
74
+ if (JS_TAINTED_PATTERNS.some(p => p.pattern.test(sinkLineText)))
75
+ return true;
76
+ return false;
77
+ });
78
+ }
79
+ }
80
+ return { sources, sinks: filtered, sanitizers };
81
+ }
82
+ }
83
+ function evaluateSimpleExpression(expr, symbols) {
84
+ let evaluated = expr;
85
+ for (const [name, val] of symbols) {
86
+ if (val.type === 'int' || val.type === 'float') {
87
+ const regex = new RegExp(`\\b${name}\\b`, 'g');
88
+ evaluated = evaluated.replace(regex, String(val.value));
89
+ }
90
+ }
91
+ try {
92
+ if (/^[\d\s+\-*/().]+$/.test(evaluated)) {
93
+ const result = Function('"use strict"; return (' + evaluated + ')')();
94
+ if (typeof result === 'number' && !isNaN(result))
95
+ return String(Math.floor(result));
96
+ }
97
+ }
98
+ catch { /* evaluation failed */ }
99
+ return expr;
100
+ }
101
+ function isStringLiteralExpression(expr) {
102
+ const trimmed = expr.trim();
103
+ return (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
104
+ (trimmed.startsWith("'") && trimmed.endsWith("'"));
105
+ }
106
+ function filterCleanArraySinks(sinks, calls, taintedArrayElements, symbols) {
107
+ const callsByLine = new Map();
108
+ for (const call of calls) {
109
+ const existing = callsByLine.get(call.location.line) ?? [];
110
+ existing.push(call);
111
+ callsByLine.set(call.location.line, existing);
112
+ }
113
+ return sinks.filter(sink => {
114
+ const callsAtSink = callsByLine.get(sink.line) ?? [];
115
+ for (const call of callsAtSink) {
116
+ for (const arg of call.arguments) {
117
+ const arrayAccessMatch = arg.expression?.match(/^(\w+)\[(\d+|[^[\]]+)\]$/);
118
+ if (arrayAccessMatch) {
119
+ const arrayName = arrayAccessMatch[1];
120
+ let indexStr = arrayAccessMatch[2];
121
+ indexStr = evaluateSimpleExpression(indexStr, symbols);
122
+ const taintedIndices = taintedArrayElements.get(arrayName);
123
+ if (taintedIndices !== undefined) {
124
+ const isTainted = taintedIndices.has(indexStr) || taintedIndices.has('*');
125
+ if (!isTainted)
126
+ return false;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return true;
132
+ });
133
+ }
134
+ export function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanitizedVars, synchronizedLines) {
135
+ const fieldNames = new Set();
136
+ if (dfg) {
137
+ for (const def of dfg.defs) {
138
+ if (def.kind === 'field')
139
+ fieldNames.add(def.variable);
140
+ }
141
+ }
142
+ const callsByLine = new Map();
143
+ for (const call of calls) {
144
+ const existing = callsByLine.get(call.location.line) ?? [];
145
+ existing.push(call);
146
+ callsByLine.set(call.location.line, existing);
147
+ }
148
+ return sinks.filter(sink => {
149
+ const callsAtSink = callsByLine.get(sink.line) ?? [];
150
+ const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
151
+ for (const call of callsAtSink) {
152
+ let allArgsAreClean = true;
153
+ const methodName = call.in_method;
154
+ for (const arg of call.arguments) {
155
+ if (arg.variable && !arg.expression?.includes('[')) {
156
+ const varName = arg.variable;
157
+ const scopedName = methodName ? `${methodName}:${varName}` : varName;
158
+ if (fieldNames.has(varName) && !isInSynchronizedBlock) {
159
+ allArgsAreClean = false;
160
+ continue;
161
+ }
162
+ if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName))
163
+ continue;
164
+ if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
165
+ allArgsAreClean = false;
166
+ continue;
167
+ }
168
+ const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
169
+ if (symbolValue && symbolValue.type !== 'unknown')
170
+ continue;
171
+ allArgsAreClean = false;
172
+ }
173
+ else {
174
+ if (arg.literal != null)
175
+ continue;
176
+ if (arg.expression && !arg.variable && isStringLiteralExpression(arg.expression))
177
+ continue;
178
+ allArgsAreClean = false;
179
+ }
180
+ }
181
+ if (allArgsAreClean && call.arguments.length > 0)
182
+ return false;
183
+ }
184
+ return true;
185
+ });
186
+ }
187
+ export function filterSanitizedSinks(sinks, sanitizers, calls) {
188
+ if (!sanitizers || sanitizers.length === 0)
189
+ return sinks;
190
+ const sanitizersByLine = new Map();
191
+ for (const san of sanitizers) {
192
+ const existing = sanitizersByLine.get(san.line) ?? [];
193
+ existing.push(san);
194
+ sanitizersByLine.set(san.line, existing);
195
+ }
196
+ const callsByLine = new Map();
197
+ for (const call of calls) {
198
+ const existing = callsByLine.get(call.location.line) ?? [];
199
+ existing.push(call);
200
+ callsByLine.set(call.location.line, existing);
201
+ }
202
+ return sinks.filter(sink => {
203
+ const lineSanitizers = sanitizersByLine.get(sink.line);
204
+ if (!lineSanitizers || lineSanitizers.length === 0)
205
+ return true;
206
+ for (const san of lineSanitizers) {
207
+ if (san.sanitizes.includes(sink.type)) {
208
+ const lineCalls = callsByLine.get(sink.line) ?? [];
209
+ for (const call of lineCalls) {
210
+ for (const arg of call.arguments) {
211
+ const expr = arg.expression || '';
212
+ const sanMethodMatch = san.method.match(/(?:(\w+)\.)?(\w+)\(\)/);
213
+ if (sanMethodMatch) {
214
+ const sanMethodName = sanMethodMatch[2];
215
+ const sanClassName = sanMethodMatch[1];
216
+ if (sanClassName) {
217
+ if (expr.includes(`${sanClassName}.${sanMethodName}(`))
218
+ return false;
219
+ }
220
+ else if (expr.includes(`${sanMethodName}(`)) {
221
+ return false;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ return true;
229
+ });
230
+ }
231
+ //# sourceMappingURL=sink-filter-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink-filter-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/sink-filter-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pass #33: stale-doc-ref
3
+ *
4
+ * Flags doc comment references ({@link ClassName} and @see ClassName) that
5
+ * point to symbols not found in the file's type declarations or imports.
6
+ * Stale doc refs cause confusion and erode documentation trustworthiness.
7
+ *
8
+ * Category: maintainability | Severity: low | Level: note | CWE: none
9
+ */
10
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
11
+ export interface StaleDocRefResult {
12
+ staleRefs: Array<{
13
+ line: number;
14
+ ref: string;
15
+ }>;
16
+ }
17
+ export declare class StaleDocRefPass implements AnalysisPass<StaleDocRefResult> {
18
+ readonly name = "stale-doc-ref";
19
+ readonly category: "maintainability";
20
+ run(ctx: PassContext): StaleDocRefResult;
21
+ }