circle-ir 3.8.3 → 3.9.7
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/README.md +82 -5
- package/dist/analysis/config-loader.js +1 -1
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/dfg-verifier.d.ts +3 -14
- package/dist/analysis/dfg-verifier.js +43 -74
- package/dist/analysis/dfg-verifier.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts +5 -1
- package/dist/analysis/interprocedural.js +62 -60
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/metrics/index.d.ts +2 -0
- package/dist/analysis/metrics/index.js +2 -0
- package/dist/analysis/metrics/index.js.map +1 -0
- package/dist/analysis/metrics/metric-pass.d.ts +27 -0
- package/dist/analysis/metrics/metric-pass.js +2 -0
- package/dist/analysis/metrics/metric-pass.js.map +1 -0
- package/dist/analysis/metrics/metric-runner.d.ts +21 -0
- package/dist/analysis/metrics/metric-runner.js +47 -0
- package/dist/analysis/metrics/metric-runner.js.map +1 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
- package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
- package/dist/analysis/passes/circular-dependency-pass.js +39 -0
- package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/constant-propagation-pass.js +44 -0
- package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
- package/dist/analysis/passes/cross-file-pass.js +102 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -0
- package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
- package/dist/analysis/passes/dead-code-pass.js +117 -0
- package/dist/analysis/passes/dead-code-pass.js.map +1 -0
- package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
- package/dist/analysis/passes/interprocedural-pass.js +169 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
- package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
- package/dist/analysis/passes/language-sources-pass.js +491 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -0
- package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
- package/dist/analysis/passes/leaked-global-pass.js +108 -0
- package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
- package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
- package/dist/analysis/passes/missing-await-pass.js +90 -0
- package/dist/analysis/passes/missing-await-pass.js.map +1 -0
- package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
- package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
- package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
- package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
- package/dist/analysis/passes/n-plus-one-pass.js +100 -0
- package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
- package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
- package/dist/analysis/passes/null-deref-pass.js +130 -0
- package/dist/analysis/passes/null-deref-pass.js.map +1 -0
- package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
- package/dist/analysis/passes/orphan-module-pass.js +38 -0
- package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
- package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
- package/dist/analysis/passes/resource-leak-pass.js +156 -0
- package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
- package/dist/analysis/passes/sink-filter-pass.js +231 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
- package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
- package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
- package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
- package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
- package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
- package/dist/analysis/passes/sync-io-async-pass.js +80 -0
- package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
- package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
- package/dist/analysis/passes/taint-matcher-pass.js +71 -0
- package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
- package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/taint-propagation-pass.js +266 -0
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
- package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
- package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
- package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
- package/dist/analysis/passes/unchecked-return-pass.js +106 -0
- package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
- package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
- package/dist/analysis/passes/unused-variable-pass.js +150 -0
- package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
- package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
- package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
- package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +3 -13
- package/dist/analysis/path-finder.js +48 -63
- package/dist/analysis/path-finder.js.map +1 -1
- package/dist/analysis/taint-matcher.js +8 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +5 -1
- package/dist/analysis/taint-propagation.js +44 -41
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts +42 -1
- package/dist/analyzer.js +234 -1476
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3414 -1272
- package/dist/core/circle-ir-core.cjs +361 -107
- package/dist/core/circle-ir-core.js +361 -107
- package/dist/core/extractors/imports.js +18 -0
- package/dist/core/extractors/imports.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +68 -0
- package/dist/graph/analysis-pass.js +51 -0
- package/dist/graph/analysis-pass.js.map +1 -0
- package/dist/graph/code-graph.d.ts +92 -0
- package/dist/graph/code-graph.js +262 -0
- package/dist/graph/code-graph.js.map +1 -0
- package/dist/graph/import-graph.d.ts +33 -0
- package/dist/graph/import-graph.js +170 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/project-graph.d.ts +43 -0
- package/dist/graph/project-graph.js +80 -0
- package/dist/graph/project-graph.js.map +1 -0
- package/dist/graph/scope-graph.d.ts +63 -0
- package/dist/graph/scope-graph.js +89 -0
- package/dist/graph/scope-graph.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resolution/cross-file.js +52 -19
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/types/index.d.ts +151 -0
- package/docs/SPEC.md +10 -6
- package/package.json +3 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #20: null-deref (CWE-476, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects variables that are explicitly assigned null/None/undefined and then
|
|
5
|
+
* used as a receiver (method call or field access) without an intervening
|
|
6
|
+
* null guard.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Find DFG defs where the expression is an explicit null literal
|
|
10
|
+
* (null / None / undefined).
|
|
11
|
+
* 2. For each such def, find all DFG uses via graph.usesOfDef().
|
|
12
|
+
* 3. For each use that occurs after the def in the same method and is used
|
|
13
|
+
* as a receiver, check whether any line between def and use contains a
|
|
14
|
+
* null-check for that variable.
|
|
15
|
+
* 4. Emit a finding if no guard is found.
|
|
16
|
+
*
|
|
17
|
+
* Scope is limited to the enclosing method to avoid cross-method FPs.
|
|
18
|
+
*/
|
|
19
|
+
/** Expression values that represent an explicit null assignment. */
|
|
20
|
+
const NULL_EXPR_RE = /^\s*(null|None|undefined)\s*$/;
|
|
21
|
+
/** Escape a variable name for use in a RegExp. */
|
|
22
|
+
function escRe(s) {
|
|
23
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if any source line in the range [fromLine, toLine) contains a
|
|
27
|
+
* null-guard pattern referencing varName.
|
|
28
|
+
*
|
|
29
|
+
* Guards recognised:
|
|
30
|
+
* - Java/JS: `x != null`, `x !== null`, `null != x`
|
|
31
|
+
* - Java/JS: `x == null`, `x === null` (guard on the null branch)
|
|
32
|
+
* - Python: `x is not None`, `if x:`
|
|
33
|
+
* - Optional chaining: `x?.`
|
|
34
|
+
* - Optional API: `x.isPresent()`, `Optional`
|
|
35
|
+
*/
|
|
36
|
+
function hasNullGuard(codeLines, varName, fromLine, toLine) {
|
|
37
|
+
const esc = escRe(varName);
|
|
38
|
+
// Build one composite regex (OR of guard patterns)
|
|
39
|
+
const pattern = new RegExp(`\\b${esc}\\b\\s*!==?\\s*(null|None|undefined)` +
|
|
40
|
+
`|(null|None|undefined)\\s*!==?\\s*\\b${esc}\\b` +
|
|
41
|
+
`|\\b${esc}\\b\\s*===?\\s*(null|None|undefined)` + // null-branch check
|
|
42
|
+
`|(null|None|undefined)\\s*===?\\s*\\b${esc}\\b` +
|
|
43
|
+
`|\\bis\\s+not\\s+None\\b.*\\b${esc}\\b` + // Python is not None
|
|
44
|
+
`|\\b${esc}\\b.*\\bis\\s+not\\s+None\\b` +
|
|
45
|
+
`|if\\s*\\(\\s*${esc}\\s*[)!&|]` + // if (x), if (!x)
|
|
46
|
+
`|if\\s+${esc}\\s*:` + // Python: if x:
|
|
47
|
+
`|\\b${esc}\\b\\s*\\.\\s*isPresent\\(\\)` + // Optional.isPresent()
|
|
48
|
+
`|\\bOptional\\b`);
|
|
49
|
+
for (let l = fromLine; l < toLine; l++) {
|
|
50
|
+
const line = codeLines[l - 1] ?? '';
|
|
51
|
+
if (pattern.test(line))
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
export class NullDerefPass {
|
|
57
|
+
name = 'null-deref';
|
|
58
|
+
category = 'reliability';
|
|
59
|
+
run(ctx) {
|
|
60
|
+
const { graph, code, language } = ctx;
|
|
61
|
+
// Rust has the Option/Result type system — NPE is not applicable.
|
|
62
|
+
// Bash has no objects to dereference.
|
|
63
|
+
if (language === 'rust' || language === 'bash') {
|
|
64
|
+
return { potentialNullDerefs: [] };
|
|
65
|
+
}
|
|
66
|
+
const file = graph.ir.meta.file;
|
|
67
|
+
const codeLines = code.split('\n');
|
|
68
|
+
const potentialNullDerefs = [];
|
|
69
|
+
const reported = new Set(); // deduplicate by variable+useLine
|
|
70
|
+
for (const def of graph.ir.dfg.defs) {
|
|
71
|
+
if (!def.expression || !NULL_EXPR_RE.test(def.expression))
|
|
72
|
+
continue;
|
|
73
|
+
const varName = def.variable;
|
|
74
|
+
const defLine = def.line;
|
|
75
|
+
// Determine enclosing method bounds to limit search scope
|
|
76
|
+
const methodInfo = graph.methodAtLine(defLine);
|
|
77
|
+
const methodEnd = methodInfo?.method.end_line ?? Number.MAX_SAFE_INTEGER;
|
|
78
|
+
// Find all downstream uses of this null-assigned variable
|
|
79
|
+
const uses = graph.usesOfDef(def.id);
|
|
80
|
+
for (const use of uses) {
|
|
81
|
+
const useLine = use.line;
|
|
82
|
+
// Use must be AFTER the assignment and within the same method
|
|
83
|
+
if (useLine <= defLine || useLine > methodEnd)
|
|
84
|
+
continue;
|
|
85
|
+
// Check if the variable is used as a call receiver at this line
|
|
86
|
+
const callsAtLine = graph.callsAtLine(useLine);
|
|
87
|
+
const isCallReceiver = callsAtLine.some(c => c.receiver === varName);
|
|
88
|
+
// Also detect field-access pattern: `varName.field`
|
|
89
|
+
const lineText = codeLines[useLine - 1] ?? '';
|
|
90
|
+
const fieldAccessRe = new RegExp(`\\b${escRe(varName)}\\s*\\.`);
|
|
91
|
+
const isFieldAccess = fieldAccessRe.test(lineText);
|
|
92
|
+
if (!isCallReceiver && !isFieldAccess)
|
|
93
|
+
continue;
|
|
94
|
+
// Optional chaining (`?.`) is safe — skip
|
|
95
|
+
const optionalChainRe = new RegExp(`\\b${escRe(varName)}\\s*\\?\\s*\\.`);
|
|
96
|
+
if (optionalChainRe.test(lineText))
|
|
97
|
+
continue;
|
|
98
|
+
// Check whether a null guard exists between the assignment and this use
|
|
99
|
+
if (hasNullGuard(codeLines, varName, defLine + 1, useLine))
|
|
100
|
+
continue;
|
|
101
|
+
const key = `${varName}-${useLine}`;
|
|
102
|
+
if (reported.has(key))
|
|
103
|
+
continue;
|
|
104
|
+
reported.add(key);
|
|
105
|
+
potentialNullDerefs.push({ defLine, useLine, variable: varName });
|
|
106
|
+
ctx.addFinding({
|
|
107
|
+
id: `null-deref-${file}-${useLine}`,
|
|
108
|
+
pass: this.name,
|
|
109
|
+
category: this.category,
|
|
110
|
+
rule_id: this.name,
|
|
111
|
+
cwe: 'CWE-476',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
level: 'error',
|
|
114
|
+
message: `Potential null dereference: '${varName}' was assigned null at line ${defLine} ` +
|
|
115
|
+
`and is used at line ${useLine} without a null check`,
|
|
116
|
+
file,
|
|
117
|
+
line: useLine,
|
|
118
|
+
snippet: lineText.trim(),
|
|
119
|
+
fix: `Add a null check before dereferencing: \`if (${varName} != null) { ... }\``,
|
|
120
|
+
evidence: {
|
|
121
|
+
variable: varName,
|
|
122
|
+
assigned_null_at: defLine,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { potentialNullDerefs };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=null-deref-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"null-deref-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/null-deref-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #71: orphan-module (project-level)
|
|
3
|
+
*
|
|
4
|
+
* Flags modules that have no incoming import edges and are not recognized
|
|
5
|
+
* entry points. Orphan modules are likely dead code, leftover scaffolding,
|
|
6
|
+
* or files that were accidentally disconnected from the project.
|
|
7
|
+
*
|
|
8
|
+
* This is a project-level pass — it does NOT extend AnalysisPass.
|
|
9
|
+
* It is invoked from analyzeProject() after all per-file analyses are complete.
|
|
10
|
+
*
|
|
11
|
+
* Entry points (excluded from flagging): filename base matches
|
|
12
|
+
* /^(index|main|app|server|mod)$/i
|
|
13
|
+
*
|
|
14
|
+
* Category: architecture | Severity: low | Level: note | CWE: none
|
|
15
|
+
*/
|
|
16
|
+
import type { SastFinding } from '../../types/index.js';
|
|
17
|
+
import type { ProjectGraph } from '../../graph/project-graph.js';
|
|
18
|
+
import type { ImportGraph } from '../../graph/import-graph.js';
|
|
19
|
+
export declare class OrphanModulePass {
|
|
20
|
+
run(_projectGraph: ProjectGraph, importGraph: ImportGraph): SastFinding[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #71: orphan-module (project-level)
|
|
3
|
+
*
|
|
4
|
+
* Flags modules that have no incoming import edges and are not recognized
|
|
5
|
+
* entry points. Orphan modules are likely dead code, leftover scaffolding,
|
|
6
|
+
* or files that were accidentally disconnected from the project.
|
|
7
|
+
*
|
|
8
|
+
* This is a project-level pass — it does NOT extend AnalysisPass.
|
|
9
|
+
* It is invoked from analyzeProject() after all per-file analyses are complete.
|
|
10
|
+
*
|
|
11
|
+
* Entry points (excluded from flagging): filename base matches
|
|
12
|
+
* /^(index|main|app|server|mod)$/i
|
|
13
|
+
*
|
|
14
|
+
* Category: architecture | Severity: low | Level: note | CWE: none
|
|
15
|
+
*/
|
|
16
|
+
export class OrphanModulePass {
|
|
17
|
+
run(_projectGraph, importGraph) {
|
|
18
|
+
const findings = [];
|
|
19
|
+
const orphans = importGraph.findOrphans();
|
|
20
|
+
for (const file of orphans) {
|
|
21
|
+
const finding = {
|
|
22
|
+
id: `orphan-module-${file.replace(/[^a-z0-9]/gi, '-')}`,
|
|
23
|
+
pass: 'orphan-module',
|
|
24
|
+
category: 'architecture',
|
|
25
|
+
rule_id: 'orphan-module',
|
|
26
|
+
severity: 'low',
|
|
27
|
+
level: 'note',
|
|
28
|
+
message: `Module '${file}' has no incoming imports and is not a known entry point. It may be dead code.`,
|
|
29
|
+
file,
|
|
30
|
+
line: 1,
|
|
31
|
+
evidence: { file },
|
|
32
|
+
};
|
|
33
|
+
findings.push(finding);
|
|
34
|
+
}
|
|
35
|
+
return findings;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=orphan-module-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-module-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/orphan-module-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,OAAO,gBAAgB;IAC3B,GAAG,CAAC,aAA2B,EAAE,WAAwB;QACvD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QAE1C,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAgB;gBAC3B,EAAE,EAAQ,iBAAiB,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE;gBAC7D,IAAI,EAAM,eAAe;gBACzB,QAAQ,EAAE,cAAc;gBACxB,OAAO,EAAG,eAAe;gBACzB,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAK,MAAM;gBAChB,OAAO,EAAG,WAAW,IAAI,gFAAgF;gBACzG,IAAI;gBACJ,IAAI,EAAM,CAAC;gBACX,QAAQ,EAAE,EAAE,IAAI,EAAE;aACnB,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #21: resource-leak (CWE-772, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects I/O resources (streams, connections, sockets) that are opened but
|
|
5
|
+
* not closed on all exit paths. Unclosed resources exhaust file descriptors
|
|
6
|
+
* or connection pools and cause subtle failures under load.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Find resource-opening calls: known constructors (FileInputStream, etc.)
|
|
10
|
+
* or factory methods (open, createReadStream, etc.).
|
|
11
|
+
* 2. Get the variable bound to the resource from DFG defs at the open line.
|
|
12
|
+
* 3. Within the enclosing method, look for a close()/dispose() call whose
|
|
13
|
+
* receiver matches the resource variable.
|
|
14
|
+
* 4a. No close call found → definite leak (high, error).
|
|
15
|
+
* 4b. Close found but no `finally` keyword in the method after the open
|
|
16
|
+
* → potential leak (medium, warning): an exception skips the close.
|
|
17
|
+
*
|
|
18
|
+
* Note: Java try-with-resources generates no explicit close() in the source;
|
|
19
|
+
* the pass treats the absence of both an explicit close AND a finally as a
|
|
20
|
+
* definite leak.
|
|
21
|
+
*/
|
|
22
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
23
|
+
export interface ResourceLeakResult {
|
|
24
|
+
/** Resources that may not be properly closed. */
|
|
25
|
+
leaks: Array<{
|
|
26
|
+
line: number;
|
|
27
|
+
resource: string;
|
|
28
|
+
variable: string;
|
|
29
|
+
kind: 'definite' | 'potential';
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare class ResourceLeakPass implements AnalysisPass<ResourceLeakResult> {
|
|
33
|
+
readonly name = "resource-leak";
|
|
34
|
+
readonly category: "reliability";
|
|
35
|
+
run(ctx: PassContext): ResourceLeakResult;
|
|
36
|
+
/** True if a `finally` keyword appears in the method body after the open line. */
|
|
37
|
+
private hasFinallyBlock;
|
|
38
|
+
/**
|
|
39
|
+
* True if a try-with-resources or Python `with` statement wraps the resource,
|
|
40
|
+
* indicating implicit close. Detects `try (` or `with open(` patterns.
|
|
41
|
+
*/
|
|
42
|
+
private hasTryWithResources;
|
|
43
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #21: resource-leak (CWE-772, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects I/O resources (streams, connections, sockets) that are opened but
|
|
5
|
+
* not closed on all exit paths. Unclosed resources exhaust file descriptors
|
|
6
|
+
* or connection pools and cause subtle failures under load.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Find resource-opening calls: known constructors (FileInputStream, etc.)
|
|
10
|
+
* or factory methods (open, createReadStream, etc.).
|
|
11
|
+
* 2. Get the variable bound to the resource from DFG defs at the open line.
|
|
12
|
+
* 3. Within the enclosing method, look for a close()/dispose() call whose
|
|
13
|
+
* receiver matches the resource variable.
|
|
14
|
+
* 4a. No close call found → definite leak (high, error).
|
|
15
|
+
* 4b. Close found but no `finally` keyword in the method after the open
|
|
16
|
+
* → potential leak (medium, warning): an exception skips the close.
|
|
17
|
+
*
|
|
18
|
+
* Note: Java try-with-resources generates no explicit close() in the source;
|
|
19
|
+
* the pass treats the absence of both an explicit close AND a finally as a
|
|
20
|
+
* definite leak.
|
|
21
|
+
*/
|
|
22
|
+
/** Constructors that produce closeable resources. */
|
|
23
|
+
const RESOURCE_CTORS = new Set([
|
|
24
|
+
// Java IO
|
|
25
|
+
'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
|
|
26
|
+
'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
|
|
27
|
+
'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
|
|
28
|
+
'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
|
|
29
|
+
'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
|
|
30
|
+
// Java NIO
|
|
31
|
+
'FileChannel',
|
|
32
|
+
// Java Net
|
|
33
|
+
'Socket', 'ServerSocket', 'DatagramSocket',
|
|
34
|
+
]);
|
|
35
|
+
/** Factory / open methods that return closeable resources. */
|
|
36
|
+
const RESOURCE_FACTORY_METHODS = new Set([
|
|
37
|
+
// Java NIO/IO
|
|
38
|
+
'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
|
|
39
|
+
'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
|
|
40
|
+
// Python built-in
|
|
41
|
+
'open',
|
|
42
|
+
// Node.js streams
|
|
43
|
+
'createReadStream', 'createWriteStream', 'createConnection',
|
|
44
|
+
]);
|
|
45
|
+
/** Methods that properly release a resource. */
|
|
46
|
+
const CLOSE_METHODS = new Set([
|
|
47
|
+
'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
|
|
48
|
+
'shutdownNow', 'terminate',
|
|
49
|
+
]);
|
|
50
|
+
export class ResourceLeakPass {
|
|
51
|
+
name = 'resource-leak';
|
|
52
|
+
category = 'reliability';
|
|
53
|
+
run(ctx) {
|
|
54
|
+
const { graph, code } = ctx;
|
|
55
|
+
const file = graph.ir.meta.file;
|
|
56
|
+
const codeLines = code.split('\n');
|
|
57
|
+
const leaks = [];
|
|
58
|
+
for (const call of graph.ir.calls) {
|
|
59
|
+
const name = call.method_name;
|
|
60
|
+
const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
|
|
61
|
+
const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
|
|
62
|
+
if (!isConstructor && !isFactory)
|
|
63
|
+
continue;
|
|
64
|
+
const openLine = call.location.line;
|
|
65
|
+
// Resource must be captured in a variable to be trackable
|
|
66
|
+
const defs = graph.defsAtLine(openLine);
|
|
67
|
+
if (defs.length === 0)
|
|
68
|
+
continue;
|
|
69
|
+
const resourceVar = defs[0].variable;
|
|
70
|
+
// Limit search to the enclosing method
|
|
71
|
+
const methodInfo = graph.methodAtLine(openLine);
|
|
72
|
+
if (!methodInfo)
|
|
73
|
+
continue;
|
|
74
|
+
const methodEnd = methodInfo.method.end_line;
|
|
75
|
+
// Look for a close() call on this resource within the method
|
|
76
|
+
const closeCall = graph.ir.calls.find(c => CLOSE_METHODS.has(c.method_name) &&
|
|
77
|
+
c.receiver === resourceVar &&
|
|
78
|
+
c.location.line > openLine &&
|
|
79
|
+
c.location.line <= methodEnd);
|
|
80
|
+
const snippet = (codeLines[openLine - 1] ?? '').trim();
|
|
81
|
+
if (!closeCall) {
|
|
82
|
+
// Also accept try-with-resources or with-statement as implicit close
|
|
83
|
+
if (this.hasTryWithResources(codeLines, openLine, methodEnd))
|
|
84
|
+
continue;
|
|
85
|
+
// Definite leak: resource is never explicitly released
|
|
86
|
+
leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'definite' });
|
|
87
|
+
ctx.addFinding({
|
|
88
|
+
id: `resource-leak-${file}-${openLine}`,
|
|
89
|
+
pass: this.name,
|
|
90
|
+
category: this.category,
|
|
91
|
+
rule_id: this.name,
|
|
92
|
+
cwe: 'CWE-772',
|
|
93
|
+
severity: 'high',
|
|
94
|
+
level: 'error',
|
|
95
|
+
message: `Resource leak: \`${name}\` assigned to '${resourceVar}' at line ${openLine} ` +
|
|
96
|
+
`is never closed — file descriptors or connections may be exhausted`,
|
|
97
|
+
file,
|
|
98
|
+
line: openLine,
|
|
99
|
+
snippet,
|
|
100
|
+
fix: `Use try-with-resources (Java 7+): \`try (${name} ${resourceVar} = ...) { ... }\`, ` +
|
|
101
|
+
`or call \`${resourceVar}.close()\` in a finally block`,
|
|
102
|
+
evidence: { resource: name, variable: resourceVar },
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Close found — check if it is protected by a finally block
|
|
107
|
+
if (this.hasFinallyBlock(codeLines, openLine, methodEnd))
|
|
108
|
+
continue;
|
|
109
|
+
// Potential leak: close() exists but may be skipped on exception
|
|
110
|
+
leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'potential' });
|
|
111
|
+
ctx.addFinding({
|
|
112
|
+
id: `resource-leak-${file}-${openLine}`,
|
|
113
|
+
pass: this.name,
|
|
114
|
+
category: this.category,
|
|
115
|
+
rule_id: this.name,
|
|
116
|
+
cwe: 'CWE-772',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
level: 'warning',
|
|
119
|
+
message: `Potential resource leak: \`${name}\` ('${resourceVar}') is closed at ` +
|
|
120
|
+
`line ${closeCall.location.line} but not inside a finally block — ` +
|
|
121
|
+
`an exception could skip the close`,
|
|
122
|
+
file,
|
|
123
|
+
line: openLine,
|
|
124
|
+
snippet,
|
|
125
|
+
fix: `Move \`${resourceVar}.close()\` into a finally block, or use try-with-resources`,
|
|
126
|
+
evidence: {
|
|
127
|
+
resource: name,
|
|
128
|
+
variable: resourceVar,
|
|
129
|
+
close_line: closeCall.location.line,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { leaks };
|
|
134
|
+
}
|
|
135
|
+
/** True if a `finally` keyword appears in the method body after the open line. */
|
|
136
|
+
hasFinallyBlock(lines, fromLine, toLine) {
|
|
137
|
+
for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
|
|
138
|
+
if (/\bfinally\b/.test(lines[l - 1] ?? ''))
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* True if a try-with-resources or Python `with` statement wraps the resource,
|
|
145
|
+
* indicating implicit close. Detects `try (` or `with open(` patterns.
|
|
146
|
+
*/
|
|
147
|
+
hasTryWithResources(lines, fromLine, toLine) {
|
|
148
|
+
for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
|
|
149
|
+
const text = lines[l - 1] ?? '';
|
|
150
|
+
if (/\btry\s*\(/.test(text) || /\bwith\b.*\bopen\b/.test(text))
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=resource-leak-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-leak-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/resource-leak-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SinkFilterPass
|
|
3
|
+
*
|
|
4
|
+
* Applies the four-stage sink filtering pipeline to eliminate false positives,
|
|
5
|
+
* followed by language-specific XPath/XSS suppression.
|
|
6
|
+
*
|
|
7
|
+
* Filter stages (applied in order):
|
|
8
|
+
* 1. Dead code — remove sinks on unreachable lines
|
|
9
|
+
* 2. Clean array elements — strong updates via constant propagation
|
|
10
|
+
* 3. Clean variables — arguments proven non-tainted by constant propagation
|
|
11
|
+
* 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
|
|
12
|
+
* 5. Python XPath FP reduction
|
|
13
|
+
* 6. JavaScript XSS FP reduction
|
|
14
|
+
*
|
|
15
|
+
* Depends on: taint-matcher, constant-propagation, language-sources
|
|
16
|
+
*/
|
|
17
|
+
import type { TaintSource, TaintSink, TaintSanitizer } from '../../types/index.js';
|
|
18
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
19
|
+
export interface SinkFilterResult {
|
|
20
|
+
/** Merged sources: taint-matcher + language-sources. */
|
|
21
|
+
sources: TaintSource[];
|
|
22
|
+
/** Filtered sinks. */
|
|
23
|
+
sinks: TaintSink[];
|
|
24
|
+
sanitizers: TaintSanitizer[];
|
|
25
|
+
}
|
|
26
|
+
export declare class SinkFilterPass implements AnalysisPass<SinkFilterResult> {
|
|
27
|
+
readonly name = "sink-filter";
|
|
28
|
+
readonly category: "security";
|
|
29
|
+
run(ctx: PassContext): SinkFilterResult;
|
|
30
|
+
}
|
|
31
|
+
import type { CircleIR } from '../../types/index.js';
|
|
32
|
+
type Symbols = Map<string, {
|
|
33
|
+
value: string | number | boolean | null;
|
|
34
|
+
type: string;
|
|
35
|
+
sourceLine: number;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function filterCleanVariableSinks(sinks: CircleIR['taint']['sinks'], calls: CircleIR['calls'], taintedVars: Set<string>, symbols: Symbols, dfg?: CircleIR['dfg'], sanitizedVars?: Set<string>, synchronizedLines?: Set<number>): CircleIR['taint']['sinks'];
|
|
38
|
+
export declare function filterSanitizedSinks(sinks: CircleIR['taint']['sinks'], sanitizers: CircleIR['taint']['sanitizers'], calls: CircleIR['calls']): CircleIR['taint']['sinks'];
|
|
39
|
+
export {};
|