circle-ir 3.8.3 → 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 (159) hide show
  1. package/README.md +82 -5
  2. package/dist/analysis/config-loader.js +1 -1
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/dfg-verifier.d.ts +3 -14
  5. package/dist/analysis/dfg-verifier.js +43 -74
  6. package/dist/analysis/dfg-verifier.js.map +1 -1
  7. package/dist/analysis/interprocedural.d.ts +5 -1
  8. package/dist/analysis/interprocedural.js +62 -60
  9. package/dist/analysis/interprocedural.js.map +1 -1
  10. package/dist/analysis/metrics/index.d.ts +2 -0
  11. package/dist/analysis/metrics/index.js +2 -0
  12. package/dist/analysis/metrics/index.js.map +1 -0
  13. package/dist/analysis/metrics/metric-pass.d.ts +27 -0
  14. package/dist/analysis/metrics/metric-pass.js +2 -0
  15. package/dist/analysis/metrics/metric-pass.js.map +1 -0
  16. package/dist/analysis/metrics/metric-runner.d.ts +21 -0
  17. package/dist/analysis/metrics/metric-runner.js +47 -0
  18. package/dist/analysis/metrics/metric-runner.js.map +1 -0
  19. package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
  20. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
  21. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
  22. package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
  23. package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
  24. package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
  25. package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
  26. package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
  27. package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
  28. package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
  29. package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
  30. package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
  31. package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
  32. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
  33. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
  34. package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
  35. package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
  36. package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
  37. package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
  38. package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
  39. package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
  40. package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
  41. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
  42. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
  43. package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
  44. package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
  45. package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
  46. package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
  47. package/dist/analysis/passes/circular-dependency-pass.js +39 -0
  48. package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
  49. package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
  50. package/dist/analysis/passes/constant-propagation-pass.js +44 -0
  51. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
  52. package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
  53. package/dist/analysis/passes/cross-file-pass.js +102 -0
  54. package/dist/analysis/passes/cross-file-pass.js.map +1 -0
  55. package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
  56. package/dist/analysis/passes/dead-code-pass.js +117 -0
  57. package/dist/analysis/passes/dead-code-pass.js.map +1 -0
  58. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  59. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  60. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  61. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  62. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  63. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  64. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  65. package/dist/analysis/passes/language-sources-pass.js +491 -0
  66. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  67. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  68. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  69. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  70. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  71. package/dist/analysis/passes/missing-await-pass.js +90 -0
  72. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  73. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  74. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  75. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  76. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  77. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  78. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  79. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  80. package/dist/analysis/passes/null-deref-pass.js +130 -0
  81. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  82. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  83. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  84. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  85. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  86. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  87. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  88. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  89. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  90. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  91. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  92. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  93. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  94. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  95. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  96. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  97. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  98. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  99. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  100. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  101. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  102. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  103. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  104. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  105. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  106. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  107. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  108. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  109. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  110. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  111. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  112. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  113. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  114. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  115. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  116. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  117. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  118. package/dist/analysis/path-finder.d.ts +3 -13
  119. package/dist/analysis/path-finder.js +48 -63
  120. package/dist/analysis/path-finder.js.map +1 -1
  121. package/dist/analysis/taint-matcher.js +8 -1
  122. package/dist/analysis/taint-matcher.js.map +1 -1
  123. package/dist/analysis/taint-propagation.d.ts +5 -1
  124. package/dist/analysis/taint-propagation.js +44 -41
  125. package/dist/analysis/taint-propagation.js.map +1 -1
  126. package/dist/analyzer.d.ts +42 -1
  127. package/dist/analyzer.js +234 -1476
  128. package/dist/analyzer.js.map +1 -1
  129. package/dist/browser/circle-ir.js +3414 -1272
  130. package/dist/core/circle-ir-core.cjs +361 -107
  131. package/dist/core/circle-ir-core.js +361 -107
  132. package/dist/core/extractors/imports.js +18 -0
  133. package/dist/core/extractors/imports.js.map +1 -1
  134. package/dist/graph/analysis-pass.d.ts +68 -0
  135. package/dist/graph/analysis-pass.js +51 -0
  136. package/dist/graph/analysis-pass.js.map +1 -0
  137. package/dist/graph/code-graph.d.ts +92 -0
  138. package/dist/graph/code-graph.js +262 -0
  139. package/dist/graph/code-graph.js.map +1 -0
  140. package/dist/graph/import-graph.d.ts +33 -0
  141. package/dist/graph/import-graph.js +170 -0
  142. package/dist/graph/import-graph.js.map +1 -0
  143. package/dist/graph/index.d.ts +4 -0
  144. package/dist/graph/index.js +5 -0
  145. package/dist/graph/index.js.map +1 -0
  146. package/dist/graph/project-graph.d.ts +43 -0
  147. package/dist/graph/project-graph.js +80 -0
  148. package/dist/graph/project-graph.js.map +1 -0
  149. package/dist/graph/scope-graph.d.ts +63 -0
  150. package/dist/graph/scope-graph.js +89 -0
  151. package/dist/graph/scope-graph.js.map +1 -0
  152. package/dist/index.d.ts +2 -2
  153. package/dist/index.js +1 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/resolution/cross-file.js +52 -19
  156. package/dist/resolution/cross-file.js.map +1 -1
  157. package/dist/types/index.d.ts +151 -0
  158. package/docs/SPEC.md +10 -6
  159. package/package.json +3 -2
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Pass #20: null-deref (CWE-476, category: reliability)
3
+ *
4
+ * Detects variables that are explicitly assigned null/None/undefined and then
5
+ * used as a receiver (method call or field access) without an intervening
6
+ * null guard.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Find DFG defs where the expression is an explicit null literal
10
+ * (null / None / undefined).
11
+ * 2. For each such def, find all DFG uses via graph.usesOfDef().
12
+ * 3. For each use that occurs after the def in the same method and is used
13
+ * as a receiver, check whether any line between def and use contains a
14
+ * null-check for that variable.
15
+ * 4. Emit a finding if no guard is found.
16
+ *
17
+ * Scope is limited to the enclosing method to avoid cross-method FPs.
18
+ */
19
+ /** Expression values that represent an explicit null assignment. */
20
+ const NULL_EXPR_RE = /^\s*(null|None|undefined)\s*$/;
21
+ /** Escape a variable name for use in a RegExp. */
22
+ function escRe(s) {
23
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
+ }
25
+ /**
26
+ * Returns true if any source line in the range [fromLine, toLine) contains a
27
+ * null-guard pattern referencing varName.
28
+ *
29
+ * Guards recognised:
30
+ * - Java/JS: `x != null`, `x !== null`, `null != x`
31
+ * - Java/JS: `x == null`, `x === null` (guard on the null branch)
32
+ * - Python: `x is not None`, `if x:`
33
+ * - Optional chaining: `x?.`
34
+ * - Optional API: `x.isPresent()`, `Optional`
35
+ */
36
+ function hasNullGuard(codeLines, varName, fromLine, toLine) {
37
+ const esc = escRe(varName);
38
+ // Build one composite regex (OR of guard patterns)
39
+ const pattern = new RegExp(`\\b${esc}\\b\\s*!==?\\s*(null|None|undefined)` +
40
+ `|(null|None|undefined)\\s*!==?\\s*\\b${esc}\\b` +
41
+ `|\\b${esc}\\b\\s*===?\\s*(null|None|undefined)` + // null-branch check
42
+ `|(null|None|undefined)\\s*===?\\s*\\b${esc}\\b` +
43
+ `|\\bis\\s+not\\s+None\\b.*\\b${esc}\\b` + // Python is not None
44
+ `|\\b${esc}\\b.*\\bis\\s+not\\s+None\\b` +
45
+ `|if\\s*\\(\\s*${esc}\\s*[)!&|]` + // if (x), if (!x)
46
+ `|if\\s+${esc}\\s*:` + // Python: if x:
47
+ `|\\b${esc}\\b\\s*\\.\\s*isPresent\\(\\)` + // Optional.isPresent()
48
+ `|\\bOptional\\b`);
49
+ for (let l = fromLine; l < toLine; l++) {
50
+ const line = codeLines[l - 1] ?? '';
51
+ if (pattern.test(line))
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ export class NullDerefPass {
57
+ name = 'null-deref';
58
+ category = 'reliability';
59
+ run(ctx) {
60
+ const { graph, code, language } = ctx;
61
+ // Rust has the Option/Result type system — NPE is not applicable.
62
+ // Bash has no objects to dereference.
63
+ if (language === 'rust' || language === 'bash') {
64
+ return { potentialNullDerefs: [] };
65
+ }
66
+ const file = graph.ir.meta.file;
67
+ const codeLines = code.split('\n');
68
+ const potentialNullDerefs = [];
69
+ const reported = new Set(); // deduplicate by variable+useLine
70
+ for (const def of graph.ir.dfg.defs) {
71
+ if (!def.expression || !NULL_EXPR_RE.test(def.expression))
72
+ continue;
73
+ const varName = def.variable;
74
+ const defLine = def.line;
75
+ // Determine enclosing method bounds to limit search scope
76
+ const methodInfo = graph.methodAtLine(defLine);
77
+ const methodEnd = methodInfo?.method.end_line ?? Number.MAX_SAFE_INTEGER;
78
+ // Find all downstream uses of this null-assigned variable
79
+ const uses = graph.usesOfDef(def.id);
80
+ for (const use of uses) {
81
+ const useLine = use.line;
82
+ // Use must be AFTER the assignment and within the same method
83
+ if (useLine <= defLine || useLine > methodEnd)
84
+ continue;
85
+ // Check if the variable is used as a call receiver at this line
86
+ const callsAtLine = graph.callsAtLine(useLine);
87
+ const isCallReceiver = callsAtLine.some(c => c.receiver === varName);
88
+ // Also detect field-access pattern: `varName.field`
89
+ const lineText = codeLines[useLine - 1] ?? '';
90
+ const fieldAccessRe = new RegExp(`\\b${escRe(varName)}\\s*\\.`);
91
+ const isFieldAccess = fieldAccessRe.test(lineText);
92
+ if (!isCallReceiver && !isFieldAccess)
93
+ continue;
94
+ // Optional chaining (`?.`) is safe — skip
95
+ const optionalChainRe = new RegExp(`\\b${escRe(varName)}\\s*\\?\\s*\\.`);
96
+ if (optionalChainRe.test(lineText))
97
+ continue;
98
+ // Check whether a null guard exists between the assignment and this use
99
+ if (hasNullGuard(codeLines, varName, defLine + 1, useLine))
100
+ continue;
101
+ const key = `${varName}-${useLine}`;
102
+ if (reported.has(key))
103
+ continue;
104
+ reported.add(key);
105
+ potentialNullDerefs.push({ defLine, useLine, variable: varName });
106
+ ctx.addFinding({
107
+ id: `null-deref-${file}-${useLine}`,
108
+ pass: this.name,
109
+ category: this.category,
110
+ rule_id: this.name,
111
+ cwe: 'CWE-476',
112
+ severity: 'high',
113
+ level: 'error',
114
+ message: `Potential null dereference: '${varName}' was assigned null at line ${defLine} ` +
115
+ `and is used at line ${useLine} without a null check`,
116
+ file,
117
+ line: useLine,
118
+ snippet: lineText.trim(),
119
+ fix: `Add a null check before dereferencing: \`if (${varName} != null) { ... }\``,
120
+ evidence: {
121
+ variable: varName,
122
+ assigned_null_at: defLine,
123
+ },
124
+ });
125
+ }
126
+ }
127
+ return { potentialNullDerefs };
128
+ }
129
+ }
130
+ //# sourceMappingURL=null-deref-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-deref-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/null-deref-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pass #71: orphan-module (project-level)
3
+ *
4
+ * Flags modules that have no incoming import edges and are not recognized
5
+ * entry points. Orphan modules are likely dead code, leftover scaffolding,
6
+ * or files that were accidentally disconnected from the project.
7
+ *
8
+ * This is a project-level pass — it does NOT extend AnalysisPass.
9
+ * It is invoked from analyzeProject() after all per-file analyses are complete.
10
+ *
11
+ * Entry points (excluded from flagging): filename base matches
12
+ * /^(index|main|app|server|mod)$/i
13
+ *
14
+ * Category: architecture | Severity: low | Level: note | CWE: none
15
+ */
16
+ import type { SastFinding } from '../../types/index.js';
17
+ import type { ProjectGraph } from '../../graph/project-graph.js';
18
+ import type { ImportGraph } from '../../graph/import-graph.js';
19
+ export declare class OrphanModulePass {
20
+ run(_projectGraph: ProjectGraph, importGraph: ImportGraph): SastFinding[];
21
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pass #71: orphan-module (project-level)
3
+ *
4
+ * Flags modules that have no incoming import edges and are not recognized
5
+ * entry points. Orphan modules are likely dead code, leftover scaffolding,
6
+ * or files that were accidentally disconnected from the project.
7
+ *
8
+ * This is a project-level pass — it does NOT extend AnalysisPass.
9
+ * It is invoked from analyzeProject() after all per-file analyses are complete.
10
+ *
11
+ * Entry points (excluded from flagging): filename base matches
12
+ * /^(index|main|app|server|mod)$/i
13
+ *
14
+ * Category: architecture | Severity: low | Level: note | CWE: none
15
+ */
16
+ export class OrphanModulePass {
17
+ run(_projectGraph, importGraph) {
18
+ const findings = [];
19
+ const orphans = importGraph.findOrphans();
20
+ for (const file of orphans) {
21
+ const finding = {
22
+ id: `orphan-module-${file.replace(/[^a-z0-9]/gi, '-')}`,
23
+ pass: 'orphan-module',
24
+ category: 'architecture',
25
+ rule_id: 'orphan-module',
26
+ severity: 'low',
27
+ level: 'note',
28
+ message: `Module '${file}' has no incoming imports and is not a known entry point. It may be dead code.`,
29
+ file,
30
+ line: 1,
31
+ evidence: { file },
32
+ };
33
+ findings.push(finding);
34
+ }
35
+ return findings;
36
+ }
37
+ }
38
+ //# sourceMappingURL=orphan-module-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphan-module-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/orphan-module-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,OAAO,gBAAgB;IAC3B,GAAG,CAAC,aAA2B,EAAE,WAAwB;QACvD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QAE1C,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAgB;gBAC3B,EAAE,EAAQ,iBAAiB,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE;gBAC7D,IAAI,EAAM,eAAe;gBACzB,QAAQ,EAAE,cAAc;gBACxB,OAAO,EAAG,eAAe;gBACzB,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAK,MAAM;gBAChB,OAAO,EAAG,WAAW,IAAI,gFAAgF;gBACzG,IAAI;gBACJ,IAAI,EAAM,CAAC;gBACX,QAAQ,EAAE,EAAE,IAAI,EAAE;aACnB,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,43 @@
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
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
23
+ export interface ResourceLeakResult {
24
+ /** Resources that may not be properly closed. */
25
+ leaks: Array<{
26
+ line: number;
27
+ resource: string;
28
+ variable: string;
29
+ kind: 'definite' | 'potential';
30
+ }>;
31
+ }
32
+ export declare class ResourceLeakPass implements AnalysisPass<ResourceLeakResult> {
33
+ readonly name = "resource-leak";
34
+ readonly category: "reliability";
35
+ run(ctx: PassContext): ResourceLeakResult;
36
+ /** True if a `finally` keyword appears in the method body after the open line. */
37
+ private hasFinallyBlock;
38
+ /**
39
+ * True if a try-with-resources or Python `with` statement wraps the resource,
40
+ * indicating implicit close. Detects `try (` or `with open(` patterns.
41
+ */
42
+ private hasTryWithResources;
43
+ }
@@ -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,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 {};