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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #81: leaked-global (CWE-1109, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects assignments to undeclared variables inside function bodies in
|
|
5
|
+
* JavaScript/TypeScript. In non-strict mode JS (and absent `"use strict"`)
|
|
6
|
+
* writing to a variable that has no `let`/`const`/`var` declaration anywhere
|
|
7
|
+
* in the enclosing function silently creates (or mutates) a property on the
|
|
8
|
+
* global object — a classic source of hard-to-trace bugs.
|
|
9
|
+
*
|
|
10
|
+
* Detection strategy:
|
|
11
|
+
* 1. Language filter: JS/TS only.
|
|
12
|
+
* 2. Build a ScopeGraph for declaration-keyword awareness.
|
|
13
|
+
* 3. For each `kind='local'` def whose source line has NO declaration keyword:
|
|
14
|
+
* - Skip intentional throwaway names (_, err, e, …) and loop vars.
|
|
15
|
+
* - Skip if the variable IS declared (hasDeclKeyword=true) somewhere
|
|
16
|
+
* else in the same enclosing function → it is a legitimate reassignment.
|
|
17
|
+
* - Skip top-level assignments (methodStart === -1) — module-level bare
|
|
18
|
+
* assignments are an ES module pattern.
|
|
19
|
+
* - Flag the rest as potential global leaks.
|
|
20
|
+
*/
|
|
21
|
+
import { ScopeGraph } from '../../graph/scope-graph.js';
|
|
22
|
+
/** Variable names that are intentionally never declared with a keyword. */
|
|
23
|
+
const SKIP_NAMES = new Set([
|
|
24
|
+
'_', 'e', 'err', 'error', 'event', 'ex', 'exception',
|
|
25
|
+
'i', 'j', 'k', 'n', 'idx', 'index',
|
|
26
|
+
]);
|
|
27
|
+
export class LeakedGlobalPass {
|
|
28
|
+
name = 'leaked-global';
|
|
29
|
+
category = 'reliability';
|
|
30
|
+
run(ctx) {
|
|
31
|
+
const { graph, code, language } = ctx;
|
|
32
|
+
// JS/TS only — Python, Java, Rust all require explicit declarations
|
|
33
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
34
|
+
return { leaks: [] };
|
|
35
|
+
}
|
|
36
|
+
const file = graph.ir.meta.file;
|
|
37
|
+
// Skip test files — test helpers often use bare assignments intentionally
|
|
38
|
+
if (/[./](?:test|spec)[./]/.test(file) || /\.(?:test|spec)\.[jt]s$/.test(file)) {
|
|
39
|
+
return { leaks: [] };
|
|
40
|
+
}
|
|
41
|
+
const scope = new ScopeGraph(graph, code, language);
|
|
42
|
+
const codeLines = code.split('\n');
|
|
43
|
+
const leaks = [];
|
|
44
|
+
const reported = new Set(); // deduplicate by line
|
|
45
|
+
for (const entry of scope.entries) {
|
|
46
|
+
const { def, hasDeclKeyword, methodStart } = entry;
|
|
47
|
+
// Only look at bare assignments (no let/const/var on this line)
|
|
48
|
+
if (hasDeclKeyword)
|
|
49
|
+
continue;
|
|
50
|
+
if (def.kind !== 'local')
|
|
51
|
+
continue;
|
|
52
|
+
const variable = def.variable;
|
|
53
|
+
// Intentionally-unnamed / loop variables → skip
|
|
54
|
+
if (variable.startsWith('_'))
|
|
55
|
+
continue;
|
|
56
|
+
if (SKIP_NAMES.has(variable))
|
|
57
|
+
continue;
|
|
58
|
+
// Top-level (module scope) assignments → not a leaked global
|
|
59
|
+
if (methodStart === -1)
|
|
60
|
+
continue;
|
|
61
|
+
// If the same variable has a declared def (let/const/var) anywhere in
|
|
62
|
+
// this function body, this line is a legitimate reassignment — not a leak.
|
|
63
|
+
if (scope.hasDeclaredDef(variable, methodStart))
|
|
64
|
+
continue;
|
|
65
|
+
// Text-search fallback: `let x;` (no initializer) creates no DFG def, so
|
|
66
|
+
// hasDeclaredDef misses it. Scan source lines within the method for any
|
|
67
|
+
// `let/const/var ... varName` declaration.
|
|
68
|
+
const methodInfo = graph.methodAtLine(def.line);
|
|
69
|
+
if (methodInfo) {
|
|
70
|
+
const { start_line, end_line } = methodInfo.method;
|
|
71
|
+
const escapedVar = variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
+
// Match: let/const/var followed (possibly after { or [ for destructuring) by the name
|
|
73
|
+
const declPattern = new RegExp(`\\b(?:let|const|var)\\b[^=;\\n]*\\b${escapedVar}\\b`);
|
|
74
|
+
const hasTextDecl = codeLines.slice(start_line - 1, end_line).some(l => declPattern.test(l));
|
|
75
|
+
if (hasTextDecl)
|
|
76
|
+
continue;
|
|
77
|
+
// Also check module-level lines (before the method) for `let varName`
|
|
78
|
+
const moduleDecl = new RegExp(`^(?:export\\s+)?(?:let|const|var)\\b[^=;\\n]*\\b${escapedVar}\\b`);
|
|
79
|
+
const hasModuleDecl = codeLines.slice(0, start_line - 1).some(l => moduleDecl.test(l));
|
|
80
|
+
if (hasModuleDecl)
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (reported.has(def.line))
|
|
84
|
+
continue;
|
|
85
|
+
reported.add(def.line);
|
|
86
|
+
const enclosingFunction = methodInfo?.method.name ?? null;
|
|
87
|
+
leaks.push({ line: def.line, variable, enclosingFunction });
|
|
88
|
+
const fnDesc = enclosingFunction ? ` inside '${enclosingFunction}'` : '';
|
|
89
|
+
ctx.addFinding({
|
|
90
|
+
id: `leaked-global-${file}-${def.line}`,
|
|
91
|
+
pass: this.name,
|
|
92
|
+
category: this.category,
|
|
93
|
+
rule_id: this.name,
|
|
94
|
+
cwe: 'CWE-1109',
|
|
95
|
+
severity: 'medium',
|
|
96
|
+
level: 'warning',
|
|
97
|
+
message: `'${variable}' is assigned without a declaration keyword${fnDesc} — ` +
|
|
98
|
+
`creates an accidental global in non-strict mode`,
|
|
99
|
+
file,
|
|
100
|
+
line: def.line,
|
|
101
|
+
fix: `Add \`let\`, \`const\`, or \`var\` before the first assignment to '${variable}'`,
|
|
102
|
+
evidence: { variable, enclosing_function: enclosingFunction },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return { leaks };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=leaked-global-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaked-global-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/leaked-global-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #24: missing-await (CWE-252, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects calls to well-known async APIs in JavaScript/TypeScript where the
|
|
5
|
+
* returned Promise is silently discarded — the call result is neither awaited
|
|
6
|
+
* nor captured in a variable. This is the "fire-and-forget" pattern that hides
|
|
7
|
+
* errors and makes execution non-deterministic.
|
|
8
|
+
*
|
|
9
|
+
* Detection criteria (ALL must hold):
|
|
10
|
+
* 1. Language is javascript or typescript.
|
|
11
|
+
* 2. The method name is in the curated ASYNC_METHODS set.
|
|
12
|
+
* 3. The source line does NOT contain the `await` keyword.
|
|
13
|
+
* 4. There is no DFG definition at the call's line (result not assigned).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately narrow to keep false-positive rate near zero. Only methods that
|
|
16
|
+
* are essentially always async in practice are included. The `return async()`
|
|
17
|
+
* pattern is excluded (legitimate "pass-through Promise" idiom).
|
|
18
|
+
*/
|
|
19
|
+
import type { CallInfo } from '../../types/index.js';
|
|
20
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
21
|
+
export interface MissingAwaitPassResult {
|
|
22
|
+
/** Calls where a Promise return value is silently discarded. */
|
|
23
|
+
missingAwaitCalls: CallInfo[];
|
|
24
|
+
}
|
|
25
|
+
export declare class MissingAwaitPass implements AnalysisPass<MissingAwaitPassResult> {
|
|
26
|
+
readonly name = "missing-await";
|
|
27
|
+
readonly category: "reliability";
|
|
28
|
+
run(ctx: PassContext): MissingAwaitPassResult;
|
|
29
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #24: missing-await (CWE-252, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects calls to well-known async APIs in JavaScript/TypeScript where the
|
|
5
|
+
* returned Promise is silently discarded — the call result is neither awaited
|
|
6
|
+
* nor captured in a variable. This is the "fire-and-forget" pattern that hides
|
|
7
|
+
* errors and makes execution non-deterministic.
|
|
8
|
+
*
|
|
9
|
+
* Detection criteria (ALL must hold):
|
|
10
|
+
* 1. Language is javascript or typescript.
|
|
11
|
+
* 2. The method name is in the curated ASYNC_METHODS set.
|
|
12
|
+
* 3. The source line does NOT contain the `await` keyword.
|
|
13
|
+
* 4. There is no DFG definition at the call's line (result not assigned).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately narrow to keep false-positive rate near zero. Only methods that
|
|
16
|
+
* are essentially always async in practice are included. The `return async()`
|
|
17
|
+
* pattern is excluded (legitimate "pass-through Promise" idiom).
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Methods that are async in virtually every JS/TS codebase.
|
|
21
|
+
* Grouped by domain for maintainability.
|
|
22
|
+
*/
|
|
23
|
+
const ASYNC_METHODS = new Set([
|
|
24
|
+
// Node.js fs.promises / util.promisify (the async variants)
|
|
25
|
+
'readFile', 'writeFile', 'appendFile', 'unlink', 'rmdir', 'mkdir',
|
|
26
|
+
'readdir', 'stat', 'lstat', 'access', 'rename', 'copyFile',
|
|
27
|
+
// Network / HTTP
|
|
28
|
+
'fetch',
|
|
29
|
+
// Database — raw clients
|
|
30
|
+
'query', 'execute',
|
|
31
|
+
// Mongoose ODM
|
|
32
|
+
'findOne', 'findById', 'findByIdAndUpdate', 'findByIdAndDelete',
|
|
33
|
+
'findOneAndUpdate', 'findOneAndDelete', 'countDocuments', 'aggregate',
|
|
34
|
+
// Sequelize / TypeORM overlap
|
|
35
|
+
'findAll', 'findAndCountAll', 'bulkCreate',
|
|
36
|
+
// Prisma
|
|
37
|
+
'findFirst', 'findUnique', 'findMany',
|
|
38
|
+
// Generic lifecycle
|
|
39
|
+
'connect', 'disconnect',
|
|
40
|
+
]);
|
|
41
|
+
export class MissingAwaitPass {
|
|
42
|
+
name = 'missing-await';
|
|
43
|
+
category = 'reliability';
|
|
44
|
+
run(ctx) {
|
|
45
|
+
const { graph, code, language } = ctx;
|
|
46
|
+
// Only JS/TS: other languages either don't have async/await or surface
|
|
47
|
+
// missing-await through their type systems (Rust, Java).
|
|
48
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
49
|
+
return { missingAwaitCalls: [] };
|
|
50
|
+
}
|
|
51
|
+
const lines = code.split('\n');
|
|
52
|
+
const file = graph.ir.meta.file;
|
|
53
|
+
// Lines that have a DFG definition — result is captured in a variable.
|
|
54
|
+
const linesWithDefs = new Set(graph.ir.dfg.defs.map(d => d.line));
|
|
55
|
+
const missingAwaitCalls = [];
|
|
56
|
+
for (const call of graph.ir.calls) {
|
|
57
|
+
if (!ASYNC_METHODS.has(call.method_name))
|
|
58
|
+
continue;
|
|
59
|
+
const lineNum = call.location.line;
|
|
60
|
+
const lineText = lines[lineNum - 1] ?? '';
|
|
61
|
+
// Skip if `await` is present on this line (already awaited).
|
|
62
|
+
if (/\bawait\b/.test(lineText))
|
|
63
|
+
continue;
|
|
64
|
+
// Skip if result is captured (DFG def at this line).
|
|
65
|
+
if (linesWithDefs.has(lineNum))
|
|
66
|
+
continue;
|
|
67
|
+
// Skip `return asyncOp()` — intentional Promise pass-through.
|
|
68
|
+
if (/^\s*return\b/.test(lineText))
|
|
69
|
+
continue;
|
|
70
|
+
missingAwaitCalls.push(call);
|
|
71
|
+
ctx.addFinding({
|
|
72
|
+
id: `missing-await-${file}-${lineNum}`,
|
|
73
|
+
pass: this.name,
|
|
74
|
+
category: this.category,
|
|
75
|
+
rule_id: this.name,
|
|
76
|
+
cwe: 'CWE-252',
|
|
77
|
+
severity: 'medium',
|
|
78
|
+
level: 'warning',
|
|
79
|
+
message: `Missing await: \`${call.method_name}()\` returns a Promise that is neither ` +
|
|
80
|
+
`awaited nor captured — errors will be silently swallowed`,
|
|
81
|
+
file,
|
|
82
|
+
line: lineNum,
|
|
83
|
+
snippet: lineText.trim(),
|
|
84
|
+
fix: `Add \`await\` before \`${call.method_name}()\`, or assign the Promise and handle rejection`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return { missingAwaitCalls };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=missing-await-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-await-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/missing-await-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #35: missing-public-doc (category: maintainability)
|
|
3
|
+
*
|
|
4
|
+
* Detects public/exported methods and types that have no doc comment.
|
|
5
|
+
* A "doc comment" is a Javadoc-style `/** ... *\/` block, a TypeScript/JS
|
|
6
|
+
* JSDoc `/** ... *\/` block, a Rust `///` line comment, or a Python docstring
|
|
7
|
+
* (`"""..."""` as the first statement of the function body).
|
|
8
|
+
*
|
|
9
|
+
* What counts as "public":
|
|
10
|
+
* - Java / Kotlin: `modifiers` contains `"public"`.
|
|
11
|
+
* - JavaScript / TypeScript: `modifiers` does NOT contain `"private"` or
|
|
12
|
+
* `"protected"`. (JS has no access keywords; everything is implicitly public.)
|
|
13
|
+
* - Python: method name does not start with `_`.
|
|
14
|
+
* - Rust / Bash / other: skipped (doc conventions differ too much).
|
|
15
|
+
*
|
|
16
|
+
* Types (classes, interfaces) are always checked — they are extracted only when
|
|
17
|
+
* they are top-level declarations and therefore always "public" in the IR sense.
|
|
18
|
+
*
|
|
19
|
+
* Test files are excluded: if the file path contains test/spec patterns, the
|
|
20
|
+
* pass emits no findings (doc comments in test helpers are low value).
|
|
21
|
+
*/
|
|
22
|
+
import type { MethodInfo, TypeInfo } from '../../types/index.js';
|
|
23
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
24
|
+
export interface MissingPublicDocPassResult {
|
|
25
|
+
missingDocMethods: Array<{
|
|
26
|
+
type: TypeInfo;
|
|
27
|
+
method: MethodInfo;
|
|
28
|
+
}>;
|
|
29
|
+
missingDocTypes: TypeInfo[];
|
|
30
|
+
}
|
|
31
|
+
export declare class MissingPublicDocPass implements AnalysisPass<MissingPublicDocPassResult> {
|
|
32
|
+
readonly name = "missing-public-doc";
|
|
33
|
+
readonly category: "maintainability";
|
|
34
|
+
run(ctx: PassContext): MissingPublicDocPassResult;
|
|
35
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #35: missing-public-doc (category: maintainability)
|
|
3
|
+
*
|
|
4
|
+
* Detects public/exported methods and types that have no doc comment.
|
|
5
|
+
* A "doc comment" is a Javadoc-style `/** ... *\/` block, a TypeScript/JS
|
|
6
|
+
* JSDoc `/** ... *\/` block, a Rust `///` line comment, or a Python docstring
|
|
7
|
+
* (`"""..."""` as the first statement of the function body).
|
|
8
|
+
*
|
|
9
|
+
* What counts as "public":
|
|
10
|
+
* - Java / Kotlin: `modifiers` contains `"public"`.
|
|
11
|
+
* - JavaScript / TypeScript: `modifiers` does NOT contain `"private"` or
|
|
12
|
+
* `"protected"`. (JS has no access keywords; everything is implicitly public.)
|
|
13
|
+
* - Python: method name does not start with `_`.
|
|
14
|
+
* - Rust / Bash / other: skipped (doc conventions differ too much).
|
|
15
|
+
*
|
|
16
|
+
* Types (classes, interfaces) are always checked — they are extracted only when
|
|
17
|
+
* they are top-level declarations and therefore always "public" in the IR sense.
|
|
18
|
+
*
|
|
19
|
+
* Test files are excluded: if the file path contains test/spec patterns, the
|
|
20
|
+
* pass emits no findings (doc comments in test helpers are low value).
|
|
21
|
+
*/
|
|
22
|
+
/** Files matching these path patterns are treated as test/spec files. */
|
|
23
|
+
const TEST_PATH_RE = /[/._](test|tests|spec|specs|__tests?__|__mocks?__)[/._]/i;
|
|
24
|
+
/**
|
|
25
|
+
* Files in common utility/helper directories are implementation details, not
|
|
26
|
+
* public library API. Requiring JSDoc on every class in these directories
|
|
27
|
+
* produces noise when the project is a CLI tool or application.
|
|
28
|
+
*/
|
|
29
|
+
const UTIL_DIR_RE = /[/](utils?|helpers?|internal|private|common|shared)[/]/i;
|
|
30
|
+
/** Checks whether a single character position is a doc-comment start. */
|
|
31
|
+
function hasDocCommentBefore(lines, startLine) {
|
|
32
|
+
// Look back up to 10 lines (handles multi-line annotations + blank lines).
|
|
33
|
+
const limit = Math.max(0, startLine - 11);
|
|
34
|
+
for (let i = startLine - 2; i >= limit; i--) {
|
|
35
|
+
const trimmed = lines[i]?.trim() ?? '';
|
|
36
|
+
if (trimmed === '')
|
|
37
|
+
continue; // blank — keep looking
|
|
38
|
+
if (trimmed.startsWith('/**') || // Javadoc / JSDoc open
|
|
39
|
+
trimmed.startsWith('*/') || // mid-block or close
|
|
40
|
+
trimmed.startsWith('*') || // doc block body line
|
|
41
|
+
trimmed.startsWith('///') || // Rust / C# doc comment
|
|
42
|
+
trimmed.startsWith('//!')) { // Rust inner doc
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// If we hit a non-blank, non-doc line that isn't an annotation or decorator,
|
|
46
|
+
// stop searching — we've gone past any preceding doc block.
|
|
47
|
+
if (!trimmed.startsWith('@') && !trimmed.startsWith('#['))
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/** Check whether the first statement in the method body is a Python docstring. */
|
|
53
|
+
function hasPythonDocstring(lines, method) {
|
|
54
|
+
const bodyStart = method.start_line; // start_line is the `def` line (1-indexed)
|
|
55
|
+
for (let i = bodyStart; i < Math.min(bodyStart + 4, lines.length); i++) {
|
|
56
|
+
const trimmed = lines[i]?.trim() ?? '';
|
|
57
|
+
if (trimmed === '')
|
|
58
|
+
continue;
|
|
59
|
+
return trimmed.startsWith('"""') || trimmed.startsWith("'''");
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function isPublicMethod(method, language) {
|
|
64
|
+
switch (language) {
|
|
65
|
+
case 'java':
|
|
66
|
+
return method.modifiers.includes('public');
|
|
67
|
+
case 'javascript':
|
|
68
|
+
case 'typescript':
|
|
69
|
+
return !method.modifiers.includes('private') &&
|
|
70
|
+
!method.modifiers.includes('protected');
|
|
71
|
+
case 'python':
|
|
72
|
+
return !method.name.startsWith('_');
|
|
73
|
+
default:
|
|
74
|
+
return false; // Rust, Bash, etc. — skip
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class MissingPublicDocPass {
|
|
78
|
+
name = 'missing-public-doc';
|
|
79
|
+
category = 'maintainability';
|
|
80
|
+
run(ctx) {
|
|
81
|
+
const { graph, code, language } = ctx;
|
|
82
|
+
// Skip test/spec files — doc comments in test helpers are low value.
|
|
83
|
+
if (TEST_PATH_RE.test(graph.ir.meta.file)) {
|
|
84
|
+
return { missingDocMethods: [], missingDocTypes: [] };
|
|
85
|
+
}
|
|
86
|
+
// Skip files inside utility/helper directories — these are internal
|
|
87
|
+
// implementation details, not public library API surfaces.
|
|
88
|
+
if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
|
|
89
|
+
return { missingDocMethods: [], missingDocTypes: [] };
|
|
90
|
+
}
|
|
91
|
+
// Only supported languages.
|
|
92
|
+
if (!['java', 'javascript', 'typescript', 'python'].includes(language)) {
|
|
93
|
+
return { missingDocMethods: [], missingDocTypes: [] };
|
|
94
|
+
}
|
|
95
|
+
const lines = code.split('\n');
|
|
96
|
+
const file = graph.ir.meta.file;
|
|
97
|
+
const missingDocMethods = [];
|
|
98
|
+
const missingDocTypes = [];
|
|
99
|
+
for (const type of graph.ir.types) {
|
|
100
|
+
// Skip the synthetic '<module>' type used to group top-level functions.
|
|
101
|
+
// It is not a real class/interface and requiring a doc comment on it
|
|
102
|
+
// produces misleading findings for every TypeScript/JavaScript file.
|
|
103
|
+
if (type.name === '<module>')
|
|
104
|
+
continue;
|
|
105
|
+
// Check type-level doc comment.
|
|
106
|
+
if (!hasDocCommentBefore(lines, type.start_line)) {
|
|
107
|
+
missingDocTypes.push(type);
|
|
108
|
+
ctx.addFinding({
|
|
109
|
+
id: `missing-public-doc-${file}-${type.start_line}`,
|
|
110
|
+
pass: this.name,
|
|
111
|
+
category: this.category,
|
|
112
|
+
rule_id: this.name,
|
|
113
|
+
severity: 'low',
|
|
114
|
+
level: 'note',
|
|
115
|
+
message: `Missing doc comment on ${type.kind} \`${type.name}\``,
|
|
116
|
+
file,
|
|
117
|
+
line: type.start_line,
|
|
118
|
+
fix: `Add a /** ... */ doc comment above \`${type.kind} ${type.name}\``,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// Check method-level doc comments for public methods.
|
|
122
|
+
for (const method of type.methods) {
|
|
123
|
+
if (!isPublicMethod(method, language))
|
|
124
|
+
continue;
|
|
125
|
+
const documented = language === 'python'
|
|
126
|
+
? hasPythonDocstring(lines, method)
|
|
127
|
+
: hasDocCommentBefore(lines, method.start_line);
|
|
128
|
+
if (!documented) {
|
|
129
|
+
missingDocMethods.push({ type, method });
|
|
130
|
+
ctx.addFinding({
|
|
131
|
+
id: `missing-public-doc-${file}-${method.start_line}`,
|
|
132
|
+
pass: this.name,
|
|
133
|
+
category: this.category,
|
|
134
|
+
rule_id: this.name,
|
|
135
|
+
severity: 'low',
|
|
136
|
+
level: 'note',
|
|
137
|
+
message: `Missing doc comment on public method \`${type.name}.${method.name}\``,
|
|
138
|
+
file,
|
|
139
|
+
line: method.start_line,
|
|
140
|
+
fix: `Add a /** ... */ doc comment above \`${method.name}\``,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { missingDocMethods, missingDocTypes };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=missing-public-doc-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-public-doc-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/missing-public-doc-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,yEAAyE;AACzE,MAAM,YAAY,GAAG,0DAA0D,CAAC;AAEhF;;;;GAIG;AACH,MAAM,WAAW,GAAG,yDAAyD,CAAC;AAE9E,yEAAyE;AACzE,SAAS,mBAAmB,CAAC,KAAe,EAAE,SAAiB;IAC7D,2EAA2E;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,EAAE,CAAC,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE;YAAE,SAAS,CAAuB,uBAAuB;QAC3E,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAuB,uBAAuB;YACvE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAwB,qBAAqB;YACrE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAyB,sBAAsB;YACtE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAuB,wBAAwB;YACxE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAoB,iBAAiB;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,6EAA6E;QAC7E,4DAA4D;QAC5D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,MAAM;IACnE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,kBAAkB,CAAC,KAAe,EAAE,MAAkB;IAC7D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,2CAA2C;IAChF,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE;YAAE,SAAS;QAC7B,OAAO,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,MAAkB,EAAE,QAAgB;IAC1D,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC7C,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY;YACf,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;gBACrC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACjD,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACtC;YACE,OAAO,KAAK,CAAC,CAAC,0BAA0B;IAC5C,CAAC;AACH,CAAC;AAOD,MAAM,OAAO,oBAAoB;IACtB,IAAI,GAAG,oBAAoB,CAAC;IAC5B,QAAQ,GAAG,iBAA0B,CAAC;IAE/C,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;QAEtC,qEAAqE;QACrE,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,OAAO,EAAE,iBAAiB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;QACxD,CAAC;QAED,oEAAoE;QACpE,2DAA2D;QAC3D,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,iBAAiB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;QACxD,CAAC;QAED,4BAA4B;QAC5B,IAAI,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvE,OAAO,EAAE,iBAAiB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;QACxD,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAEhC,MAAM,iBAAiB,GAAkD,EAAE,CAAC;QAC5E,MAAM,eAAe,GAAe,EAAE,CAAC;QAEvC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAClC,wEAAwE;YACxE,qEAAqE;YACrE,qEAAqE;YACrE,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;gBAAE,SAAS;YAEvC,gCAAgC;YAChC,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjD,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,GAAG,CAAC,UAAU,CAAC;oBACb,EAAE,EAAE,sBAAsB,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;oBACnD,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,OAAO,EAAE,IAAI,CAAC,IAAI;oBAClB,QAAQ,EAAE,KAAK;oBACf,KAAK,EAAE,MAAM;oBACb,OAAO,EAAE,0BAA0B,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,IAAI;oBAC/D,IAAI;oBACJ,IAAI,EAAE,IAAI,CAAC,UAAU;oBACrB,GAAG,EAAE,wCAAwC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI;iBACxE,CAAC,CAAC;YACL,CAAC;YAED,sDAAsD;YACtD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC;oBAAE,SAAS;gBAEhD,MAAM,UAAU,GAAG,QAAQ,KAAK,QAAQ;oBACtC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC;oBACnC,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;gBAElD,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,iBAAiB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;oBACzC,GAAG,CAAC,UAAU,CAAC;wBACb,EAAE,EAAE,sBAAsB,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE;wBACrD,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,OAAO,EAAE,IAAI,CAAC,IAAI;wBAClB,QAAQ,EAAE,KAAK;wBACf,KAAK,EAAE,MAAM;wBACb,OAAO,EAAE,0CAA0C,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI;wBAC/E,IAAI;wBACJ,IAAI,EAAE,MAAM,CAAC,UAAU;wBACvB,GAAG,EAAE,wCAAwC,MAAM,CAAC,IAAI,IAAI;qBAC7D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #45: n-plus-one (CWE-1049, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects calls to database or external-API methods that occur inside a loop
|
|
5
|
+
* body, producing N+1 round-trips instead of a single batch query.
|
|
6
|
+
*
|
|
7
|
+
* Detection uses two signals:
|
|
8
|
+
* 1. The call line falls inside a loop body identified by a CFG back-edge
|
|
9
|
+
* (`graph.loopBodies()`).
|
|
10
|
+
* 2. The method name (and optionally its receiver) matches a curated set of
|
|
11
|
+
* DB / HTTP / ORM patterns.
|
|
12
|
+
*
|
|
13
|
+
* Precision strategy:
|
|
14
|
+
* - High-confidence method names (e.g. `executeQuery`, `findUnique`) are
|
|
15
|
+
* flagged regardless of receiver.
|
|
16
|
+
* - Medium-confidence names (e.g. `find`, `save`, `get`) require a receiver
|
|
17
|
+
* that looks like a DB/HTTP client.
|
|
18
|
+
*/
|
|
19
|
+
import type { CallInfo } from '../../types/index.js';
|
|
20
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
21
|
+
export interface NPlusOnePassResult {
|
|
22
|
+
/** Calls inside loop bodies that hit a DB or external API. */
|
|
23
|
+
loopDbCalls: CallInfo[];
|
|
24
|
+
}
|
|
25
|
+
export declare class NPlusOnePass implements AnalysisPass<NPlusOnePassResult> {
|
|
26
|
+
readonly name = "n-plus-one";
|
|
27
|
+
readonly category: "performance";
|
|
28
|
+
run(ctx: PassContext): NPlusOnePassResult;
|
|
29
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #45: n-plus-one (CWE-1049, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects calls to database or external-API methods that occur inside a loop
|
|
5
|
+
* body, producing N+1 round-trips instead of a single batch query.
|
|
6
|
+
*
|
|
7
|
+
* Detection uses two signals:
|
|
8
|
+
* 1. The call line falls inside a loop body identified by a CFG back-edge
|
|
9
|
+
* (`graph.loopBodies()`).
|
|
10
|
+
* 2. The method name (and optionally its receiver) matches a curated set of
|
|
11
|
+
* DB / HTTP / ORM patterns.
|
|
12
|
+
*
|
|
13
|
+
* Precision strategy:
|
|
14
|
+
* - High-confidence method names (e.g. `executeQuery`, `findUnique`) are
|
|
15
|
+
* flagged regardless of receiver.
|
|
16
|
+
* - Medium-confidence names (e.g. `find`, `save`, `get`) require a receiver
|
|
17
|
+
* that looks like a DB/HTTP client.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Methods that strongly imply a DB query or external I/O, regardless of receiver.
|
|
21
|
+
* Only names with virtually no non-DB usage at this level of confidence.
|
|
22
|
+
*/
|
|
23
|
+
const HIGH_CONFIDENCE_DB_METHODS = new Set([
|
|
24
|
+
// JDBC / raw SQL
|
|
25
|
+
'executeQuery', 'executeUpdate', 'prepareStatement', 'prepareCall',
|
|
26
|
+
// Spring Data / JPA
|
|
27
|
+
'findById', 'findAll', 'saveAll', 'deleteById', 'existsById',
|
|
28
|
+
// Mongoose
|
|
29
|
+
'findByIdAndUpdate', 'findByIdAndDelete',
|
|
30
|
+
'findOneAndUpdate', 'findOneAndDelete',
|
|
31
|
+
'countDocuments', 'aggregate', 'distinct',
|
|
32
|
+
// Sequelize
|
|
33
|
+
'findByPk', 'findAndCountAll', 'bulkCreate', 'bulkUpdate',
|
|
34
|
+
// Prisma
|
|
35
|
+
'findFirst', 'findUnique', 'findMany', 'createMany', 'updateMany', 'deleteMany',
|
|
36
|
+
// Network
|
|
37
|
+
'fetch',
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Methods that may be DB/HTTP calls — flag only when the receiver looks like
|
|
41
|
+
* a database or HTTP client.
|
|
42
|
+
*/
|
|
43
|
+
const MEDIUM_CONFIDENCE_DB_METHODS = new Set([
|
|
44
|
+
'query', 'execute', 'find', 'findOne',
|
|
45
|
+
'save', 'create', 'update', 'delete', 'insert', 'upsert', 'remove',
|
|
46
|
+
'get', 'post', 'put', 'patch', 'request',
|
|
47
|
+
'load', 'lookup',
|
|
48
|
+
]);
|
|
49
|
+
/** Receiver names that indicate a DB or HTTP client. */
|
|
50
|
+
const DB_OR_HTTP_RECEIVER = /^(db|conn|connection|pool|client|repo|repository|orm|em|entityManager|sequelize|mongoose|prisma|axios|http|https|api|svc|service|dao|store|cache|gql|graphql)/i;
|
|
51
|
+
function isDbOrApiCall(call) {
|
|
52
|
+
if (HIGH_CONFIDENCE_DB_METHODS.has(call.method_name))
|
|
53
|
+
return true;
|
|
54
|
+
if (MEDIUM_CONFIDENCE_DB_METHODS.has(call.method_name)) {
|
|
55
|
+
return call.receiver != null && DB_OR_HTTP_RECEIVER.test(call.receiver);
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
export class NPlusOnePass {
|
|
60
|
+
name = 'n-plus-one';
|
|
61
|
+
category = 'performance';
|
|
62
|
+
run(ctx) {
|
|
63
|
+
const { graph } = ctx;
|
|
64
|
+
const file = graph.ir.meta.file;
|
|
65
|
+
const loops = graph.loopBodies();
|
|
66
|
+
if (loops.length === 0)
|
|
67
|
+
return { loopDbCalls: [] };
|
|
68
|
+
const loopDbCalls = [];
|
|
69
|
+
for (const call of graph.ir.calls) {
|
|
70
|
+
if (!isDbOrApiCall(call))
|
|
71
|
+
continue;
|
|
72
|
+
const line = call.location.line;
|
|
73
|
+
const loop = loops.find(l => line >= l.start_line && line <= l.end_line);
|
|
74
|
+
if (!loop)
|
|
75
|
+
continue;
|
|
76
|
+
loopDbCalls.push(call);
|
|
77
|
+
ctx.addFinding({
|
|
78
|
+
id: `n-plus-one-${file}-${line}`,
|
|
79
|
+
pass: this.name,
|
|
80
|
+
category: this.category,
|
|
81
|
+
rule_id: this.name,
|
|
82
|
+
cwe: 'CWE-1049',
|
|
83
|
+
severity: 'medium',
|
|
84
|
+
level: 'warning',
|
|
85
|
+
message: `N+1 query: \`${call.method_name}()\` is called inside a loop ` +
|
|
86
|
+
`(loop lines ${loop.start_line}–${loop.end_line}) — consider batching`,
|
|
87
|
+
file,
|
|
88
|
+
line,
|
|
89
|
+
fix: `Move \`${call.method_name}()\` outside the loop and batch the operation`,
|
|
90
|
+
evidence: {
|
|
91
|
+
loop_start: loop.start_line,
|
|
92
|
+
loop_end: loop.end_line,
|
|
93
|
+
receiver: call.receiver ?? undefined,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return { loopDbCalls };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=n-plus-one-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"n-plus-one-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/n-plus-one-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
20
|
+
export interface NullDerefResult {
|
|
21
|
+
/** Potential null-dereferences detected. */
|
|
22
|
+
potentialNullDerefs: Array<{
|
|
23
|
+
defLine: number;
|
|
24
|
+
useLine: number;
|
|
25
|
+
variable: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export declare class NullDerefPass implements AnalysisPass<NullDerefResult> {
|
|
29
|
+
readonly name = "null-deref";
|
|
30
|
+
readonly category: "reliability";
|
|
31
|
+
run(ctx: PassContext): NullDerefResult;
|
|
32
|
+
}
|