circle-ir 3.9.7 → 3.9.10

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 (56) hide show
  1. package/dist/analysis/passes/broad-catch-pass.d.ts +29 -0
  2. package/dist/analysis/passes/broad-catch-pass.js +79 -0
  3. package/dist/analysis/passes/broad-catch-pass.js.map +1 -0
  4. package/dist/analysis/passes/deep-inheritance-pass.d.ts +30 -0
  5. package/dist/analysis/passes/deep-inheritance-pass.js +82 -0
  6. package/dist/analysis/passes/deep-inheritance-pass.js.map +1 -0
  7. package/dist/analysis/passes/double-close-pass.d.ts +33 -0
  8. package/dist/analysis/passes/double-close-pass.js +109 -0
  9. package/dist/analysis/passes/double-close-pass.js.map +1 -0
  10. package/dist/analysis/passes/infinite-loop-pass.d.ts +31 -0
  11. package/dist/analysis/passes/infinite-loop-pass.js +126 -0
  12. package/dist/analysis/passes/infinite-loop-pass.js.map +1 -0
  13. package/dist/analysis/passes/react-inline-jsx-pass.d.ts +36 -0
  14. package/dist/analysis/passes/react-inline-jsx-pass.js +140 -0
  15. package/dist/analysis/passes/react-inline-jsx-pass.js.map +1 -0
  16. package/dist/analysis/passes/redundant-loop-pass.d.ts +30 -0
  17. package/dist/analysis/passes/redundant-loop-pass.js +146 -0
  18. package/dist/analysis/passes/redundant-loop-pass.js.map +1 -0
  19. package/dist/analysis/passes/serial-await-pass.d.ts +36 -0
  20. package/dist/analysis/passes/serial-await-pass.js +132 -0
  21. package/dist/analysis/passes/serial-await-pass.js.map +1 -0
  22. package/dist/analysis/passes/sink-filter-pass.js +7 -1
  23. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  24. package/dist/analysis/passes/swallowed-exception-pass.d.ts +35 -0
  25. package/dist/analysis/passes/swallowed-exception-pass.js +103 -0
  26. package/dist/analysis/passes/swallowed-exception-pass.js.map +1 -0
  27. package/dist/analysis/passes/unbounded-collection-pass.d.ts +32 -0
  28. package/dist/analysis/passes/unbounded-collection-pass.js +128 -0
  29. package/dist/analysis/passes/unbounded-collection-pass.js.map +1 -0
  30. package/dist/analysis/passes/unhandled-exception-pass.d.ts +34 -0
  31. package/dist/analysis/passes/unhandled-exception-pass.js +123 -0
  32. package/dist/analysis/passes/unhandled-exception-pass.js.map +1 -0
  33. package/dist/analysis/passes/use-after-close-pass.d.ts +30 -0
  34. package/dist/analysis/passes/use-after-close-pass.js +100 -0
  35. package/dist/analysis/passes/use-after-close-pass.js.map +1 -0
  36. package/dist/analysis/taint-matcher.js +1 -0
  37. package/dist/analysis/taint-matcher.js.map +1 -1
  38. package/dist/analyzer.d.ts +12 -1
  39. package/dist/analyzer.js +34 -1
  40. package/dist/analyzer.js.map +1 -1
  41. package/dist/browser/circle-ir.js +1035 -3
  42. package/dist/core/circle-ir-core.cjs +2 -1
  43. package/dist/core/circle-ir-core.js +2 -1
  44. package/dist/graph/dominator-graph.d.ts +53 -0
  45. package/dist/graph/dominator-graph.js +256 -0
  46. package/dist/graph/dominator-graph.js.map +1 -0
  47. package/dist/graph/exception-flow-graph.d.ts +44 -0
  48. package/dist/graph/exception-flow-graph.js +75 -0
  49. package/dist/graph/exception-flow-graph.js.map +1 -0
  50. package/dist/graph/index.d.ts +2 -0
  51. package/dist/graph/index.js +2 -0
  52. package/dist/graph/index.js.map +1 -1
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +3 -0
  55. package/dist/index.js.map +1 -1
  56. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pass #29: broad-catch (CWE-396, category: reliability)
3
+ *
4
+ * Detects catch clauses that catch a base exception type (Exception,
5
+ * Throwable, BaseException) rather than the specific subtypes the code
6
+ * can handle. Broad catches suppress unexpected errors, make bugs harder
7
+ * to find, and can inadvertently catch serious errors (OutOfMemoryError,
8
+ * StackOverflowError) that should not be swallowed.
9
+ *
10
+ * Detection strategy:
11
+ * 1. Build an ExceptionFlowGraph to locate catch handler entry lines.
12
+ * 2. Check the source text of each catch line for broad-catch patterns.
13
+ *
14
+ * Languages: Java, Python only.
15
+ * - JS/TS: no typed catch clauses; not applicable.
16
+ * - Rust/Bash: no traditional exceptions; skip.
17
+ */
18
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
19
+ export interface BroadCatchResult {
20
+ broadCatches: Array<{
21
+ line: number;
22
+ type: string;
23
+ }>;
24
+ }
25
+ export declare class BroadCatchPass implements AnalysisPass<BroadCatchResult> {
26
+ readonly name = "broad-catch";
27
+ readonly category: "reliability";
28
+ run(ctx: PassContext): BroadCatchResult;
29
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pass #29: broad-catch (CWE-396, category: reliability)
3
+ *
4
+ * Detects catch clauses that catch a base exception type (Exception,
5
+ * Throwable, BaseException) rather than the specific subtypes the code
6
+ * can handle. Broad catches suppress unexpected errors, make bugs harder
7
+ * to find, and can inadvertently catch serious errors (OutOfMemoryError,
8
+ * StackOverflowError) that should not be swallowed.
9
+ *
10
+ * Detection strategy:
11
+ * 1. Build an ExceptionFlowGraph to locate catch handler entry lines.
12
+ * 2. Check the source text of each catch line for broad-catch patterns.
13
+ *
14
+ * Languages: Java, Python only.
15
+ * - JS/TS: no typed catch clauses; not applicable.
16
+ * - Rust/Bash: no traditional exceptions; skip.
17
+ */
18
+ import { ExceptionFlowGraph } from '../../graph/exception-flow-graph.js';
19
+ /** Java: catch(Exception|Throwable|RuntimeException|Error ...) */
20
+ const JAVA_BROAD_RE = /catch\s*\(\s*(Exception|Throwable|RuntimeException|Error)\s/;
21
+ /**
22
+ * Python: bare `except:` or `except Exception[/BaseException][:]`
23
+ * Also matches `except (Exception, ...):` patterns.
24
+ */
25
+ const PYTHON_BROAD_RE = /^\s*except\s*:|except\s+(Exception|BaseException)\b/;
26
+ export class BroadCatchPass {
27
+ name = 'broad-catch';
28
+ category = 'reliability';
29
+ run(ctx) {
30
+ const { graph, code, language } = ctx;
31
+ if (language !== 'java' && language !== 'python') {
32
+ return { broadCatches: [] };
33
+ }
34
+ const { cfg } = graph.ir;
35
+ if (cfg.blocks.length === 0)
36
+ return { broadCatches: [] };
37
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
38
+ if (!exGraph.hasTryCatch)
39
+ return { broadCatches: [] };
40
+ const file = graph.ir.meta.file;
41
+ const codeLines = code.split('\n');
42
+ const broadCatches = [];
43
+ const reported = new Set();
44
+ const pattern = language === 'java' ? JAVA_BROAD_RE : PYTHON_BROAD_RE;
45
+ for (const pair of exGraph.pairs) {
46
+ const catchLine = pair.catchBlock.start_line;
47
+ if (reported.has(catchLine))
48
+ continue;
49
+ const lineText = codeLines[catchLine - 1] ?? '';
50
+ const match = pattern.exec(lineText);
51
+ if (!match)
52
+ continue;
53
+ const caughtType = match[1] ?? 'Exception';
54
+ reported.add(catchLine);
55
+ broadCatches.push({ line: catchLine, type: caughtType });
56
+ const snippet = lineText.trim();
57
+ ctx.addFinding({
58
+ id: `broad-catch-${file}-${catchLine}`,
59
+ pass: this.name,
60
+ category: this.category,
61
+ rule_id: this.name,
62
+ cwe: 'CWE-396',
63
+ severity: 'low',
64
+ level: 'warning',
65
+ message: `Broad catch: catching \`${caughtType}\` at line ${catchLine} suppresses ` +
66
+ `unexpected errors and hides bugs`,
67
+ file,
68
+ line: catchLine,
69
+ snippet,
70
+ fix: language === 'java'
71
+ ? `Catch the specific exception types your code can handle (e.g., \`IOException\`, \`SQLException\`)`
72
+ : `Catch the specific exception types your code can handle (e.g., \`ValueError\`, \`KeyError\`)`,
73
+ evidence: { caughtType },
74
+ });
75
+ }
76
+ return { broadCatches };
77
+ }
78
+ }
79
+ //# sourceMappingURL=broad-catch-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broad-catch-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/broad-catch-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,33 @@
1
+ /**
2
+ * Pass #31: double-close (CWE-675, category: reliability)
3
+ *
4
+ * Detects I/O resources that are closed more than once within the same
5
+ * method. Calling close() on an already-closed stream (e.g., Java's
6
+ * FileInputStream, Node.js streams) typically throws an exception and
7
+ * indicates a resource-management bug.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Find resource-opening calls (same patterns as resource-leak-pass).
11
+ * 2. Collect the bound variable from DFG defs at the open line.
12
+ * 3. Find ALL close() calls on that variable within the enclosing method.
13
+ * 4. If two or more close calls exist:
14
+ * a. Skip if both are inside a finally block (benign idiomatic pattern).
15
+ * b. Otherwise emit a finding.
16
+ *
17
+ * Languages: Java, JavaScript, TypeScript, Python, Rust (skip Bash).
18
+ */
19
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
20
+ export interface DoubleCloseResult {
21
+ doubleCloses: Array<{
22
+ openLine: number;
23
+ closeLines: number[];
24
+ variable: string;
25
+ }>;
26
+ }
27
+ export declare class DoubleClosePass implements AnalysisPass<DoubleCloseResult> {
28
+ readonly name = "double-close";
29
+ readonly category: "reliability";
30
+ run(ctx: PassContext): DoubleCloseResult;
31
+ /** True if the given line is inside a `finally` block in the method. */
32
+ private isInFinallyBlock;
33
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Pass #31: double-close (CWE-675, category: reliability)
3
+ *
4
+ * Detects I/O resources that are closed more than once within the same
5
+ * method. Calling close() on an already-closed stream (e.g., Java's
6
+ * FileInputStream, Node.js streams) typically throws an exception and
7
+ * indicates a resource-management bug.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Find resource-opening calls (same patterns as resource-leak-pass).
11
+ * 2. Collect the bound variable from DFG defs at the open line.
12
+ * 3. Find ALL close() calls on that variable within the enclosing method.
13
+ * 4. If two or more close calls exist:
14
+ * a. Skip if both are inside a finally block (benign idiomatic pattern).
15
+ * b. Otherwise emit a finding.
16
+ *
17
+ * Languages: Java, JavaScript, TypeScript, Python, Rust (skip Bash).
18
+ */
19
+ /** Constructors that produce closeable resources. */
20
+ const RESOURCE_CTORS = new Set([
21
+ 'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
22
+ 'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
23
+ 'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
24
+ 'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
25
+ 'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
26
+ 'FileChannel', 'Socket', 'ServerSocket', 'DatagramSocket',
27
+ ]);
28
+ /** Factory / open methods that return closeable resources. */
29
+ const RESOURCE_FACTORY_METHODS = new Set([
30
+ 'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
31
+ 'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
32
+ 'open', 'createReadStream', 'createWriteStream', 'createConnection',
33
+ ]);
34
+ /** Methods that release a resource. */
35
+ const CLOSE_METHODS = new Set([
36
+ 'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
37
+ 'shutdownNow', 'terminate',
38
+ ]);
39
+ export class DoubleClosePass {
40
+ name = 'double-close';
41
+ category = 'reliability';
42
+ run(ctx) {
43
+ const { graph, code } = ctx;
44
+ if (ctx.language === 'bash')
45
+ return { doubleCloses: [] };
46
+ const file = graph.ir.meta.file;
47
+ const codeLines = code.split('\n');
48
+ const doubleCloses = [];
49
+ for (const call of graph.ir.calls) {
50
+ const name = call.method_name;
51
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
52
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
53
+ if (!isConstructor && !isFactory)
54
+ continue;
55
+ const openLine = call.location.line;
56
+ const defs = graph.defsAtLine(openLine);
57
+ if (defs.length === 0)
58
+ continue;
59
+ const resourceVar = defs[0].variable;
60
+ const methodInfo = graph.methodAtLine(openLine);
61
+ if (!methodInfo)
62
+ continue;
63
+ const { start_line: methodStart, end_line: methodEnd } = methodInfo.method;
64
+ // Collect all close calls on resourceVar within the method
65
+ const closeCalls = graph.ir.calls.filter(c => CLOSE_METHODS.has(c.method_name) &&
66
+ c.receiver === resourceVar &&
67
+ c.location.line > openLine &&
68
+ c.location.line <= methodEnd);
69
+ if (closeCalls.length < 2)
70
+ continue;
71
+ const closeLines = closeCalls.map(c => c.location.line);
72
+ // Benign check: skip if all closes are guarded by finally
73
+ // (common idiom: try { ... } finally { res.close(); } + catch { res.close(); })
74
+ const allInFinally = closeLines.every(cl => this.isInFinallyBlock(codeLines, cl, methodStart, methodEnd));
75
+ if (allInFinally)
76
+ continue;
77
+ doubleCloses.push({ openLine, closeLines, variable: resourceVar });
78
+ const snippet = (codeLines[openLine - 1] ?? '').trim();
79
+ const linesStr = closeLines.join(' and ');
80
+ ctx.addFinding({
81
+ id: `double-close-${file}-${openLine}`,
82
+ pass: this.name,
83
+ category: this.category,
84
+ rule_id: this.name,
85
+ cwe: 'CWE-675',
86
+ severity: 'medium',
87
+ level: 'warning',
88
+ message: `Double close: \`${resourceVar}\` is closed at lines ${linesStr} — ` +
89
+ `closing an already-closed resource may throw`,
90
+ file,
91
+ line: openLine,
92
+ snippet,
93
+ fix: `Close the resource exactly once in a finally block; ` +
94
+ `add a null/isClosed guard before the second close if closing on multiple paths`,
95
+ evidence: { variable: resourceVar, close_lines: closeLines },
96
+ });
97
+ }
98
+ return { doubleCloses };
99
+ }
100
+ /** True if the given line is inside a `finally` block in the method. */
101
+ isInFinallyBlock(lines, targetLine, methodStart, methodEnd) {
102
+ for (let ln = methodStart; ln <= targetLine && ln <= methodEnd && ln <= lines.length; ln++) {
103
+ if (/\bfinally\b/.test(lines[ln - 1] ?? ''))
104
+ return true;
105
+ }
106
+ return false;
107
+ }
108
+ }
109
+ //# sourceMappingURL=double-close-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"double-close-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/double-close-pass.ts"],"names":[],"mappings":"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"}
@@ -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
+ }
@@ -0,0 +1,126 @@
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
+ /** Exit keywords to scan for as a text-level fallback. */
21
+ const EXIT_KEYWORDS = /\b(return|throw|raise|break|System\.exit|process\.exit|os\._exit|exit!\()\b/;
22
+ export class InfiniteLoopPass {
23
+ name = 'infinite-loop';
24
+ category = 'reliability';
25
+ run(ctx) {
26
+ const { graph, code, language } = ctx;
27
+ if (language === 'bash') {
28
+ return { potentialInfiniteLoops: [] };
29
+ }
30
+ const { blocks, edges } = graph.ir.cfg;
31
+ if (blocks.length === 0)
32
+ return { potentialInfiniteLoops: [] };
33
+ const file = graph.ir.meta.file;
34
+ const codeLines = code.split('\n');
35
+ // Build adjacency maps
36
+ const outgoing = new Map();
37
+ for (const edge of edges) {
38
+ const list = outgoing.get(edge.from) ?? [];
39
+ list.push({ to: edge.to, type: edge.type });
40
+ outgoing.set(edge.from, list);
41
+ }
42
+ // Find back-edges: each back-edge defines a loop
43
+ const backEdges = edges.filter(e => e.type === 'back');
44
+ const potentialInfiniteLoops = [];
45
+ const reportedHeaders = new Set();
46
+ for (const backEdge of backEdges) {
47
+ const headerId = backEdge.to;
48
+ const tailId = backEdge.from;
49
+ const header = graph.blockById.get(headerId);
50
+ const tail = graph.blockById.get(tailId);
51
+ if (!header || !tail)
52
+ continue;
53
+ // Deduplicate: one finding per header
54
+ if (reportedHeaders.has(headerId))
55
+ continue;
56
+ // Collect loop body blocks via BFS from header, stopping at tail
57
+ const bodyIds = new Set();
58
+ const queue = [headerId];
59
+ bodyIds.add(headerId);
60
+ while (queue.length > 0) {
61
+ const cur = queue.shift();
62
+ for (const { to, type } of outgoing.get(cur) ?? []) {
63
+ // Don't follow back edges (stay inside the loop)
64
+ if (type === 'back')
65
+ continue;
66
+ if (!bodyIds.has(to)) {
67
+ bodyIds.add(to);
68
+ queue.push(to);
69
+ }
70
+ }
71
+ // Stop expanding from tail (the back-edge source)
72
+ if (cur === tailId)
73
+ break;
74
+ }
75
+ // Check for any exit edge: an edge from a body block to a non-body block
76
+ let hasExit = false;
77
+ for (const bodyId of bodyIds) {
78
+ for (const { to, type } of outgoing.get(bodyId) ?? []) {
79
+ if (type === 'back')
80
+ continue;
81
+ if (!bodyIds.has(to)) {
82
+ hasExit = true;
83
+ break;
84
+ }
85
+ }
86
+ if (hasExit)
87
+ break;
88
+ }
89
+ if (hasExit)
90
+ continue;
91
+ // Text-level fallback: scan source lines for exit keywords
92
+ const bodyStart = header.start_line;
93
+ const bodyEnd = tail.end_line;
94
+ let hasKeywordExit = false;
95
+ for (let ln = bodyStart; ln <= bodyEnd && ln <= codeLines.length; ln++) {
96
+ if (EXIT_KEYWORDS.test(codeLines[ln - 1] ?? '')) {
97
+ hasKeywordExit = true;
98
+ break;
99
+ }
100
+ }
101
+ if (hasKeywordExit)
102
+ continue;
103
+ reportedHeaders.add(headerId);
104
+ potentialInfiniteLoops.push({ headerLine: header.start_line, bodyEndLine: bodyEnd });
105
+ const loc = bodyStart === bodyEnd
106
+ ? `line ${bodyStart}`
107
+ : `lines ${bodyStart}–${bodyEnd}`;
108
+ ctx.addFinding({
109
+ id: `infinite-loop-${file}-${header.start_line}`,
110
+ pass: this.name,
111
+ category: this.category,
112
+ rule_id: this.name,
113
+ cwe: 'CWE-835',
114
+ severity: 'medium',
115
+ level: 'warning',
116
+ message: `Potential infinite loop: no reachable break, return, or throw found in loop body (${loc})`,
117
+ file,
118
+ line: header.start_line,
119
+ end_line: bodyEnd > header.start_line ? bodyEnd : undefined,
120
+ fix: 'Ensure the loop has a reachable exit condition (break, return, or throw) on all paths',
121
+ });
122
+ }
123
+ return { potentialInfiniteLoops };
124
+ }
125
+ }
126
+ //# sourceMappingURL=infinite-loop-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infinite-loop-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/infinite-loop-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pass #33: react-inline-jsx (category: performance)
3
+ *
4
+ * Detects inline object literals or arrow functions passed as JSX props.
5
+ * These create a new reference on every render, defeating React.memo /
6
+ * shouldComponentUpdate optimisations and causing unnecessary re-renders.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Only runs on JavaScript/TypeScript files that appear to contain JSX
10
+ * (quick check: source contains a `<UpperCase` JSX component pattern).
11
+ * 2. Scans each source line for:
12
+ * a. Inline object prop: propName={{ (double-brace)
13
+ * b. Inline arrow prop: propName={(...) => or propName={identifier =>
14
+ * c. Inline function prop: propName={function(
15
+ * 3. Skips:
16
+ * - `style={{` — idiomatic and near-impossible to hoist statically
17
+ * - `key=` — must be inline
18
+ * - `data-*` attribute names
19
+ * - Lines that are comments
20
+ * 4. Emits one finding per matched line.
21
+ *
22
+ * Languages: JavaScript and TypeScript only.
23
+ */
24
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
25
+ export interface ReactInlineJsxResult {
26
+ inlineProps: Array<{
27
+ line: number;
28
+ propName: string;
29
+ kind: 'object' | 'arrow' | 'function';
30
+ }>;
31
+ }
32
+ export declare class ReactInlineJsxPass implements AnalysisPass<ReactInlineJsxResult> {
33
+ readonly name = "react-inline-jsx";
34
+ readonly category: "performance";
35
+ run(ctx: PassContext): ReactInlineJsxResult;
36
+ }