circle-ir 3.8.4 → 3.9.8
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/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/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/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/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/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/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/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/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.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/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/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 +48 -1
- package/dist/analyzer.js +252 -1476
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3952 -1270
- package/dist/core/circle-ir-core.cjs +360 -106
- package/dist/core/circle-ir-core.js +360 -106
- 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/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/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 +5 -0
- package/dist/graph/index.js +6 -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 +3 -2
- package/dist/index.js +3 -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 +1 -1
|
@@ -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,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #33: react-inline-jsx (category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects inline object literals or arrow functions passed as JSX props.
|
|
5
|
+
* These create a new reference on every render, defeating React.memo /
|
|
6
|
+
* shouldComponentUpdate optimisations and causing unnecessary re-renders.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Only runs on JavaScript/TypeScript files that appear to contain JSX
|
|
10
|
+
* (quick check: source contains a `<UpperCase` JSX component pattern).
|
|
11
|
+
* 2. Scans each source line for:
|
|
12
|
+
* a. Inline object prop: propName={{ (double-brace)
|
|
13
|
+
* b. Inline arrow prop: propName={(...) => or propName={identifier =>
|
|
14
|
+
* c. Inline function prop: propName={function(
|
|
15
|
+
* 3. Skips:
|
|
16
|
+
* - `style={{` — idiomatic and near-impossible to hoist statically
|
|
17
|
+
* - `key=` — must be inline
|
|
18
|
+
* - `data-*` attribute names
|
|
19
|
+
* - Lines that are comments
|
|
20
|
+
* 4. Emits one finding per matched line.
|
|
21
|
+
*
|
|
22
|
+
* Languages: JavaScript and TypeScript only.
|
|
23
|
+
*/
|
|
24
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
25
|
+
export interface ReactInlineJsxResult {
|
|
26
|
+
inlineProps: Array<{
|
|
27
|
+
line: number;
|
|
28
|
+
propName: string;
|
|
29
|
+
kind: 'object' | 'arrow' | 'function';
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare class ReactInlineJsxPass implements AnalysisPass<ReactInlineJsxResult> {
|
|
33
|
+
readonly name = "react-inline-jsx";
|
|
34
|
+
readonly category: "performance";
|
|
35
|
+
run(ctx: PassContext): ReactInlineJsxResult;
|
|
36
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #33: react-inline-jsx (category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects inline object literals or arrow functions passed as JSX props.
|
|
5
|
+
* These create a new reference on every render, defeating React.memo /
|
|
6
|
+
* shouldComponentUpdate optimisations and causing unnecessary re-renders.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Only runs on JavaScript/TypeScript files that appear to contain JSX
|
|
10
|
+
* (quick check: source contains a `<UpperCase` JSX component pattern).
|
|
11
|
+
* 2. Scans each source line for:
|
|
12
|
+
* a. Inline object prop: propName={{ (double-brace)
|
|
13
|
+
* b. Inline arrow prop: propName={(...) => or propName={identifier =>
|
|
14
|
+
* c. Inline function prop: propName={function(
|
|
15
|
+
* 3. Skips:
|
|
16
|
+
* - `style={{` — idiomatic and near-impossible to hoist statically
|
|
17
|
+
* - `key=` — must be inline
|
|
18
|
+
* - `data-*` attribute names
|
|
19
|
+
* - Lines that are comments
|
|
20
|
+
* 4. Emits one finding per matched line.
|
|
21
|
+
*
|
|
22
|
+
* Languages: JavaScript and TypeScript only.
|
|
23
|
+
*/
|
|
24
|
+
/** Quick heuristic: does the file contain any JSX component usage? */
|
|
25
|
+
const JSX_COMPONENT_RE = /<[A-Z][A-Za-z0-9]*/;
|
|
26
|
+
/** Inline object prop: propName={{ (but NOT style={{ ) */
|
|
27
|
+
const INLINE_OBJECT_RE = /\s([A-Za-z][A-Za-z0-9_]*)=\{\{/g;
|
|
28
|
+
/** Inline arrow function prop: propName={(...) => or propName={x => */
|
|
29
|
+
const INLINE_ARROW_RE = /\s([A-Za-z][A-Za-z0-9_]*)=\{(?:\(|[A-Za-z_$]).*?=>/g;
|
|
30
|
+
/** Inline function expression prop: propName={function( */
|
|
31
|
+
const INLINE_FUNCTION_RE = /\s([A-Za-z][A-Za-z0-9_]*)=\{function\s*\(/g;
|
|
32
|
+
/** Props to always skip regardless of value shape. */
|
|
33
|
+
const SKIP_PROPS = new Set(['style', 'key', 'ref', 'className', 'id']);
|
|
34
|
+
export class ReactInlineJsxPass {
|
|
35
|
+
name = 'react-inline-jsx';
|
|
36
|
+
category = 'performance';
|
|
37
|
+
run(ctx) {
|
|
38
|
+
const { graph, code, language } = ctx;
|
|
39
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
40
|
+
return { inlineProps: [] };
|
|
41
|
+
}
|
|
42
|
+
// Quick file-level JSX check
|
|
43
|
+
if (!JSX_COMPONENT_RE.test(code)) {
|
|
44
|
+
return { inlineProps: [] };
|
|
45
|
+
}
|
|
46
|
+
const file = graph.ir.meta.file;
|
|
47
|
+
const codeLines = code.split('\n');
|
|
48
|
+
const inlineProps = [];
|
|
49
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
50
|
+
const lineText = codeLines[i];
|
|
51
|
+
const ln = i + 1;
|
|
52
|
+
// Skip comment lines
|
|
53
|
+
const trimmed = lineText.trimStart();
|
|
54
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// --- Inline object prop: propName={{ ---
|
|
58
|
+
INLINE_OBJECT_RE.lastIndex = 0;
|
|
59
|
+
let m;
|
|
60
|
+
while ((m = INLINE_OBJECT_RE.exec(lineText)) !== null) {
|
|
61
|
+
const propName = m[1];
|
|
62
|
+
if (SKIP_PROPS.has(propName))
|
|
63
|
+
continue;
|
|
64
|
+
if (propName.startsWith('data-'))
|
|
65
|
+
continue; // data-* attributes
|
|
66
|
+
inlineProps.push({ line: ln, propName, kind: 'object' });
|
|
67
|
+
ctx.addFinding({
|
|
68
|
+
id: `react-inline-jsx-obj-${file}-${ln}`,
|
|
69
|
+
pass: this.name,
|
|
70
|
+
category: this.category,
|
|
71
|
+
rule_id: this.name,
|
|
72
|
+
cwe: undefined,
|
|
73
|
+
severity: 'low',
|
|
74
|
+
level: 'note',
|
|
75
|
+
message: `Inline object in JSX prop \`${propName}\` creates a new reference on every render, ` +
|
|
76
|
+
`defeating memoization`,
|
|
77
|
+
file,
|
|
78
|
+
line: ln,
|
|
79
|
+
snippet: lineText.trim(),
|
|
80
|
+
fix: `Extract the object literal into a \`useMemo\` hook or a module-level constant, ` +
|
|
81
|
+
`then pass the reference: \`${propName}={myConstObject}\`.`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// --- Inline arrow function prop: propName={(...) => or propName={x => ---
|
|
85
|
+
INLINE_ARROW_RE.lastIndex = 0;
|
|
86
|
+
while ((m = INLINE_ARROW_RE.exec(lineText)) !== null) {
|
|
87
|
+
const propName = m[1];
|
|
88
|
+
if (SKIP_PROPS.has(propName))
|
|
89
|
+
continue;
|
|
90
|
+
if (propName.startsWith('data'))
|
|
91
|
+
continue;
|
|
92
|
+
inlineProps.push({ line: ln, propName, kind: 'arrow' });
|
|
93
|
+
ctx.addFinding({
|
|
94
|
+
id: `react-inline-jsx-arrow-${file}-${ln}`,
|
|
95
|
+
pass: this.name,
|
|
96
|
+
category: this.category,
|
|
97
|
+
rule_id: this.name,
|
|
98
|
+
cwe: undefined,
|
|
99
|
+
severity: 'low',
|
|
100
|
+
level: 'note',
|
|
101
|
+
message: `Inline arrow function in JSX prop \`${propName}\` creates a new function reference on every render, ` +
|
|
102
|
+
`defeating memoization`,
|
|
103
|
+
file,
|
|
104
|
+
line: ln,
|
|
105
|
+
snippet: lineText.trim(),
|
|
106
|
+
fix: `Wrap the handler with \`useCallback\` or define it outside the component: ` +
|
|
107
|
+
`\`const handle${propName.charAt(0).toUpperCase()}${propName.slice(1)} = useCallback(...)\`.`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// --- Inline function expression prop: propName={function( ---
|
|
111
|
+
INLINE_FUNCTION_RE.lastIndex = 0;
|
|
112
|
+
while ((m = INLINE_FUNCTION_RE.exec(lineText)) !== null) {
|
|
113
|
+
const propName = m[1];
|
|
114
|
+
if (SKIP_PROPS.has(propName))
|
|
115
|
+
continue;
|
|
116
|
+
if (propName.startsWith('data'))
|
|
117
|
+
continue;
|
|
118
|
+
inlineProps.push({ line: ln, propName, kind: 'function' });
|
|
119
|
+
ctx.addFinding({
|
|
120
|
+
id: `react-inline-jsx-fn-${file}-${ln}`,
|
|
121
|
+
pass: this.name,
|
|
122
|
+
category: this.category,
|
|
123
|
+
rule_id: this.name,
|
|
124
|
+
cwe: undefined,
|
|
125
|
+
severity: 'low',
|
|
126
|
+
level: 'note',
|
|
127
|
+
message: `Inline function expression in JSX prop \`${propName}\` creates a new function reference on every render, ` +
|
|
128
|
+
`defeating memoization`,
|
|
129
|
+
file,
|
|
130
|
+
line: ln,
|
|
131
|
+
snippet: lineText.trim(),
|
|
132
|
+
fix: `Wrap the handler with \`useCallback\` or define it outside the component: ` +
|
|
133
|
+
`\`const handle${propName.charAt(0).toUpperCase()}${propName.slice(1)} = useCallback(...)\`.`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { inlineProps };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=react-inline-jsx-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-inline-jsx-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/react-inline-jsx-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #30: redundant-loop-computation (CWE-1050, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects loop-invariant expressions that are recomputed on every iteration.
|
|
5
|
+
* The most common and highest-signal patterns:
|
|
6
|
+
* - `.length` / `.size()` / `.count()` on a variable not modified in the loop
|
|
7
|
+
* - `Object.keys(x)` / `Object.values(x)` / `Object.entries(x)` on invariant `x`
|
|
8
|
+
* - Pure math: `Math.sqrt(x)`, `Math.pow(x, n)`, `Math.abs(x)` on invariant args
|
|
9
|
+
*
|
|
10
|
+
* Detection strategy:
|
|
11
|
+
* 1. Identify loop bodies via `graph.loopBodies()` (CFG back-edge derived).
|
|
12
|
+
* 2. Build `modifiedVars`: DFG defs whose line falls inside the loop range.
|
|
13
|
+
* 3. Scan source lines for the invariant patterns.
|
|
14
|
+
* 4. If the receiver/argument variable is NOT in `modifiedVars`, emit a finding.
|
|
15
|
+
*
|
|
16
|
+
* Languages: JavaScript/TypeScript, Java, Python, Rust. Bash — skipped.
|
|
17
|
+
*/
|
|
18
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
19
|
+
export interface RedundantLoopResult {
|
|
20
|
+
invariants: Array<{
|
|
21
|
+
line: number;
|
|
22
|
+
expression: string;
|
|
23
|
+
variable: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare class RedundantLoopPass implements AnalysisPass<RedundantLoopResult> {
|
|
27
|
+
readonly name = "redundant-loop-computation";
|
|
28
|
+
readonly category: "performance";
|
|
29
|
+
run(ctx: PassContext): RedundantLoopResult;
|
|
30
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #30: redundant-loop-computation (CWE-1050, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects loop-invariant expressions that are recomputed on every iteration.
|
|
5
|
+
* The most common and highest-signal patterns:
|
|
6
|
+
* - `.length` / `.size()` / `.count()` on a variable not modified in the loop
|
|
7
|
+
* - `Object.keys(x)` / `Object.values(x)` / `Object.entries(x)` on invariant `x`
|
|
8
|
+
* - Pure math: `Math.sqrt(x)`, `Math.pow(x, n)`, `Math.abs(x)` on invariant args
|
|
9
|
+
*
|
|
10
|
+
* Detection strategy:
|
|
11
|
+
* 1. Identify loop bodies via `graph.loopBodies()` (CFG back-edge derived).
|
|
12
|
+
* 2. Build `modifiedVars`: DFG defs whose line falls inside the loop range.
|
|
13
|
+
* 3. Scan source lines for the invariant patterns.
|
|
14
|
+
* 4. If the receiver/argument variable is NOT in `modifiedVars`, emit a finding.
|
|
15
|
+
*
|
|
16
|
+
* Languages: JavaScript/TypeScript, Java, Python, Rust. Bash — skipped.
|
|
17
|
+
*/
|
|
18
|
+
// Match: varName.length or varName.size() or varName.count()
|
|
19
|
+
const LENGTH_PATTERN = /\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\.\s*(?:length|size\(\)|count\(\))/g;
|
|
20
|
+
// Match: Object.keys(varName) Object.values(varName) Object.entries(varName)
|
|
21
|
+
const OBJECT_STATIC_PATTERN = /\bObject\s*\.\s*(?:keys|values|entries)\s*\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*\)/g;
|
|
22
|
+
// Match: Math.sqrt(varName) Math.pow(varName Math.abs(varName) Math.floor(varName) Math.ceil(varName)
|
|
23
|
+
const MATH_PATTERN = /\bMath\s*\.\s*(?:sqrt|pow|abs|floor|ceil|round|log|log2|log10)\s*\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*[,)]/g;
|
|
24
|
+
export class RedundantLoopPass {
|
|
25
|
+
name = 'redundant-loop-computation';
|
|
26
|
+
category = 'performance';
|
|
27
|
+
run(ctx) {
|
|
28
|
+
const { graph, code, language } = ctx;
|
|
29
|
+
if (language === 'bash') {
|
|
30
|
+
return { invariants: [] };
|
|
31
|
+
}
|
|
32
|
+
const file = graph.ir.meta.file;
|
|
33
|
+
const codeLines = code.split('\n');
|
|
34
|
+
const loops = graph.loopBodies();
|
|
35
|
+
if (loops.length === 0)
|
|
36
|
+
return { invariants: [] };
|
|
37
|
+
const invariants = [];
|
|
38
|
+
const reported = new Set(); // deduplicate by line+expression
|
|
39
|
+
for (const loop of loops) {
|
|
40
|
+
const { start_line, end_line } = loop;
|
|
41
|
+
// Collect variables modified (written) inside the loop body
|
|
42
|
+
const modifiedVars = new Set();
|
|
43
|
+
for (const def of graph.ir.dfg.defs) {
|
|
44
|
+
if (def.line >= start_line && def.line <= end_line) {
|
|
45
|
+
modifiedVars.add(def.variable);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Scan each line in the loop body for invariant patterns
|
|
49
|
+
for (let ln = start_line; ln <= end_line && ln <= codeLines.length; ln++) {
|
|
50
|
+
const lineText = codeLines[ln - 1] ?? '';
|
|
51
|
+
// Skip blank lines
|
|
52
|
+
if (lineText.trim() === '')
|
|
53
|
+
continue;
|
|
54
|
+
// --- .length / .size() / .count() ---
|
|
55
|
+
LENGTH_PATTERN.lastIndex = 0;
|
|
56
|
+
let m;
|
|
57
|
+
while ((m = LENGTH_PATTERN.exec(lineText)) !== null) {
|
|
58
|
+
const varName = m[1];
|
|
59
|
+
if (modifiedVars.has(varName))
|
|
60
|
+
continue;
|
|
61
|
+
// Skip if used in a for-loop initialisation line (e.g., for (let i = 0; i < arr.length; i++))
|
|
62
|
+
// — the loop header itself scanning is expected; flag it only inside the body
|
|
63
|
+
const expr = m[0];
|
|
64
|
+
const key = `${ln}-${expr}`;
|
|
65
|
+
if (reported.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
reported.add(key);
|
|
68
|
+
invariants.push({ line: ln, expression: expr, variable: varName });
|
|
69
|
+
ctx.addFinding({
|
|
70
|
+
id: `redundant-loop-computation-${file}-${ln}`,
|
|
71
|
+
pass: this.name,
|
|
72
|
+
category: this.category,
|
|
73
|
+
rule_id: this.name,
|
|
74
|
+
cwe: 'CWE-1050',
|
|
75
|
+
severity: 'low',
|
|
76
|
+
level: 'note',
|
|
77
|
+
message: `Loop-invariant computation: \`${expr}\` is recomputed on every iteration; hoist outside loop`,
|
|
78
|
+
file,
|
|
79
|
+
line: ln,
|
|
80
|
+
snippet: lineText.trim(),
|
|
81
|
+
fix: `Compute \`${expr}\` once before the loop and use the cached value inside.`,
|
|
82
|
+
evidence: { variable: varName, loop_start: start_line, loop_end: end_line },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// --- Object.keys/values/entries(x) ---
|
|
86
|
+
OBJECT_STATIC_PATTERN.lastIndex = 0;
|
|
87
|
+
while ((m = OBJECT_STATIC_PATTERN.exec(lineText)) !== null) {
|
|
88
|
+
const varName = m[1];
|
|
89
|
+
if (modifiedVars.has(varName))
|
|
90
|
+
continue;
|
|
91
|
+
const expr = m[0];
|
|
92
|
+
const key = `${ln}-${expr}`;
|
|
93
|
+
if (reported.has(key))
|
|
94
|
+
continue;
|
|
95
|
+
reported.add(key);
|
|
96
|
+
invariants.push({ line: ln, expression: expr, variable: varName });
|
|
97
|
+
ctx.addFinding({
|
|
98
|
+
id: `redundant-loop-computation-${file}-${ln}-obj`,
|
|
99
|
+
pass: this.name,
|
|
100
|
+
category: this.category,
|
|
101
|
+
rule_id: this.name,
|
|
102
|
+
cwe: 'CWE-1050',
|
|
103
|
+
severity: 'low',
|
|
104
|
+
level: 'note',
|
|
105
|
+
message: `Loop-invariant computation: \`${expr}\` allocates a new array on every iteration; hoist outside loop`,
|
|
106
|
+
file,
|
|
107
|
+
line: ln,
|
|
108
|
+
snippet: lineText.trim(),
|
|
109
|
+
fix: `Compute \`${expr}\` once before the loop.`,
|
|
110
|
+
evidence: { variable: varName, loop_start: start_line, loop_end: end_line },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// --- Math.*(x) ---
|
|
114
|
+
MATH_PATTERN.lastIndex = 0;
|
|
115
|
+
while ((m = MATH_PATTERN.exec(lineText)) !== null) {
|
|
116
|
+
const varName = m[1];
|
|
117
|
+
if (modifiedVars.has(varName))
|
|
118
|
+
continue;
|
|
119
|
+
const expr = m[0].replace(/[,)]?\s*$/, ')');
|
|
120
|
+
const key = `${ln}-${expr}`;
|
|
121
|
+
if (reported.has(key))
|
|
122
|
+
continue;
|
|
123
|
+
reported.add(key);
|
|
124
|
+
invariants.push({ line: ln, expression: expr, variable: varName });
|
|
125
|
+
ctx.addFinding({
|
|
126
|
+
id: `redundant-loop-computation-${file}-${ln}-math`,
|
|
127
|
+
pass: this.name,
|
|
128
|
+
category: this.category,
|
|
129
|
+
rule_id: this.name,
|
|
130
|
+
cwe: 'CWE-1050',
|
|
131
|
+
severity: 'low',
|
|
132
|
+
level: 'note',
|
|
133
|
+
message: `Loop-invariant computation: \`${expr}\` is recomputed on every iteration; hoist outside loop`,
|
|
134
|
+
file,
|
|
135
|
+
line: ln,
|
|
136
|
+
snippet: lineText.trim(),
|
|
137
|
+
fix: `Compute \`${expr}\` once before the loop.`,
|
|
138
|
+
evidence: { variable: varName, loop_start: start_line, loop_end: end_line },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { invariants };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=redundant-loop-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redundant-loop-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/redundant-loop-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
+
}
|