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,44 @@
1
+ /**
2
+ * ConstantPropagationPass
3
+ *
4
+ * Runs constant propagation + dead-code detection over the AST.
5
+ *
6
+ * Depends on: taint-matcher (to extract inter-procedural tainted parameters
7
+ * before propagation, so method-parameter taint is seeded correctly).
8
+ *
9
+ * Receives the parsed Tree via constructor because it needs the raw AST for
10
+ * node-level analysis — the CodeGraph contains only extracted IR.
11
+ */
12
+ import { analyzeConstantPropagation } from '../constant-propagation.js';
13
+ export class ConstantPropagationPass {
14
+ tree;
15
+ name = 'constant-propagation';
16
+ category = 'security';
17
+ constructor(tree) {
18
+ this.tree = tree;
19
+ }
20
+ run(ctx) {
21
+ const { code } = ctx;
22
+ const taintMatcher = ctx.getResult('taint-matcher');
23
+ // Extract inter-procedural parameter sources from the preliminary taint results.
24
+ // These seeds ensure that method parameters receiving tainted data are tracked.
25
+ const taintedParameters = [];
26
+ for (const source of taintMatcher.sources) {
27
+ if (source.type === 'interprocedural_param') {
28
+ // Location format: "ParamType paramName in methodName"
29
+ const match = source.location.match(/(\S+)\s+(\S+)\s+in\s+(\S+)/);
30
+ if (match) {
31
+ taintedParameters.push({
32
+ methodName: match[3],
33
+ paramName: match[2],
34
+ });
35
+ }
36
+ }
37
+ }
38
+ return analyzeConstantPropagation(this.tree, code, {
39
+ sanitizerMethods: taintMatcher.sanitizerMethods,
40
+ taintedParameters,
41
+ });
42
+ }
43
+ }
44
+ //# sourceMappingURL=constant-propagation-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constant-propagation-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/constant-propagation-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,0BAA0B,EAAiC,MAAM,4BAA4B,CAAC;AAKvG,MAAM,OAAO,uBAAuB;IAIL;IAHpB,IAAI,GAAG,sBAAsB,CAAC;IAC9B,QAAQ,GAAG,UAAmB,CAAC;IAExC,YAA6B,IAAU;QAAV,SAAI,GAAJ,IAAI,CAAM;IAAG,CAAC;IAE3C,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;QACrB,MAAM,YAAY,GAAG,GAAG,CAAC,SAAS,CAAqB,eAAe,CAAC,CAAC;QAExE,iFAAiF;QACjF,gFAAgF;QAChF,MAAM,iBAAiB,GAAqD,EAAE,CAAC;QAC/E,KAAK,MAAM,MAAM,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;gBAC5C,uDAAuD;gBACvD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBAClE,IAAI,KAAK,EAAE,CAAC;oBACV,iBAAiB,CAAC,IAAI,CAAC;wBACrB,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;wBACpB,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;qBACpB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,0BAA0B,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YACjD,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;YAC/C,iBAAiB;SAClB,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CrossFilePass
3
+ *
4
+ * Project-level pass that uses the CrossFileResolver to surface taint flows
5
+ * that cross file boundaries and to map resolved inter-file calls.
6
+ *
7
+ * Unlike the single-file AnalysisPass instances, CrossFilePass operates across
8
+ * the full set of files in a ProjectGraph and is invoked once after all
9
+ * per-file analyses are complete.
10
+ *
11
+ * Depends on: ProjectGraph (with all files registered)
12
+ */
13
+ import type { CrossFileCall, TaintPath, TypeHierarchy } from '../../types/index.js';
14
+ import type { ProjectGraph } from '../../graph/project-graph.js';
15
+ export interface CrossFilePassResult {
16
+ /** Inter-file method calls (source file → target file). */
17
+ crossFileCalls: CrossFileCall[];
18
+ /** Taint paths that cross file boundaries. */
19
+ taintPaths: TaintPath[];
20
+ /** Type hierarchy across all files. */
21
+ typeHierarchy: TypeHierarchy;
22
+ }
23
+ export declare class CrossFilePass {
24
+ run(projectGraph: ProjectGraph,
25
+ /** Raw source lines per file (used to populate `code` fields in paths). */
26
+ sourceLines: Map<string, string[]>): CrossFilePassResult;
27
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * CrossFilePass
3
+ *
4
+ * Project-level pass that uses the CrossFileResolver to surface taint flows
5
+ * that cross file boundaries and to map resolved inter-file calls.
6
+ *
7
+ * Unlike the single-file AnalysisPass instances, CrossFilePass operates across
8
+ * the full set of files in a ProjectGraph and is invoked once after all
9
+ * per-file analyses are complete.
10
+ *
11
+ * Depends on: ProjectGraph (with all files registered)
12
+ */
13
+ export class CrossFilePass {
14
+ run(projectGraph,
15
+ /** Raw source lines per file (used to populate `code` fields in paths). */
16
+ sourceLines) {
17
+ const resolver = projectGraph.resolver;
18
+ // --- 1. Cross-file taint flows → TaintPath[] ----------------------------
19
+ const flows = resolver.findCrossFileTaintFlows();
20
+ const taintPaths = flows.flatMap((flow, idx) => {
21
+ const srcLines = sourceLines.get(flow.sourceFile) ?? [];
22
+ const tgtLines = sourceLines.get(flow.targetFile) ?? [];
23
+ // Look up matched sink from the target file's IR to get type + cwe.
24
+ // Skip flows where no known sink exists at the target line — we never
25
+ // default to 'sql_injection' because that produces massive false positives
26
+ // for any TypeScript project that uses string manipulation helpers.
27
+ const targetIR = projectGraph.getIR(flow.targetFile);
28
+ if (!targetIR || targetIR.taint.sinks.length === 0)
29
+ return [];
30
+ const matchedSink = targetIR.taint.sinks.find(s => s.line === flow.targetLine);
31
+ if (!matchedSink)
32
+ return [];
33
+ return [{
34
+ id: `cf-${idx}`,
35
+ source: {
36
+ file: flow.sourceFile,
37
+ line: flow.sourceLine,
38
+ type: flow.sourceType,
39
+ code: srcLines[flow.sourceLine - 1] ?? '',
40
+ },
41
+ sink: {
42
+ file: flow.targetFile,
43
+ line: flow.targetLine,
44
+ type: matchedSink.type,
45
+ cwe: matchedSink.cwe,
46
+ code: tgtLines[flow.targetLine - 1] ?? '',
47
+ },
48
+ hops: [
49
+ {
50
+ file: flow.sourceFile,
51
+ method: '',
52
+ line: flow.sourceLine,
53
+ code: srcLines[flow.sourceLine - 1] ?? '',
54
+ variable: '',
55
+ },
56
+ {
57
+ file: flow.targetFile,
58
+ method: flow.targetMethod,
59
+ line: flow.targetLine,
60
+ code: tgtLines[flow.targetLine - 1] ?? '',
61
+ variable: '',
62
+ },
63
+ ],
64
+ sanitizers_in_path: [],
65
+ path_exists: true,
66
+ confidence: 0.7,
67
+ }];
68
+ });
69
+ // --- 2. Resolved inter-file calls → CrossFileCall[] --------------------
70
+ const crossFileCalls = [];
71
+ for (const filePath of projectGraph.filePaths) {
72
+ const resolved = resolver.getResolvedCallsFromFile(filePath);
73
+ for (const rc of resolved) {
74
+ if (rc.sourceFile === rc.targetFile)
75
+ continue; // same-file, skip
76
+ crossFileCalls.push({
77
+ id: `${rc.sourceFile}:${rc.call.location.line}:${rc.targetMethod}`,
78
+ from: {
79
+ file: rc.sourceFile,
80
+ method: rc.call.in_method ?? '',
81
+ line: rc.call.location.line,
82
+ },
83
+ to: {
84
+ file: rc.targetFile,
85
+ method: rc.targetMethod,
86
+ line: 0, // target line resolved via symbol table if needed
87
+ },
88
+ args_mapping: (rc.call.arguments ?? []).map((_, i) => ({
89
+ caller_arg: i,
90
+ callee_param: i,
91
+ taint_propagates: false,
92
+ })),
93
+ resolved: rc.resolution === 'exact',
94
+ });
95
+ }
96
+ }
97
+ // --- 3. Type hierarchy --------------------------------------------------
98
+ const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
99
+ return { crossFileCalls, taintPaths, typeHierarchy };
100
+ }
101
+ }
102
+ //# sourceMappingURL=cross-file-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-file-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/cross-file-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pass #22: dead-code (CWE-561, category: reliability)
3
+ *
4
+ * Detects CFG blocks that are structurally unreachable from the entry block
5
+ * (i.e., no path of control-flow edges leads to them). This is pure CFG
6
+ * reachability — independent of constant-propagation or taint analysis.
7
+ *
8
+ * Examples: code after an unconditional `return`/`throw`, branches of
9
+ * `if (false)` where the condition is a literal (compiler-level dead code).
10
+ *
11
+ * Note: semantic dead code eliminated by constant propagation (e.g.,
12
+ * `if (DEBUG_MODE) { ... }` where DEBUG_MODE is a compile-time constant)
13
+ * is handled by ConstantPropagationPass, not this pass.
14
+ */
15
+ import type { CFGBlock } from '../../types/index.js';
16
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
17
+ export interface DeadCodePassResult {
18
+ /** CFG blocks with no incoming reachable path from the entry block. */
19
+ deadBlocks: CFGBlock[];
20
+ }
21
+ export declare class DeadCodePass implements AnalysisPass<DeadCodePassResult> {
22
+ readonly name = "dead-code";
23
+ readonly category: "reliability";
24
+ run(ctx: PassContext): DeadCodePassResult;
25
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pass #22: dead-code (CWE-561, category: reliability)
3
+ *
4
+ * Detects CFG blocks that are structurally unreachable from the entry block
5
+ * (i.e., no path of control-flow edges leads to them). This is pure CFG
6
+ * reachability — independent of constant-propagation or taint analysis.
7
+ *
8
+ * Examples: code after an unconditional `return`/`throw`, branches of
9
+ * `if (false)` where the condition is a literal (compiler-level dead code).
10
+ *
11
+ * Note: semantic dead code eliminated by constant propagation (e.g.,
12
+ * `if (DEBUG_MODE) { ... }` where DEBUG_MODE is a compile-time constant)
13
+ * is handled by ConstantPropagationPass, not this pass.
14
+ */
15
+ export class DeadCodePass {
16
+ name = 'dead-code';
17
+ category = 'reliability';
18
+ run(ctx) {
19
+ const { graph, code } = ctx;
20
+ const { blocks, edges } = graph.ir.cfg;
21
+ const file = graph.ir.meta.file;
22
+ const language = graph.ir.meta.language ?? '';
23
+ if (blocks.length === 0)
24
+ return { deadBlocks: [] };
25
+ // Build outgoing adjacency: block id → reachable block ids
26
+ const outgoing = new Map();
27
+ for (const edge of edges) {
28
+ let list = outgoing.get(edge.from);
29
+ if (!list) {
30
+ list = [];
31
+ outgoing.set(edge.from, list);
32
+ }
33
+ list.push(edge.to);
34
+ }
35
+ // Find ALL root blocks: blocks with no incoming edges AND at least one
36
+ // outgoing edge. Each function body, arrow function, and class method
37
+ // creates its own disconnected sub-graph in the CFG (intra-procedural
38
+ // CFGs don't model call edges). These sub-graph roots have no incoming
39
+ // edges but DO have outgoing edges into their body blocks.
40
+ //
41
+ // A completely isolated block (no incoming, no outgoing) is the canonical
42
+ // shape of dead code after an unconditional return/throw — it is NOT
43
+ // treated as a root so it gets correctly reported.
44
+ const hasIncoming = new Set(edges.map(e => e.to));
45
+ const hasOutgoing = new Set(edges.map(e => e.from));
46
+ const roots = blocks.filter(b => !hasIncoming.has(b.id) && hasOutgoing.has(b.id));
47
+ // Always have at least one root: prefer type='entry', then any root,
48
+ // then the lowest-id block.
49
+ if (roots.length === 0) {
50
+ const fallback = blocks.find(b => b.type === 'entry') ??
51
+ blocks.find(b => !hasIncoming.has(b.id)) ??
52
+ blocks.reduce((a, b) => (a.id < b.id ? a : b));
53
+ roots.push(fallback);
54
+ }
55
+ // BFS from ALL roots to mark reachable block ids.
56
+ const reachable = new Set(roots.map(r => r.id));
57
+ const queue = roots.map(r => r.id);
58
+ while (queue.length > 0) {
59
+ const id = queue.shift();
60
+ for (const next of outgoing.get(id) ?? []) {
61
+ if (!reachable.has(next)) {
62
+ reachable.add(next);
63
+ queue.push(next);
64
+ }
65
+ }
66
+ }
67
+ // Collect unreachable blocks that are worth reporting:
68
+ // - not the entry or exit sentinel blocks
69
+ // - have a positive start line (skip synthetic 0-line blocks)
70
+ const isJsTs = language === 'javascript' || language === 'typescript';
71
+ const codeLines = isJsTs ? code.split('\n') : [];
72
+ const deadBlocks = [];
73
+ for (const block of blocks) {
74
+ if (reachable.has(block.id))
75
+ continue;
76
+ if (block.type === 'entry' || block.type === 'exit')
77
+ continue;
78
+ if (block.start_line <= 0)
79
+ continue;
80
+ // In JS/TS, completely isolated blocks (no incoming AND no outgoing edges)
81
+ // are often arrow function expression bodies or simple function bodies — the
82
+ // intra-procedural CFG extractor gives them no edges. Suppress these to avoid
83
+ // false positives. Real dead code (post-return) always has a preceding block
84
+ // with an outgoing edge, so it is not affected by this check.
85
+ if (isJsTs && !hasIncoming.has(block.id) && !hasOutgoing.has(block.id)) {
86
+ const prevLine = codeLines[block.start_line - 2]?.trimEnd() ?? '';
87
+ const startLine = codeLines[block.start_line - 1]?.trimEnd() ?? '';
88
+ // Arrow function body: the line before OR the block's own first line
89
+ // contains '=>' (handles both multi-line and inline arrow functions).
90
+ // Regular function/method body: the preceding line ends with '{'.
91
+ if (prevLine.includes('=>') || prevLine.endsWith('{') ||
92
+ startLine.includes('=>'))
93
+ continue;
94
+ }
95
+ deadBlocks.push(block);
96
+ const loc = block.start_line === block.end_line
97
+ ? `line ${block.start_line}`
98
+ : `lines ${block.start_line}–${block.end_line}`;
99
+ ctx.addFinding({
100
+ id: `dead-code-${file}-${block.start_line}`,
101
+ pass: this.name,
102
+ category: this.category,
103
+ rule_id: this.name,
104
+ cwe: 'CWE-561',
105
+ severity: 'low',
106
+ level: 'warning',
107
+ message: `Dead code at ${loc}: block is unreachable from any entry point`,
108
+ file,
109
+ line: block.start_line,
110
+ end_line: block.end_line > block.start_line ? block.end_line : undefined,
111
+ fix: 'Remove the unreachable block or fix the control flow that precedes it',
112
+ });
113
+ }
114
+ return { deadBlocks };
115
+ }
116
+ }
117
+ //# sourceMappingURL=dead-code-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-code-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/dead-code-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pass #29: deep-inheritance (CWE-1086, category: architecture)
3
+ *
4
+ * Detects class inheritance chains deeper than the configured threshold
5
+ * (default: 5). Deep inheritance hierarchies increase coupling, make code
6
+ * harder to understand, and indicate a design smell (violation of composition
7
+ * over inheritance).
8
+ *
9
+ * Detection strategy:
10
+ * 1. Build a parentMap: className → parentName from `ir.types[*].extends`.
11
+ * 2. For each class with a known `start_line`, walk up the parentMap
12
+ * counting depth. Stop at depth 20 to defend against cycles.
13
+ * 3. If depth > THRESHOLD, emit a finding at the class `start_line`.
14
+ *
15
+ * Languages: Java, JavaScript/TypeScript, Python. Rust/Bash do not have
16
+ * class inheritance — skipped.
17
+ */
18
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
19
+ export interface DeepInheritanceResult {
20
+ deepClasses: Array<{
21
+ className: string;
22
+ depth: number;
23
+ line: number;
24
+ }>;
25
+ }
26
+ export declare class DeepInheritancePass implements AnalysisPass<DeepInheritanceResult> {
27
+ readonly name = "deep-inheritance";
28
+ readonly category: "architecture";
29
+ run(ctx: PassContext): DeepInheritanceResult;
30
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Pass #29: deep-inheritance (CWE-1086, category: architecture)
3
+ *
4
+ * Detects class inheritance chains deeper than the configured threshold
5
+ * (default: 5). Deep inheritance hierarchies increase coupling, make code
6
+ * harder to understand, and indicate a design smell (violation of composition
7
+ * over inheritance).
8
+ *
9
+ * Detection strategy:
10
+ * 1. Build a parentMap: className → parentName from `ir.types[*].extends`.
11
+ * 2. For each class with a known `start_line`, walk up the parentMap
12
+ * counting depth. Stop at depth 20 to defend against cycles.
13
+ * 3. If depth > THRESHOLD, emit a finding at the class `start_line`.
14
+ *
15
+ * Languages: Java, JavaScript/TypeScript, Python. Rust/Bash do not have
16
+ * class inheritance — skipped.
17
+ */
18
+ const DEPTH_THRESHOLD = 5;
19
+ const CYCLE_GUARD = 20;
20
+ export class DeepInheritancePass {
21
+ name = 'deep-inheritance';
22
+ category = 'architecture';
23
+ run(ctx) {
24
+ const { graph, language } = ctx;
25
+ if (language === 'rust' || language === 'bash') {
26
+ return { deepClasses: [] };
27
+ }
28
+ const file = graph.ir.meta.file;
29
+ const types = graph.ir.types;
30
+ if (types.length === 0)
31
+ return { deepClasses: [] };
32
+ // Build parentMap: class name → parent class name
33
+ const parentMap = new Map();
34
+ for (const typeInfo of types) {
35
+ if (typeInfo.extends) {
36
+ // Strip generic parameters (e.g., "BaseRepo<User>" → "BaseRepo")
37
+ const parentName = typeInfo.extends.replace(/<.*>/, '').trim();
38
+ if (parentName) {
39
+ parentMap.set(typeInfo.name, parentName);
40
+ }
41
+ }
42
+ }
43
+ const deepClasses = [];
44
+ for (const typeInfo of types) {
45
+ if (typeInfo.kind !== 'class')
46
+ continue;
47
+ if (typeInfo.start_line <= 0)
48
+ continue;
49
+ // Walk up the inheritance chain
50
+ let depth = 0;
51
+ let current = parentMap.get(typeInfo.name);
52
+ const visited = new Set([typeInfo.name]);
53
+ while (current !== undefined && depth < CYCLE_GUARD) {
54
+ depth++;
55
+ if (visited.has(current))
56
+ break; // cycle guard
57
+ visited.add(current);
58
+ current = parentMap.get(current);
59
+ }
60
+ if (depth > DEPTH_THRESHOLD) {
61
+ deepClasses.push({ className: typeInfo.name, depth, line: typeInfo.start_line });
62
+ ctx.addFinding({
63
+ id: `deep-inheritance-${file}-${typeInfo.start_line}`,
64
+ pass: this.name,
65
+ category: this.category,
66
+ rule_id: this.name,
67
+ cwe: 'CWE-1086',
68
+ severity: 'low',
69
+ level: 'warning',
70
+ message: `Deep inheritance: class \`${typeInfo.name}\` has inheritance depth ${depth} (threshold: ${DEPTH_THRESHOLD})`,
71
+ file,
72
+ line: typeInfo.start_line,
73
+ fix: `Refactor to prefer composition over inheritance. ` +
74
+ `Consider extracting shared behaviour into interfaces or mixins.`,
75
+ evidence: { depth, threshold: DEPTH_THRESHOLD },
76
+ });
77
+ }
78
+ }
79
+ return { deepClasses };
80
+ }
81
+ }
82
+ //# sourceMappingURL=deep-inheritance-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-inheritance-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/deep-inheritance-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pass #72: dependency-fan-out
3
+ *
4
+ * Flags modules that import an excessive number of other modules (≥20).
5
+ * High fan-out is a coupling smell that makes modules hard to test and modify
6
+ * independently.
7
+ *
8
+ * Category: architecture | Severity: low | Level: note | CWE: none
9
+ */
10
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
11
+ export interface DependencyFanOutResult {
12
+ importCount: number;
13
+ exceeded: boolean;
14
+ }
15
+ export declare class DependencyFanOutPass implements AnalysisPass<DependencyFanOutResult> {
16
+ readonly name = "dependency-fan-out";
17
+ readonly category: "architecture";
18
+ run(ctx: PassContext): DependencyFanOutResult;
19
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pass #72: dependency-fan-out
3
+ *
4
+ * Flags modules that import an excessive number of other modules (≥20).
5
+ * High fan-out is a coupling smell that makes modules hard to test and modify
6
+ * independently.
7
+ *
8
+ * Category: architecture | Severity: low | Level: note | CWE: none
9
+ */
10
+ const FAN_OUT_THRESHOLD = 20;
11
+ export class DependencyFanOutPass {
12
+ name = 'dependency-fan-out';
13
+ category = 'architecture';
14
+ run(ctx) {
15
+ const importCount = ctx.graph.ir.imports.length;
16
+ const exceeded = importCount >= FAN_OUT_THRESHOLD;
17
+ if (exceeded) {
18
+ const finding = {
19
+ id: `dependency-fan-out-${ctx.graph.ir.meta.file.replace(/[^a-z0-9]/gi, '-')}`,
20
+ pass: 'dependency-fan-out',
21
+ category: 'architecture',
22
+ rule_id: 'dependency-fan-out',
23
+ severity: 'low',
24
+ level: 'note',
25
+ message: `Module imports ${importCount} dependencies (threshold: ${FAN_OUT_THRESHOLD}). High fan-out increases coupling and makes the module harder to test.`,
26
+ file: ctx.graph.ir.meta.file,
27
+ line: 1,
28
+ evidence: { importCount, threshold: FAN_OUT_THRESHOLD },
29
+ };
30
+ ctx.addFinding(finding);
31
+ }
32
+ return { importCount, exceeded };
33
+ }
34
+ }
35
+ //# sourceMappingURL=dependency-fan-out-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-fan-out-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/dependency-fan-out-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,OAAO,oBAAoB;IACtB,IAAI,GAAG,oBAAoB,CAAC;IAC5B,QAAQ,GAAG,cAAuB,CAAC;IAE5C,GAAG,CAAC,GAAgB;QAClB,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;QAChD,MAAM,QAAQ,GAAM,WAAW,IAAI,iBAAiB,CAAC;QAErD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,OAAO,GAAgB;gBAC3B,EAAE,EAAQ,sBAAsB,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE;gBACpF,IAAI,EAAM,oBAAoB;gBAC9B,QAAQ,EAAE,cAAc;gBACxB,OAAO,EAAG,oBAAoB;gBAC9B,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAK,MAAM;gBAChB,OAAO,EAAG,kBAAkB,WAAW,6BAA6B,iBAAiB,yEAAyE;gBAC9J,IAAI,EAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI;gBAChC,IAAI,EAAM,CAAC;gBACX,QAAQ,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE;aACxD,CAAC;YACF,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QAED,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pass #28: infinite-loop (CWE-835, category: reliability)
3
+ *
4
+ * Detects loops with no reachable exit edge — i.e., loops that can run
5
+ * forever because every execution path through the loop body leads back to
6
+ * the loop header without a break, return, throw, or continue-to-outer.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Identify loop headers: back-edge targets in the CFG (`edge.type === 'back'`).
10
+ * 2. For each loop, collect the loop body blocks via BFS from the header,
11
+ * stopping at back-edge sources (the "tail" blocks).
12
+ * 3. Check whether any block in the body has an outgoing edge that exits
13
+ * the loop (target not in body set and not back to header).
14
+ * 4. As a text-level fallback, scan source lines in the loop body for
15
+ * `return`, `throw`/`raise`, `break`, `System.exit` keywords.
16
+ * 5. Emit a finding at the loop header's start_line if no exit is found.
17
+ *
18
+ * Languages: Java, JavaScript, TypeScript, Python, Rust. Skip Bash.
19
+ */
20
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
21
+ export interface InfiniteLoopResult {
22
+ potentialInfiniteLoops: Array<{
23
+ headerLine: number;
24
+ bodyEndLine: number;
25
+ }>;
26
+ }
27
+ export declare class InfiniteLoopPass implements AnalysisPass<InfiniteLoopResult> {
28
+ readonly name = "infinite-loop";
29
+ readonly category: "reliability";
30
+ run(ctx: PassContext): InfiniteLoopResult;
31
+ }