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,103 @@
1
+ /**
2
+ * Pass #28: swallowed-exception (CWE-390, category: reliability)
3
+ *
4
+ * Detects catch blocks that silently discard exceptions — no re-throw,
5
+ * no logging call, no error return. Swallowed exceptions hide failures,
6
+ * make debugging extremely difficult, and can mask security issues.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Build an ExceptionFlowGraph from the CFG exception edges.
10
+ * 2. For each catch handler entry block, determine the catch body bounds
11
+ * using a brace-depth walk of the source text.
12
+ * 3. Scan the catch body for any "meaningful action": throw/raise,
13
+ * logging API call, or a non-empty return statement.
14
+ * 4. If nothing is found → emit a finding at the catch line.
15
+ *
16
+ * Languages: Java, JavaScript, TypeScript, Python (skip Rust/Bash).
17
+ */
18
+ import { ExceptionFlowGraph } from '../../graph/exception-flow-graph.js';
19
+ const MEANINGFUL_ACTION_RE = /\b(throw|raise|log|logger|console\.(error|warn|log|debug|info)|System\.(out|err)\.|print(?:ln|f)?|warn|error|debug|info|fatal|LOGGER|LOG|logging\.(warning|error|debug|info|critical))\b|\breturn\s+\S/;
20
+ export class SwallowedExceptionPass {
21
+ name = 'swallowed-exception';
22
+ category = 'reliability';
23
+ run(ctx) {
24
+ const { graph, code, language } = ctx;
25
+ if (language === 'rust' || language === 'bash') {
26
+ return { swallowed: [] };
27
+ }
28
+ const { cfg } = graph.ir;
29
+ if (cfg.blocks.length === 0)
30
+ return { swallowed: [] };
31
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
32
+ if (!exGraph.hasTryCatch)
33
+ return { swallowed: [] };
34
+ const file = graph.ir.meta.file;
35
+ const codeLines = code.split('\n');
36
+ const swallowed = [];
37
+ const reported = new Set();
38
+ for (const pair of exGraph.pairs) {
39
+ const catchLine = pair.catchBlock.start_line;
40
+ if (reported.has(catchLine))
41
+ continue;
42
+ // Determine catch body end via brace-depth walk
43
+ const methodInfo = graph.methodAtLine(catchLine);
44
+ const scanEnd = methodInfo ? methodInfo.method.end_line : codeLines.length;
45
+ const catchBodyEnd = this.findCatchBodyEnd(codeLines, catchLine, scanEnd);
46
+ // Scan for any meaningful action
47
+ let hasAction = false;
48
+ for (let ln = catchLine; ln <= catchBodyEnd && ln <= codeLines.length; ln++) {
49
+ if (MEANINGFUL_ACTION_RE.test(codeLines[ln - 1] ?? '')) {
50
+ hasAction = true;
51
+ break;
52
+ }
53
+ }
54
+ if (!hasAction) {
55
+ reported.add(catchLine);
56
+ swallowed.push({ line: catchLine });
57
+ const snippet = (codeLines[catchLine - 1] ?? '').trim();
58
+ ctx.addFinding({
59
+ id: `swallowed-exception-${file}-${catchLine}`,
60
+ pass: this.name,
61
+ category: this.category,
62
+ rule_id: this.name,
63
+ cwe: 'CWE-390',
64
+ severity: 'medium',
65
+ level: 'warning',
66
+ message: `Swallowed exception: catch block at line ${catchLine} has no throw, log, or ` +
67
+ `return — the exception is silently discarded`,
68
+ file,
69
+ line: catchLine,
70
+ snippet,
71
+ fix: 'At minimum log the exception, or re-throw it; never silently discard exceptions',
72
+ });
73
+ }
74
+ }
75
+ return { swallowed };
76
+ }
77
+ /**
78
+ * Walks source lines starting at `startLine` counting brace depth.
79
+ * Returns the line where the brace depth first returns to zero after
80
+ * the opening brace (i.e., the closing brace of the catch block).
81
+ * Capped at `maxLine`.
82
+ */
83
+ findCatchBodyEnd(lines, startLine, maxLine) {
84
+ let depth = 0;
85
+ let started = false;
86
+ for (let ln = startLine; ln <= maxLine && ln <= lines.length; ln++) {
87
+ const text = lines[ln - 1] ?? '';
88
+ for (const ch of text) {
89
+ if (ch === '{') {
90
+ depth++;
91
+ started = true;
92
+ }
93
+ else if (ch === '}' && started) {
94
+ depth--;
95
+ }
96
+ }
97
+ if (started && depth <= 0)
98
+ return ln;
99
+ }
100
+ return maxLine;
101
+ }
102
+ }
103
+ //# sourceMappingURL=swallowed-exception-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swallowed-exception-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/swallowed-exception-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Pass #31: unbounded-collection (CWE-770, category: performance)
3
+ *
4
+ * Detects collections that grow unboundedly inside a loop with no
5
+ * corresponding size limit check or clear/remove operation.
6
+ *
7
+ * Detection strategy:
8
+ * 1. For each loop body (via `graph.loopBodies()`), find all calls in the
9
+ * range whose method_name is a known "grow" operation.
10
+ * 2. For each grow call, extract the receiver as the collection variable.
11
+ * 3. Check if the loop body also contains any shrink operation (`clear`,
12
+ * `remove`, `delete`, `shift`, `pop`, `removeFirst`, `poll`) on the
13
+ * same receiver, OR a size-limit guard in the source text
14
+ * (`size() <`, `length <`, `size() <=`, etc.).
15
+ * 4. If grow-only with no limit found: emit a finding.
16
+ *
17
+ * Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
18
+ */
19
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
20
+ export interface UnboundedCollectionResult {
21
+ unboundedCollections: Array<{
22
+ receiver: string;
23
+ line: number;
24
+ loopStart: number;
25
+ loopEnd: number;
26
+ }>;
27
+ }
28
+ export declare class UnboundedCollectionPass implements AnalysisPass<UnboundedCollectionResult> {
29
+ readonly name = "unbounded-collection";
30
+ readonly category: "performance";
31
+ run(ctx: PassContext): UnboundedCollectionResult;
32
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Pass #31: unbounded-collection (CWE-770, category: performance)
3
+ *
4
+ * Detects collections that grow unboundedly inside a loop with no
5
+ * corresponding size limit check or clear/remove operation.
6
+ *
7
+ * Detection strategy:
8
+ * 1. For each loop body (via `graph.loopBodies()`), find all calls in the
9
+ * range whose method_name is a known "grow" operation.
10
+ * 2. For each grow call, extract the receiver as the collection variable.
11
+ * 3. Check if the loop body also contains any shrink operation (`clear`,
12
+ * `remove`, `delete`, `shift`, `pop`, `removeFirst`, `poll`) on the
13
+ * same receiver, OR a size-limit guard in the source text
14
+ * (`size() <`, `length <`, `size() <=`, etc.).
15
+ * 4. If grow-only with no limit found: emit a finding.
16
+ *
17
+ * Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
18
+ */
19
+ /** Method names that grow a collection, keyed by language group. */
20
+ const GROW_METHODS = {
21
+ java: new Set(['add', 'put', 'offer', 'push', 'addAll', 'addFirst', 'addLast', 'enqueue', 'insert']),
22
+ javascript: new Set(['push', 'set', 'add', 'unshift', 'append', 'prepend']),
23
+ typescript: new Set(['push', 'set', 'add', 'unshift', 'append', 'prepend']),
24
+ python: new Set(['append', 'extend', 'update', 'add', 'insert']),
25
+ rust: new Set(['push', 'insert', 'push_back', 'push_front']),
26
+ };
27
+ /** Method names that shrink a collection. Language-agnostic. */
28
+ const SHRINK_METHODS = new Set([
29
+ 'clear', 'remove', 'delete', 'shift', 'pop', 'removeFirst', 'removeLast',
30
+ 'poll', 'pollFirst', 'pollLast', 'dequeue', 'discard', 'drain',
31
+ ]);
32
+ /** Regex: size limit guard pattern in source text. */
33
+ const SIZE_LIMIT_RE = /\b(?:size|length|count|len)\s*\(\)?\s*[<>]=?\s*\d|\b(?:MAX|LIMIT|CAPACITY|MAX_SIZE)\b/i;
34
+ export class UnboundedCollectionPass {
35
+ name = 'unbounded-collection';
36
+ category = 'performance';
37
+ run(ctx) {
38
+ const { graph, code, language } = ctx;
39
+ if (language === 'bash') {
40
+ return { unboundedCollections: [] };
41
+ }
42
+ const growMethods = GROW_METHODS[language] ?? GROW_METHODS['javascript'];
43
+ const file = graph.ir.meta.file;
44
+ const codeLines = code.split('\n');
45
+ const loops = graph.loopBodies();
46
+ if (loops.length === 0)
47
+ return { unboundedCollections: [] };
48
+ const unboundedCollections = [];
49
+ const reported = new Set();
50
+ for (const loop of loops) {
51
+ const { start_line, end_line } = loop;
52
+ // Collect source text for the loop body (for heuristic checks)
53
+ const loopSource = codeLines.slice(start_line - 1, end_line).join('\n');
54
+ // Find grow calls in the loop body
55
+ const growCalls = [];
56
+ for (const call of graph.ir.calls) {
57
+ const ln = call.location.line;
58
+ if (ln < start_line || ln > end_line)
59
+ continue;
60
+ if (!growMethods.has(call.method_name))
61
+ continue;
62
+ if (!call.receiver)
63
+ continue;
64
+ // Skip 'this' receiver — can't reliably bound
65
+ if (call.receiver === 'this' || call.receiver === 'self')
66
+ continue;
67
+ growCalls.push({ receiver: call.receiver, line: ln });
68
+ }
69
+ if (growCalls.length === 0)
70
+ continue;
71
+ // Group by receiver
72
+ const receiverLines = new Map();
73
+ for (const { receiver, line } of growCalls) {
74
+ if (!receiverLines.has(receiver)) {
75
+ receiverLines.set(receiver, line);
76
+ }
77
+ }
78
+ for (const [receiver, firstGrowLine] of receiverLines.entries()) {
79
+ // Check for shrink operations on the same receiver in the loop body
80
+ let hasShrink = false;
81
+ for (const call of graph.ir.calls) {
82
+ const ln = call.location.line;
83
+ if (ln < start_line || ln > end_line)
84
+ continue;
85
+ if (call.receiver !== receiver)
86
+ continue;
87
+ if (SHRINK_METHODS.has(call.method_name)) {
88
+ hasShrink = true;
89
+ break;
90
+ }
91
+ }
92
+ if (hasShrink)
93
+ continue;
94
+ // Check for size-limit guard in loop source
95
+ if (SIZE_LIMIT_RE.test(loopSource))
96
+ continue;
97
+ const key = `${receiver}-${start_line}`;
98
+ if (reported.has(key))
99
+ continue;
100
+ reported.add(key);
101
+ unboundedCollections.push({
102
+ receiver,
103
+ line: firstGrowLine,
104
+ loopStart: start_line,
105
+ loopEnd: end_line,
106
+ });
107
+ ctx.addFinding({
108
+ id: `unbounded-collection-${file}-${firstGrowLine}`,
109
+ pass: this.name,
110
+ category: this.category,
111
+ rule_id: this.name,
112
+ cwe: 'CWE-770',
113
+ severity: 'medium',
114
+ level: 'warning',
115
+ message: `Unbounded collection: \`${receiver}\` grows inside a loop (lines ${start_line}–${end_line}) ` +
116
+ `with no size limit or clear`,
117
+ file,
118
+ line: firstGrowLine,
119
+ fix: `Add a size limit check (e.g., \`if (${receiver}.size() >= MAX) break;\`) ` +
120
+ `or periodically clear/drain \`${receiver}\`.`,
121
+ evidence: { receiver, loop_start: start_line, loop_end: end_line },
122
+ });
123
+ }
124
+ }
125
+ return { unboundedCollections };
126
+ }
127
+ }
128
+ //# sourceMappingURL=unbounded-collection-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbounded-collection-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/unbounded-collection-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Pass #30: unhandled-exception (CWE-390, category: reliability)
3
+ *
4
+ * Detects explicit throw/raise statements that are not covered by any
5
+ * try/catch in the same function. Uncaught exceptions surface as
6
+ * unhandled-rejection crashes (Node.js) or propagate unexpectedly to
7
+ * callers who may not anticipate them.
8
+ *
9
+ * Detection strategy (conservative, low false-positive):
10
+ * 1. Build ExceptionFlowGraph. Derive "covered" line ranges as
11
+ * [tryBlock.start_line, catchBlock.start_line − 1] for each pair.
12
+ * 2. Scan source lines for explicit throw/raise keywords.
13
+ * 3. Skip if the throw line is already inside a catch block (re-throw).
14
+ * 4. Skip if the throw line falls within any covered range.
15
+ * 5. Emit one finding per enclosing method (avoid duplicate findings for
16
+ * multiple throws in the same uncovered method).
17
+ *
18
+ * Languages: JavaScript, TypeScript, Python only.
19
+ * - Java: checked exceptions are intentionally propagated via `throws`;
20
+ * too noisy without type hierarchy support.
21
+ * - Rust/Bash: no traditional throw/raise; skip.
22
+ */
23
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
24
+ export interface UnhandledExceptionResult {
25
+ unhandled: Array<{
26
+ line: number;
27
+ method: string;
28
+ }>;
29
+ }
30
+ export declare class UnhandledExceptionPass implements AnalysisPass<UnhandledExceptionResult> {
31
+ readonly name = "unhandled-exception";
32
+ readonly category: "reliability";
33
+ run(ctx: PassContext): UnhandledExceptionResult;
34
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Pass #30: unhandled-exception (CWE-390, category: reliability)
3
+ *
4
+ * Detects explicit throw/raise statements that are not covered by any
5
+ * try/catch in the same function. Uncaught exceptions surface as
6
+ * unhandled-rejection crashes (Node.js) or propagate unexpectedly to
7
+ * callers who may not anticipate them.
8
+ *
9
+ * Detection strategy (conservative, low false-positive):
10
+ * 1. Build ExceptionFlowGraph. Derive "covered" line ranges as
11
+ * [tryBlock.start_line, catchBlock.start_line − 1] for each pair.
12
+ * 2. Scan source lines for explicit throw/raise keywords.
13
+ * 3. Skip if the throw line is already inside a catch block (re-throw).
14
+ * 4. Skip if the throw line falls within any covered range.
15
+ * 5. Emit one finding per enclosing method (avoid duplicate findings for
16
+ * multiple throws in the same uncovered method).
17
+ *
18
+ * Languages: JavaScript, TypeScript, Python only.
19
+ * - Java: checked exceptions are intentionally propagated via `throws`;
20
+ * too noisy without type hierarchy support.
21
+ * - Rust/Bash: no traditional throw/raise; skip.
22
+ */
23
+ import { ExceptionFlowGraph } from '../../graph/exception-flow-graph.js';
24
+ const JS_THROW_RE = /^\s*throw\s+/;
25
+ const PYTHON_RAISE_RE = /^\s*raise\b/;
26
+ export class UnhandledExceptionPass {
27
+ name = 'unhandled-exception';
28
+ category = 'reliability';
29
+ run(ctx) {
30
+ const { graph, code, language } = ctx;
31
+ if (language !== 'javascript' && language !== 'typescript' && language !== 'python') {
32
+ return { unhandled: [] };
33
+ }
34
+ const { cfg } = graph.ir;
35
+ const file = graph.ir.meta.file;
36
+ const codeLines = code.split('\n');
37
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
38
+ // Build covered ranges: [tryBlock.start_line, catchBlock.start_line - 1]
39
+ const coveredRanges = [];
40
+ for (const pair of exGraph.pairs) {
41
+ if (pair.catchBlock.start_line > pair.tryBlock.start_line) {
42
+ coveredRanges.push({
43
+ start: pair.tryBlock.start_line,
44
+ end: pair.catchBlock.start_line - 1,
45
+ });
46
+ }
47
+ }
48
+ // Collect catch-block start lines (to detect re-throws)
49
+ const catchStarts = new Set(exGraph.pairs.map(p => p.catchBlock.start_line));
50
+ const throwRe = language === 'python' ? PYTHON_RAISE_RE : JS_THROW_RE;
51
+ const unhandled = [];
52
+ const reportedMethods = new Set();
53
+ for (let ln = 1; ln <= codeLines.length; ln++) {
54
+ const lineText = codeLines[ln - 1] ?? '';
55
+ if (!throwRe.test(lineText))
56
+ continue;
57
+ // Skip re-throws inside catch blocks
58
+ let inCatch = false;
59
+ for (const cs of catchStarts) {
60
+ if (ln >= cs) {
61
+ inCatch = true;
62
+ break;
63
+ }
64
+ }
65
+ // More precise: only skip if ln is actually within a catch body
66
+ // (not just any line after a catch start). Use method boundary check.
67
+ // Simplified: if the line is >= any catch start within the same method, skip.
68
+ // Better heuristic: check if any pair has catchBlock.start_line <= ln
69
+ // and the throw is inside that catch body (ln <= methodEnd of that catch).
70
+ // We use a simple check: if the throw line is >= a catch start and
71
+ // the enclosing method contains the corresponding try, treat as re-throw.
72
+ inCatch = false;
73
+ for (const pair of exGraph.pairs) {
74
+ if (ln >= pair.catchBlock.start_line) {
75
+ // Check same method
76
+ const mThrow = graph.methodAtLine(ln);
77
+ const mCatch = graph.methodAtLine(pair.catchBlock.start_line);
78
+ if (mThrow &&
79
+ mCatch &&
80
+ mThrow.method.start_line === mCatch.method.start_line) {
81
+ inCatch = true;
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ if (inCatch)
87
+ continue;
88
+ // Check if covered by a try/catch range
89
+ const isCovered = coveredRanges.some(r => ln >= r.start && ln <= r.end);
90
+ if (isCovered)
91
+ continue;
92
+ // Deduplicate by enclosing method
93
+ const methodInfo = graph.methodAtLine(ln);
94
+ const methodKey = methodInfo
95
+ ? `${methodInfo.method.start_line}-${methodInfo.method.end_line}`
96
+ : `global-${ln}`;
97
+ if (reportedMethods.has(methodKey))
98
+ continue;
99
+ reportedMethods.add(methodKey);
100
+ const methodName = methodInfo?.method.name ?? '<anonymous>';
101
+ unhandled.push({ line: ln, method: methodName });
102
+ const snippet = lineText.trim();
103
+ ctx.addFinding({
104
+ id: `unhandled-exception-${file}-${ln}`,
105
+ pass: this.name,
106
+ category: this.category,
107
+ rule_id: this.name,
108
+ cwe: 'CWE-390',
109
+ severity: 'medium',
110
+ level: 'warning',
111
+ message: `Unhandled exception: \`throw\` at line ${ln} in \`${methodName}\` is not inside ` +
112
+ `a try/catch — callers receive an unexpected exception`,
113
+ file,
114
+ line: ln,
115
+ snippet,
116
+ fix: 'Wrap throwing code in a try/catch, or document the exception in the function signature',
117
+ evidence: { method: methodName },
118
+ });
119
+ }
120
+ return { unhandled };
121
+ }
122
+ }
123
+ //# sourceMappingURL=unhandled-exception-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unhandled-exception-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/unhandled-exception-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pass #32: use-after-close (CWE-672, category: reliability)
3
+ *
4
+ * Detects method calls on a resource variable that occur after the resource
5
+ * has been closed (close/dispose/shutdown). Using a closed stream or
6
+ * connection throws an IOException or similar, causing unexpected runtime
7
+ * failures.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Find resource-opening calls (same patterns as resource-leak-pass).
11
+ * 2. Find the FIRST close() call on the resource variable within the method.
12
+ * 3. If a close is found, scan subsequent calls on the same receiver variable
13
+ * that are NOT themselves close calls → use-after-close.
14
+ *
15
+ * Languages: Java, JavaScript, TypeScript, Python, Rust (skip Bash).
16
+ */
17
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
18
+ export interface UseAfterCloseResult {
19
+ useAfterCloses: Array<{
20
+ openLine: number;
21
+ closeLine: number;
22
+ useLine: number;
23
+ variable: string;
24
+ }>;
25
+ }
26
+ export declare class UseAfterClosePass implements AnalysisPass<UseAfterCloseResult> {
27
+ readonly name = "use-after-close";
28
+ readonly category: "reliability";
29
+ run(ctx: PassContext): UseAfterCloseResult;
30
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Pass #32: use-after-close (CWE-672, category: reliability)
3
+ *
4
+ * Detects method calls on a resource variable that occur after the resource
5
+ * has been closed (close/dispose/shutdown). Using a closed stream or
6
+ * connection throws an IOException or similar, causing unexpected runtime
7
+ * failures.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Find resource-opening calls (same patterns as resource-leak-pass).
11
+ * 2. Find the FIRST close() call on the resource variable within the method.
12
+ * 3. If a close is found, scan subsequent calls on the same receiver variable
13
+ * that are NOT themselves close calls → use-after-close.
14
+ *
15
+ * Languages: Java, JavaScript, TypeScript, Python, Rust (skip Bash).
16
+ */
17
+ const RESOURCE_CTORS = new Set([
18
+ 'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
19
+ 'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
20
+ 'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
21
+ 'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
22
+ 'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
23
+ 'FileChannel', 'Socket', 'ServerSocket', 'DatagramSocket',
24
+ ]);
25
+ const RESOURCE_FACTORY_METHODS = new Set([
26
+ 'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
27
+ 'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
28
+ 'open', 'createReadStream', 'createWriteStream', 'createConnection',
29
+ ]);
30
+ const CLOSE_METHODS = new Set([
31
+ 'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
32
+ 'shutdownNow', 'terminate',
33
+ ]);
34
+ export class UseAfterClosePass {
35
+ name = 'use-after-close';
36
+ category = 'reliability';
37
+ run(ctx) {
38
+ const { graph, code } = ctx;
39
+ if (ctx.language === 'bash')
40
+ return { useAfterCloses: [] };
41
+ const file = graph.ir.meta.file;
42
+ const codeLines = code.split('\n');
43
+ const useAfterCloses = [];
44
+ for (const call of graph.ir.calls) {
45
+ const name = call.method_name;
46
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
47
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
48
+ if (!isConstructor && !isFactory)
49
+ continue;
50
+ const openLine = call.location.line;
51
+ const defs = graph.defsAtLine(openLine);
52
+ if (defs.length === 0)
53
+ continue;
54
+ const resourceVar = defs[0].variable;
55
+ const methodInfo = graph.methodAtLine(openLine);
56
+ if (!methodInfo)
57
+ continue;
58
+ const methodEnd = methodInfo.method.end_line;
59
+ // Find the FIRST close call on resourceVar
60
+ const firstClose = graph.ir.calls
61
+ .filter(c => CLOSE_METHODS.has(c.method_name) &&
62
+ c.receiver === resourceVar &&
63
+ c.location.line > openLine &&
64
+ c.location.line <= methodEnd)
65
+ .sort((a, b) => a.location.line - b.location.line)[0];
66
+ if (!firstClose)
67
+ continue; // No close → handled by resource-leak pass
68
+ const closeLine = firstClose.location.line;
69
+ // Find any non-close call on resourceVar after closeLine
70
+ const usesAfterClose = graph.ir.calls.filter(c => c.receiver === resourceVar &&
71
+ c.location.line > closeLine &&
72
+ c.location.line <= methodEnd &&
73
+ !CLOSE_METHODS.has(c.method_name));
74
+ for (const use of usesAfterClose) {
75
+ const useLine = use.location.line;
76
+ useAfterCloses.push({ openLine, closeLine, useLine, variable: resourceVar });
77
+ const snippet = (codeLines[useLine - 1] ?? '').trim();
78
+ ctx.addFinding({
79
+ id: `use-after-close-${file}-${useLine}`,
80
+ pass: this.name,
81
+ category: this.category,
82
+ rule_id: this.name,
83
+ cwe: 'CWE-672',
84
+ severity: 'high',
85
+ level: 'error',
86
+ message: `Use after close: \`${resourceVar}.${use.method_name}()\` at line ${useLine} ` +
87
+ `is called after \`${resourceVar}.close()\` at line ${closeLine}`,
88
+ file,
89
+ line: useLine,
90
+ snippet,
91
+ fix: `Do not use a resource after closing it; keep \`${resourceVar}\` open ` +
92
+ `until all uses are complete`,
93
+ evidence: { variable: resourceVar, close_line: closeLine, open_line: openLine },
94
+ });
95
+ }
96
+ }
97
+ return { useAfterCloses };
98
+ }
99
+ }
100
+ //# sourceMappingURL=use-after-close-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-after-close-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/use-after-close-pass.ts"],"names":[],"mappings":"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"}
@@ -279,6 +279,7 @@ function findSinks(calls, patterns) {
279
279
  location,
280
280
  line: call.location.line,
281
281
  confidence,
282
+ method: call.method_name,
282
283
  });
283
284
  }
284
285
  }