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.
- package/dist/analysis/passes/broad-catch-pass.d.ts +29 -0
- package/dist/analysis/passes/broad-catch-pass.js +79 -0
- package/dist/analysis/passes/broad-catch-pass.js.map +1 -0
- package/dist/analysis/passes/deep-inheritance-pass.d.ts +30 -0
- package/dist/analysis/passes/deep-inheritance-pass.js +82 -0
- package/dist/analysis/passes/deep-inheritance-pass.js.map +1 -0
- package/dist/analysis/passes/double-close-pass.d.ts +33 -0
- package/dist/analysis/passes/double-close-pass.js +109 -0
- package/dist/analysis/passes/double-close-pass.js.map +1 -0
- package/dist/analysis/passes/infinite-loop-pass.d.ts +31 -0
- package/dist/analysis/passes/infinite-loop-pass.js +126 -0
- package/dist/analysis/passes/infinite-loop-pass.js.map +1 -0
- package/dist/analysis/passes/react-inline-jsx-pass.d.ts +36 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js +140 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js.map +1 -0
- package/dist/analysis/passes/redundant-loop-pass.d.ts +30 -0
- package/dist/analysis/passes/redundant-loop-pass.js +146 -0
- package/dist/analysis/passes/redundant-loop-pass.js.map +1 -0
- package/dist/analysis/passes/serial-await-pass.d.ts +36 -0
- package/dist/analysis/passes/serial-await-pass.js +132 -0
- package/dist/analysis/passes/serial-await-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.js +7 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/swallowed-exception-pass.d.ts +35 -0
- package/dist/analysis/passes/swallowed-exception-pass.js +103 -0
- package/dist/analysis/passes/swallowed-exception-pass.js.map +1 -0
- package/dist/analysis/passes/unbounded-collection-pass.d.ts +32 -0
- package/dist/analysis/passes/unbounded-collection-pass.js +128 -0
- package/dist/analysis/passes/unbounded-collection-pass.js.map +1 -0
- package/dist/analysis/passes/unhandled-exception-pass.d.ts +34 -0
- package/dist/analysis/passes/unhandled-exception-pass.js +123 -0
- package/dist/analysis/passes/unhandled-exception-pass.js.map +1 -0
- package/dist/analysis/passes/use-after-close-pass.d.ts +30 -0
- package/dist/analysis/passes/use-after-close-pass.js +100 -0
- package/dist/analysis/passes/use-after-close-pass.js.map +1 -0
- package/dist/analysis/taint-matcher.js +1 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +12 -1
- package/dist/analyzer.js +34 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +1035 -3
- package/dist/core/circle-ir-core.cjs +2 -1
- package/dist/core/circle-ir-core.js +2 -1
- package/dist/graph/dominator-graph.d.ts +53 -0
- package/dist/graph/dominator-graph.js +256 -0
- package/dist/graph/dominator-graph.js.map +1 -0
- package/dist/graph/exception-flow-graph.d.ts +44 -0
- package/dist/graph/exception-flow-graph.js +75 -0
- package/dist/graph/exception-flow-graph.js.map +1 -0
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.js +2 -0
- package/dist/graph/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- 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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"}
|