circle-ir 3.9.8 → 3.11.0

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 (52) 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/cleanup-verify-pass.d.ts +28 -0
  5. package/dist/analysis/passes/cleanup-verify-pass.js +130 -0
  6. package/dist/analysis/passes/cleanup-verify-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/missing-guard-dom-pass.d.ts +25 -0
  11. package/dist/analysis/passes/missing-guard-dom-pass.js +99 -0
  12. package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -0
  13. package/dist/analysis/passes/missing-override-pass.d.ts +27 -0
  14. package/dist/analysis/passes/missing-override-pass.js +110 -0
  15. package/dist/analysis/passes/missing-override-pass.js.map +1 -0
  16. package/dist/analysis/passes/sink-filter-pass.js +88 -9
  17. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  18. package/dist/analysis/passes/swallowed-exception-pass.d.ts +35 -0
  19. package/dist/analysis/passes/swallowed-exception-pass.js +103 -0
  20. package/dist/analysis/passes/swallowed-exception-pass.js.map +1 -0
  21. package/dist/analysis/passes/taint-matcher-pass.js +6 -1
  22. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
  23. package/dist/analysis/passes/taint-propagation-pass.js +2 -3
  24. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  25. package/dist/analysis/passes/unhandled-exception-pass.d.ts +34 -0
  26. package/dist/analysis/passes/unhandled-exception-pass.js +123 -0
  27. package/dist/analysis/passes/unhandled-exception-pass.js.map +1 -0
  28. package/dist/analysis/passes/unused-interface-method-pass.d.ts +27 -0
  29. package/dist/analysis/passes/unused-interface-method-pass.js +62 -0
  30. package/dist/analysis/passes/unused-interface-method-pass.js.map +1 -0
  31. package/dist/analysis/passes/use-after-close-pass.d.ts +30 -0
  32. package/dist/analysis/passes/use-after-close-pass.js +100 -0
  33. package/dist/analysis/passes/use-after-close-pass.js.map +1 -0
  34. package/dist/analysis/taint-matcher.d.ts +2 -1
  35. package/dist/analysis/taint-matcher.js +10 -5
  36. package/dist/analysis/taint-matcher.js.map +1 -1
  37. package/dist/analyzer.d.ts +12 -3
  38. package/dist/analyzer.js +30 -3
  39. package/dist/analyzer.js.map +1 -1
  40. package/dist/browser/circle-ir.js +1523 -18
  41. package/dist/core/circle-ir-core.cjs +10 -6
  42. package/dist/core/circle-ir-core.js +10 -6
  43. package/dist/graph/exception-flow-graph.d.ts +44 -0
  44. package/dist/graph/exception-flow-graph.js +75 -0
  45. package/dist/graph/exception-flow-graph.js.map +1 -0
  46. package/dist/graph/index.d.ts +1 -0
  47. package/dist/graph/index.js +1 -0
  48. package/dist/graph/index.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. 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,28 @@
1
+ /**
2
+ * Pass: cleanup-verify (#54, CWE-772)
3
+ *
4
+ * Detects resources that have a close() call but that close() does not
5
+ * post-dominate the acquisition point — meaning some control-flow paths
6
+ * skip the cleanup entirely.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Find resource-opening calls (same set as ResourceLeakPass).
10
+ * 2. Locate the corresponding close() call within the enclosing method.
11
+ * 3. Build a post-dominator graph by reversing all CFG edges and computing
12
+ * a DominatorGraph from the exit block.
13
+ * 4. If close() block does NOT post-dominate the open block → emit finding.
14
+ *
15
+ * Languages: Java, Python, JavaScript/TypeScript.
16
+ * Skips: Rust (RAII guarantees cleanup), Bash.
17
+ *
18
+ * Note: complements ResourceLeakPass, which handles the no-close() case.
19
+ */
20
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
21
+ export interface CleanupVerifyResult {
22
+ findings: number;
23
+ }
24
+ export declare class CleanupVerifyPass implements AnalysisPass<CleanupVerifyResult> {
25
+ readonly name = "cleanup-verify";
26
+ readonly category: "reliability";
27
+ run(ctx: PassContext): CleanupVerifyResult;
28
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Pass: cleanup-verify (#54, CWE-772)
3
+ *
4
+ * Detects resources that have a close() call but that close() does not
5
+ * post-dominate the acquisition point — meaning some control-flow paths
6
+ * skip the cleanup entirely.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Find resource-opening calls (same set as ResourceLeakPass).
10
+ * 2. Locate the corresponding close() call within the enclosing method.
11
+ * 3. Build a post-dominator graph by reversing all CFG edges and computing
12
+ * a DominatorGraph from the exit block.
13
+ * 4. If close() block does NOT post-dominate the open block → emit finding.
14
+ *
15
+ * Languages: Java, Python, JavaScript/TypeScript.
16
+ * Skips: Rust (RAII guarantees cleanup), Bash.
17
+ *
18
+ * Note: complements ResourceLeakPass, which handles the no-close() case.
19
+ */
20
+ import { DominatorGraph } from '../../graph/dominator-graph.js';
21
+ /** Resource-opening constructors (same set as ResourceLeakPass). */
22
+ const RESOURCE_CTORS = new Set([
23
+ 'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
24
+ 'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
25
+ 'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
26
+ 'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
27
+ 'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
28
+ 'FileChannel', 'Socket', 'ServerSocket', 'DatagramSocket',
29
+ ]);
30
+ /** Factory / open methods that return closeable resources. */
31
+ const RESOURCE_FACTORY_METHODS = new Set([
32
+ 'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
33
+ 'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
34
+ 'open', 'createReadStream', 'createWriteStream', 'createConnection',
35
+ ]);
36
+ /** Methods that release a resource. */
37
+ const CLOSE_METHODS = new Set([
38
+ 'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
39
+ 'shutdownNow', 'terminate',
40
+ ]);
41
+ /**
42
+ * Build a post-dominator graph by reversing all CFG edges and running
43
+ * the dominator algorithm from the exit block.
44
+ * `postDom.dominates(A, B)` means "A post-dominates B in the original CFG".
45
+ */
46
+ function buildPostDomGraph(cfg) {
47
+ const exitBlock = cfg.blocks.find(b => b.type === 'exit') ??
48
+ cfg.blocks.find(b => !cfg.edges.some(e => e.from === b.id));
49
+ if (!exitBlock || cfg.blocks.length === 0) {
50
+ return new DominatorGraph({ blocks: [], edges: [] });
51
+ }
52
+ const reversed = {
53
+ blocks: cfg.blocks,
54
+ edges: cfg.edges.map(e => ({ from: e.to, to: e.from, type: e.type })),
55
+ };
56
+ return new DominatorGraph(reversed, exitBlock.id);
57
+ }
58
+ export class CleanupVerifyPass {
59
+ name = 'cleanup-verify';
60
+ category = 'reliability';
61
+ run(ctx) {
62
+ const { graph, language } = ctx;
63
+ // Rust RAII guarantees cleanup; Bash has no structured resource model
64
+ if (language === 'rust' || language === 'bash')
65
+ return { findings: 0 };
66
+ const { cfg, calls } = graph.ir;
67
+ const file = graph.ir.meta.file;
68
+ if (cfg.blocks.length === 0)
69
+ return { findings: 0 };
70
+ const postDom = buildPostDomGraph(cfg);
71
+ const blockContainingLine = (line) => cfg.blocks.find(b => b.start_line <= line && line <= b.end_line) ?? null;
72
+ let count = 0;
73
+ for (const call of calls) {
74
+ const name = call.method_name;
75
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
76
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
77
+ if (!isConstructor && !isFactory)
78
+ continue;
79
+ const openLine = call.location.line;
80
+ // Resource must be captured in a variable to be trackable
81
+ const defs = graph.defsAtLine(openLine);
82
+ if (defs.length === 0)
83
+ continue;
84
+ const resourceVar = defs[0].variable;
85
+ const methodInfo = graph.methodAtLine(openLine);
86
+ if (!methodInfo)
87
+ continue;
88
+ const methodEnd = methodInfo.method.end_line;
89
+ // Find the first close() call for this resource within the enclosing method
90
+ const closeCall = calls.find(c => CLOSE_METHODS.has(c.method_name) &&
91
+ c.receiver === resourceVar &&
92
+ c.location.line > openLine &&
93
+ c.location.line <= methodEnd);
94
+ // ResourceLeakPass handles the no-close() case; we only care about
95
+ // close() calls that may be skipped on some paths
96
+ if (!closeCall)
97
+ continue;
98
+ const openBlock = blockContainingLine(openLine);
99
+ const closeBlock = blockContainingLine(closeCall.location.line);
100
+ if (!openBlock || !closeBlock)
101
+ continue;
102
+ // If close post-dominates open, cleanup is guaranteed on every exit path
103
+ if (postDom.dominates(closeBlock.id, openBlock.id))
104
+ continue;
105
+ count++;
106
+ ctx.addFinding({
107
+ id: `cleanup-verify-${file}-${openLine}`,
108
+ pass: this.name,
109
+ category: this.category,
110
+ rule_id: 'cleanup-verify',
111
+ cwe: 'CWE-772',
112
+ severity: 'medium',
113
+ level: 'warning',
114
+ message: `Resource \`${resourceVar}\` opened at line ${openLine} may not close on all ` +
115
+ `paths — close() at line ${closeCall.location.line} does not post-dominate ` +
116
+ `the acquisition`,
117
+ file,
118
+ line: openLine,
119
+ fix: 'Use try-with-resources (Java) or a finally block to guarantee cleanup on all paths',
120
+ evidence: {
121
+ resource: name,
122
+ variable: resourceVar,
123
+ close_line: closeCall.location.line,
124
+ },
125
+ });
126
+ }
127
+ return { findings: count };
128
+ }
129
+ }
130
+ //# sourceMappingURL=cleanup-verify-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup-verify-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/cleanup-verify-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGhE,oEAAoE;AACpE,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC;IAClD,iBAAiB,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY;IACjE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,mBAAmB;IACtE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB;IAC/E,mBAAmB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,iBAAiB;IAC9E,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB;IAC1E,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,gBAAgB;CAC1D,CAAC,CAAC;AAEH,8DAA8D;AAC9D,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IAC5D,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB;IACnE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB;IAC1D,MAAM,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,kBAAkB;CACpE,CAAC,CAAC;AAEH,uCAAuC;AACvC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IACjD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM;IAC1E,aAAa,EAAE,WAAW;CAC3B,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,GAAQ;IACjC,MAAM,SAAS,GACb,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;QACvC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE9D,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,QAAQ,GAAQ;QACpB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;KACtE,CAAC;IAEF,OAAO,IAAI,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAMD,MAAM,OAAO,iBAAiB;IACnB,IAAI,GAAG,gBAAgB,CAAC;IACxB,QAAQ,GAAG,aAAsB,CAAC;IAE3C,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;QAEhC,sEAAsE;QACtE,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,MAAM;YAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAEvE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAEhC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAEpD,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAEvC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,EAAE,CAC3C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;QAE3E,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;YAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,KAAK,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/E,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7E,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS;gBAAE,SAAS;YAE3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAEpC,0DAA0D;YAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAChC,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAErC,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAC1B,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;YAE7C,4EAA4E;YAC5E,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAC1B,CAAC,CAAC,EAAE,CACF,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC;gBAChC,CAAC,CAAC,QAAQ,KAAK,WAAW;gBAC1B,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,QAAQ;gBAC1B,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAS,CAC/B,CAAC;YAEF,mEAAmE;YACnE,kDAAkD;YAClD,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEzB,MAAM,SAAS,GAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YACjD,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,SAAS,IAAI,CAAC,UAAU;gBAAE,SAAS;YAExC,yEAAyE;YACzE,IAAI,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC;gBAAE,SAAS;YAE7D,KAAK,EAAE,CAAC;YACR,GAAG,CAAC,UAAU,CAAC;gBACb,EAAE,EAAE,kBAAkB,IAAI,IAAI,QAAQ,EAAE;gBACxC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,gBAAgB;gBACzB,GAAG,EAAE,SAAS;gBACd,QAAQ,EAAE,QAAQ;gBAClB,KAAK,EAAE,SAAS;gBAChB,OAAO,EACL,cAAc,WAAW,qBAAqB,QAAQ,wBAAwB;oBAC9E,2BAA2B,SAAS,CAAC,QAAQ,CAAC,IAAI,0BAA0B;oBAC5E,iBAAiB;gBACnB,IAAI;gBACJ,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,oFAAoF;gBACzF,QAAQ,EAAE;oBACR,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,WAAW;oBACrB,UAAU,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI;iBACpC;aACF,CAAC,CAAC;QACL,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF"}
@@ -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,25 @@
1
+ /**
2
+ * Pass: missing-guard-dom (#53, CWE-285)
3
+ *
4
+ * Detects sensitive operations that are not dominated by an authentication
5
+ * or authorization check on all control-flow paths within the same method.
6
+ *
7
+ * Detection strategy:
8
+ * 1. Identify calls to known authentication methods and sensitive operations.
9
+ * 2. Build a DominatorGraph from the file-level CFG.
10
+ * 3. For each sensitive operation, find the CFG block containing it and check
11
+ * whether any auth-check block in the same method dominates that block.
12
+ * 4. If no auth-check block dominates the sensitive-op block → emit finding.
13
+ *
14
+ * Language: Java only (other languages handled differently or not yet).
15
+ * Dedup: at most one finding per method.
16
+ */
17
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
18
+ export interface MissingGuardDomResult {
19
+ findings: number;
20
+ }
21
+ export declare class MissingGuardDomPass implements AnalysisPass<MissingGuardDomResult> {
22
+ readonly name = "missing-guard-dom";
23
+ readonly category: "security";
24
+ run(ctx: PassContext): MissingGuardDomResult;
25
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pass: missing-guard-dom (#53, CWE-285)
3
+ *
4
+ * Detects sensitive operations that are not dominated by an authentication
5
+ * or authorization check on all control-flow paths within the same method.
6
+ *
7
+ * Detection strategy:
8
+ * 1. Identify calls to known authentication methods and sensitive operations.
9
+ * 2. Build a DominatorGraph from the file-level CFG.
10
+ * 3. For each sensitive operation, find the CFG block containing it and check
11
+ * whether any auth-check block in the same method dominates that block.
12
+ * 4. If no auth-check block dominates the sensitive-op block → emit finding.
13
+ *
14
+ * Language: Java only (other languages handled differently or not yet).
15
+ * Dedup: at most one finding per method.
16
+ */
17
+ import { DominatorGraph } from '../../graph/dominator-graph.js';
18
+ const AUTH_METHODS = new Set([
19
+ 'authenticate', 'isAuthenticated', 'isAuthorized', 'isAdmin',
20
+ 'checkAuth', 'hasPermission', 'requiresAuth', 'verifyToken',
21
+ 'validateToken', 'checkRole', 'authorize', 'isLoggedIn',
22
+ ]);
23
+ const SENSITIVE_METHODS = new Set([
24
+ 'delete', 'deleteById', 'drop', 'truncate', 'executeUpdate',
25
+ 'createUser', 'createAdmin', 'modifyPermission', 'grantRole',
26
+ 'setAdmin', 'elevatePrivilege',
27
+ ]);
28
+ export class MissingGuardDomPass {
29
+ name = 'missing-guard-dom';
30
+ category = 'security';
31
+ run(ctx) {
32
+ const { graph, language } = ctx;
33
+ if (language !== 'java')
34
+ return { findings: 0 };
35
+ const { cfg, calls } = graph.ir;
36
+ if (cfg.blocks.length === 0 || cfg.edges.length === 0)
37
+ return { findings: 0 };
38
+ const dom = new DominatorGraph(cfg);
39
+ const file = graph.ir.meta.file;
40
+ // Collect auth-check and sensitive-op call lines from the IR
41
+ const authCallLines = [];
42
+ const sensitiveOps = [];
43
+ for (const call of calls) {
44
+ if (AUTH_METHODS.has(call.method_name)) {
45
+ authCallLines.push(call.location.line);
46
+ }
47
+ if (SENSITIVE_METHODS.has(call.method_name)) {
48
+ sensitiveOps.push({ line: call.location.line, method: call.method_name });
49
+ }
50
+ }
51
+ if (sensitiveOps.length === 0)
52
+ return { findings: 0 };
53
+ // Helper: find the CFG block whose [start_line, end_line] contains a given line
54
+ const blockContainingLine = (line) => cfg.blocks.find(b => b.start_line <= line && line <= b.end_line) ?? null;
55
+ // Emit at most one finding per method to avoid noise
56
+ const reportedMethods = new Set();
57
+ let count = 0;
58
+ for (const op of sensitiveOps) {
59
+ const opBlock = blockContainingLine(op.line);
60
+ if (!opBlock)
61
+ continue;
62
+ const methodInfo = graph.methodAtLine(op.line);
63
+ if (!methodInfo)
64
+ continue;
65
+ const methodKey = `${methodInfo.type.name}::${methodInfo.method.name}`;
66
+ if (reportedMethods.has(methodKey))
67
+ continue;
68
+ const { start_line, end_line } = methodInfo.method;
69
+ // Restrict auth checks to those inside the same method
70
+ const authInMethod = authCallLines.filter(l => l >= start_line && l <= end_line);
71
+ // Check whether any auth-check block dominates the sensitive-op block
72
+ const dominated = authInMethod.some(authLine => {
73
+ const authBlock = blockContainingLine(authLine);
74
+ return authBlock !== null && dom.dominates(authBlock.id, opBlock.id);
75
+ });
76
+ if (!dominated) {
77
+ reportedMethods.add(methodKey);
78
+ count++;
79
+ ctx.addFinding({
80
+ id: `missing-guard-dom-${file}-${op.line}`,
81
+ pass: this.name,
82
+ category: this.category,
83
+ rule_id: 'missing-guard-dom',
84
+ cwe: 'CWE-285',
85
+ severity: 'high',
86
+ level: 'error',
87
+ message: `Sensitive operation \`${op.method}()\` at line ${op.line} is not dominated ` +
88
+ `by an authentication check`,
89
+ file,
90
+ line: op.line,
91
+ fix: `Add authentication/authorization check on all paths leading to line ${op.line}`,
92
+ evidence: { method: op.method },
93
+ });
94
+ }
95
+ }
96
+ return { findings: count };
97
+ }
98
+ }
99
+ //# sourceMappingURL=missing-guard-dom-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-guard-dom-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/missing-guard-dom-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Pass: missing-override (#64)
3
+ *
4
+ * Detects Java methods that override a parent class method but lack the
5
+ * @Override annotation. Without @Override the compiler cannot catch signature
6
+ * mismatches introduced by a parent-class refactoring.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Build a map of class → method names from all types in the IR.
10
+ * 2. Build a parent map: class name → direct parent class name (strip generics).
11
+ * 3. For each class that has a parent in the same file, walk the inheritance
12
+ * chain (max 10 hops, cycle guard) to collect all ancestor method names.
13
+ * 4. For each non-constructor, non-private, non-static, non-abstract method
14
+ * whose name appears in the ancestor set — if @Override is absent → finding.
15
+ *
16
+ * Language: Java only.
17
+ * Dedup: at most one finding per class:method pair.
18
+ */
19
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
20
+ export interface MissingOverrideResult {
21
+ findings: number;
22
+ }
23
+ export declare class MissingOverridePass implements AnalysisPass<MissingOverrideResult> {
24
+ readonly name = "missing-override";
25
+ readonly category: "maintainability";
26
+ run(ctx: PassContext): MissingOverrideResult;
27
+ }