circle-ir 3.12.1 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/passes/blocking-main-thread-pass.d.ts +40 -0
- package/dist/analysis/passes/blocking-main-thread-pass.js +112 -0
- package/dist/analysis/passes/blocking-main-thread-pass.js.map +1 -0
- package/dist/analysis/passes/excessive-allocation-pass.d.ts +29 -0
- package/dist/analysis/passes/excessive-allocation-pass.js +85 -0
- package/dist/analysis/passes/excessive-allocation-pass.js.map +1 -0
- package/dist/analysis/passes/feature-envy-pass.d.ts +54 -0
- package/dist/analysis/passes/feature-envy-pass.js +132 -0
- package/dist/analysis/passes/feature-envy-pass.js.map +1 -0
- package/dist/analysis/passes/god-class-pass.d.ts +58 -0
- package/dist/analysis/passes/god-class-pass.js +197 -0
- package/dist/analysis/passes/god-class-pass.js.map +1 -0
- package/dist/analysis/passes/missing-guard-dom-pass.d.ts +18 -0
- package/dist/analysis/passes/missing-guard-dom-pass.js +18 -0
- package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -1
- package/dist/analysis/passes/missing-stream-pass.d.ts +28 -0
- package/dist/analysis/passes/missing-stream-pass.js +173 -0
- package/dist/analysis/passes/missing-stream-pass.js.map +1 -0
- package/dist/analysis/passes/n-plus-one-pass.js +18 -3
- package/dist/analysis/passes/n-plus-one-pass.js.map +1 -1
- package/dist/analysis/passes/naming-convention-pass.d.ts +62 -0
- package/dist/analysis/passes/naming-convention-pass.js +169 -0
- package/dist/analysis/passes/naming-convention-pass.js.map +1 -0
- package/dist/analysis/passes/null-deref-pass.js +17 -1
- package/dist/analysis/passes/null-deref-pass.js.map +1 -1
- package/dist/analysis/passes/serial-await-pass.js +3 -2
- package/dist/analysis/passes/serial-await-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +70 -8
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analyzer.d.ts +28 -12
- package/dist/analyzer.js +30 -14
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +690 -101
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #83: blocking-main-thread (CWE-1050, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects synchronous/blocking operations inside HTTP request handlers
|
|
5
|
+
* that stall the Node.js event loop, degrading latency under load.
|
|
6
|
+
*
|
|
7
|
+
* Scope: JavaScript / TypeScript only.
|
|
8
|
+
*
|
|
9
|
+
* Differentiation from SyncIoAsyncPass (#48):
|
|
10
|
+
* SyncIoAsyncPass catches *Sync calls inside any `async` function.
|
|
11
|
+
* This pass focuses specifically on request handler context
|
|
12
|
+
* (NestJS/Express/Koa/Fastify/Hono) and includes expensive crypto/hashing
|
|
13
|
+
* operations that are particularly harmful in synchronous handlers.
|
|
14
|
+
*
|
|
15
|
+
* Detection strategy:
|
|
16
|
+
* 1. Identify request handler methods by:
|
|
17
|
+
* a. HTTP method decorators in annotations (Get, Post, Put, Patch, Delete)
|
|
18
|
+
* b. Common handler parameter names (req, res, ctx, c)
|
|
19
|
+
* c. Conventional handler method names (handle, handler)
|
|
20
|
+
* 2. Within those method ranges, scan graph.ir.calls for:
|
|
21
|
+
* a. Blocking *Sync calls (readFileSync, execSync, spawnSync, etc.)
|
|
22
|
+
* b. Synchronous crypto operations (createHash, hashSync, pbkdf2Sync,
|
|
23
|
+
* scryptSync, generateKeyPairSync)
|
|
24
|
+
* 3. Emit one warning per blocking call site.
|
|
25
|
+
*/
|
|
26
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
27
|
+
export interface BlockingMainThreadResult {
|
|
28
|
+
blockingInHandlers: Array<{
|
|
29
|
+
line: number;
|
|
30
|
+
method: string;
|
|
31
|
+
handler: string;
|
|
32
|
+
reason: 'sync-suffix' | 'crypto';
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare class BlockingMainThreadPass implements AnalysisPass<BlockingMainThreadResult> {
|
|
36
|
+
readonly name = "blocking-main-thread";
|
|
37
|
+
readonly category: "performance";
|
|
38
|
+
run(ctx: PassContext): BlockingMainThreadResult;
|
|
39
|
+
private isRequestHandler;
|
|
40
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #83: blocking-main-thread (CWE-1050, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects synchronous/blocking operations inside HTTP request handlers
|
|
5
|
+
* that stall the Node.js event loop, degrading latency under load.
|
|
6
|
+
*
|
|
7
|
+
* Scope: JavaScript / TypeScript only.
|
|
8
|
+
*
|
|
9
|
+
* Differentiation from SyncIoAsyncPass (#48):
|
|
10
|
+
* SyncIoAsyncPass catches *Sync calls inside any `async` function.
|
|
11
|
+
* This pass focuses specifically on request handler context
|
|
12
|
+
* (NestJS/Express/Koa/Fastify/Hono) and includes expensive crypto/hashing
|
|
13
|
+
* operations that are particularly harmful in synchronous handlers.
|
|
14
|
+
*
|
|
15
|
+
* Detection strategy:
|
|
16
|
+
* 1. Identify request handler methods by:
|
|
17
|
+
* a. HTTP method decorators in annotations (Get, Post, Put, Patch, Delete)
|
|
18
|
+
* b. Common handler parameter names (req, res, ctx, c)
|
|
19
|
+
* c. Conventional handler method names (handle, handler)
|
|
20
|
+
* 2. Within those method ranges, scan graph.ir.calls for:
|
|
21
|
+
* a. Blocking *Sync calls (readFileSync, execSync, spawnSync, etc.)
|
|
22
|
+
* b. Synchronous crypto operations (createHash, hashSync, pbkdf2Sync,
|
|
23
|
+
* scryptSync, generateKeyPairSync)
|
|
24
|
+
* 3. Emit one warning per blocking call site.
|
|
25
|
+
*/
|
|
26
|
+
/** HTTP method decorator names (NestJS / Fastify / express-style, without the @ prefix). */
|
|
27
|
+
const HTTP_DECORATORS = new Set([
|
|
28
|
+
'Get', 'Post', 'Put', 'Patch', 'Delete', 'All', 'Options', 'Head',
|
|
29
|
+
'Route', 'Handler',
|
|
30
|
+
]);
|
|
31
|
+
/** Parameter names that indicate an HTTP request handler. */
|
|
32
|
+
const HANDLER_PARAM_NAMES = new Set([
|
|
33
|
+
'req', 'res', 'request', 'response', 'ctx', 'c', 'event',
|
|
34
|
+
]);
|
|
35
|
+
/** Method names that strongly suggest HTTP request handling. */
|
|
36
|
+
const HANDLER_METHOD_NAMES = new Set([
|
|
37
|
+
'handle', 'handler', 'dispatch', 'invoke', 'serve',
|
|
38
|
+
]);
|
|
39
|
+
/** Synchronous crypto operations that are expensive in the request path. */
|
|
40
|
+
const CRYPTO_BLOCKING_METHODS = new Set([
|
|
41
|
+
'createHash', 'hashSync', 'pbkdf2Sync', 'scryptSync',
|
|
42
|
+
'generateKeyPairSync', 'generateKeySync', 'deriveKeySync',
|
|
43
|
+
]);
|
|
44
|
+
const SYNC_SUFFIX_RE = /Sync$/;
|
|
45
|
+
export class BlockingMainThreadPass {
|
|
46
|
+
name = 'blocking-main-thread';
|
|
47
|
+
category = 'performance';
|
|
48
|
+
run(ctx) {
|
|
49
|
+
const { graph, language } = ctx;
|
|
50
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
51
|
+
return { blockingInHandlers: [] };
|
|
52
|
+
}
|
|
53
|
+
const file = graph.ir.meta.file;
|
|
54
|
+
// Collect request handler method line ranges
|
|
55
|
+
const handlerRanges = [];
|
|
56
|
+
for (const type of graph.ir.types) {
|
|
57
|
+
for (const method of type.methods) {
|
|
58
|
+
if (this.isRequestHandler(method)) {
|
|
59
|
+
handlerRanges.push({
|
|
60
|
+
start: method.start_line,
|
|
61
|
+
end: method.end_line,
|
|
62
|
+
name: method.name,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (handlerRanges.length === 0)
|
|
68
|
+
return { blockingInHandlers: [] };
|
|
69
|
+
const blockingInHandlers = [];
|
|
70
|
+
for (const call of graph.ir.calls) {
|
|
71
|
+
const name = call.method_name;
|
|
72
|
+
const isCrypto = CRYPTO_BLOCKING_METHODS.has(name);
|
|
73
|
+
const isSyncSuffix = SYNC_SUFFIX_RE.test(name);
|
|
74
|
+
if (!isCrypto && !isSyncSuffix)
|
|
75
|
+
continue;
|
|
76
|
+
const line = call.location.line;
|
|
77
|
+
const range = handlerRanges.find(r => line >= r.start && line <= r.end);
|
|
78
|
+
if (!range)
|
|
79
|
+
continue;
|
|
80
|
+
const reason = isCrypto ? 'crypto' : 'sync-suffix';
|
|
81
|
+
blockingInHandlers.push({ line, method: name, handler: range.name, reason });
|
|
82
|
+
ctx.addFinding({
|
|
83
|
+
id: `blocking-main-thread-${file}-${line}`,
|
|
84
|
+
pass: this.name,
|
|
85
|
+
category: this.category,
|
|
86
|
+
rule_id: this.name,
|
|
87
|
+
cwe: 'CWE-1050',
|
|
88
|
+
severity: 'medium',
|
|
89
|
+
level: 'warning',
|
|
90
|
+
message: `Blocking call \`${name}()\` inside request handler '${range.name}' ` +
|
|
91
|
+
`stalls the event loop under concurrent load`,
|
|
92
|
+
file,
|
|
93
|
+
line,
|
|
94
|
+
fix: 'Move to an async equivalent or offload to a worker thread',
|
|
95
|
+
evidence: { handler: range.name, blocking_method: name, reason },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return { blockingInHandlers };
|
|
99
|
+
}
|
|
100
|
+
isRequestHandler(method) {
|
|
101
|
+
// NestJS / Fastify HTTP decorators
|
|
102
|
+
if (method.annotations.some(a => HTTP_DECORATORS.has(a)))
|
|
103
|
+
return true;
|
|
104
|
+
// Conventional handler method names
|
|
105
|
+
if (HANDLER_METHOD_NAMES.has(method.name))
|
|
106
|
+
return true;
|
|
107
|
+
// Express/Koa/Hono request handler patterns: (req, res) or (ctx)
|
|
108
|
+
const paramNames = method.parameters.map(p => p.name.toLowerCase());
|
|
109
|
+
return paramNames.some(n => HANDLER_PARAM_NAMES.has(n));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=blocking-main-thread-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocking-main-thread-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #84: excessive-allocation (CWE-770, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects collection/object allocations inside loop bodies that
|
|
5
|
+
* create unnecessary GC pressure and slow down hot paths.
|
|
6
|
+
* Each allocation forces the garbage collector to do extra work,
|
|
7
|
+
* degrading throughput in tight loops.
|
|
8
|
+
*
|
|
9
|
+
* Detection strategy:
|
|
10
|
+
* 1. Identify loop body line ranges via graph.loopBodies().
|
|
11
|
+
* 2. For each line within a loop body, scan source text for
|
|
12
|
+
* language-specific allocation patterns.
|
|
13
|
+
* 3. Skip lines with explicit reuse signals (pool, cache, preallocat).
|
|
14
|
+
* 4. Emit one warning per allocation site.
|
|
15
|
+
*
|
|
16
|
+
* Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
|
|
17
|
+
*/
|
|
18
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
19
|
+
export interface ExcessiveAllocationResult {
|
|
20
|
+
allocationsInLoops: Array<{
|
|
21
|
+
line: number;
|
|
22
|
+
pattern: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare class ExcessiveAllocationPass implements AnalysisPass<ExcessiveAllocationResult> {
|
|
26
|
+
readonly name = "excessive-allocation";
|
|
27
|
+
readonly category: "performance";
|
|
28
|
+
run(ctx: PassContext): ExcessiveAllocationResult;
|
|
29
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #84: excessive-allocation (CWE-770, category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects collection/object allocations inside loop bodies that
|
|
5
|
+
* create unnecessary GC pressure and slow down hot paths.
|
|
6
|
+
* Each allocation forces the garbage collector to do extra work,
|
|
7
|
+
* degrading throughput in tight loops.
|
|
8
|
+
*
|
|
9
|
+
* Detection strategy:
|
|
10
|
+
* 1. Identify loop body line ranges via graph.loopBodies().
|
|
11
|
+
* 2. For each line within a loop body, scan source text for
|
|
12
|
+
* language-specific allocation patterns.
|
|
13
|
+
* 3. Skip lines with explicit reuse signals (pool, cache, preallocat).
|
|
14
|
+
* 4. Emit one warning per allocation site.
|
|
15
|
+
*
|
|
16
|
+
* Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
|
|
17
|
+
*/
|
|
18
|
+
/** Allocation patterns keyed by language. */
|
|
19
|
+
const ALLOC_PATTERNS = {
|
|
20
|
+
javascript: /\bnew\s+(Array|Map|Set|Object|WeakMap|WeakSet|Error|RegExp|Date|Buffer|Uint8Array|Int8Array|Float32Array|ArrayBuffer)\s*[(<]|\bArray\.from\s*\(|\bstructuredClone\s*\(|\bObject\.create\s*\(/,
|
|
21
|
+
typescript: /\bnew\s+(Array|Map|Set|Object|WeakMap|WeakSet|Error|RegExp|Date|Buffer|Uint8Array|Int8Array|Float32Array|ArrayBuffer)\s*[(<]|\bArray\.from\s*\(|\bstructuredClone\s*\(|\bObject\.create\s*\(/,
|
|
22
|
+
java: /\bnew\s+(ArrayList|HashMap|HashSet|LinkedList|TreeMap|TreeSet|PriorityQueue|ArrayDeque|StringBuilder|StringBuffer|CopyOnWriteArrayList|ConcurrentHashMap)\s*[(<]|\bnew\s+\w[\w.<>]*\[\s*[a-zA-Z]\w*/,
|
|
23
|
+
python: /\b(list|dict|set|tuple|bytearray|defaultdict|OrderedDict|Counter|deque)\s*\(\s*\)|\[\s*\]|\{\s*\}(?!\s*[}\]])/,
|
|
24
|
+
rust: /\b(Vec|HashMap|HashSet|BTreeMap|BTreeSet|VecDeque|LinkedList|String|Box|Rc|Arc)\s*::\s*new\s*\(/,
|
|
25
|
+
};
|
|
26
|
+
/** Signals that the allocation is intentional / is a reuse pattern. */
|
|
27
|
+
const BENIGN_RE = /\bpool\b|\bcache\b|\breuse\b|\bpreallocat|\brecycl/i;
|
|
28
|
+
export class ExcessiveAllocationPass {
|
|
29
|
+
name = 'excessive-allocation';
|
|
30
|
+
category = 'performance';
|
|
31
|
+
run(ctx) {
|
|
32
|
+
const { graph, code, language } = ctx;
|
|
33
|
+
if (language === 'bash') {
|
|
34
|
+
return { allocationsInLoops: [] };
|
|
35
|
+
}
|
|
36
|
+
const pattern = ALLOC_PATTERNS[language];
|
|
37
|
+
if (!pattern)
|
|
38
|
+
return { allocationsInLoops: [] };
|
|
39
|
+
const loops = graph.loopBodies();
|
|
40
|
+
if (loops.length === 0)
|
|
41
|
+
return { allocationsInLoops: [] };
|
|
42
|
+
const file = graph.ir.meta.file;
|
|
43
|
+
const codeLines = code.split('\n');
|
|
44
|
+
const allocationsInLoops = [];
|
|
45
|
+
const reported = new Set();
|
|
46
|
+
for (const loop of loops) {
|
|
47
|
+
for (let ln = loop.start_line; ln <= loop.end_line; ln++) {
|
|
48
|
+
if (reported.has(ln))
|
|
49
|
+
continue;
|
|
50
|
+
const src = codeLines[ln - 1] ?? '';
|
|
51
|
+
const match = pattern.exec(src);
|
|
52
|
+
if (!match)
|
|
53
|
+
continue;
|
|
54
|
+
if (BENIGN_RE.test(src))
|
|
55
|
+
continue;
|
|
56
|
+
// Extract a short label for the allocation pattern
|
|
57
|
+
const allocLabel = match[0].replace(/\s+/g, ' ').trim();
|
|
58
|
+
allocationsInLoops.push({ line: ln, pattern: allocLabel });
|
|
59
|
+
reported.add(ln);
|
|
60
|
+
ctx.addFinding({
|
|
61
|
+
id: `excessive-allocation-${file}-${ln}`,
|
|
62
|
+
pass: this.name,
|
|
63
|
+
category: this.category,
|
|
64
|
+
rule_id: this.name,
|
|
65
|
+
cwe: 'CWE-770',
|
|
66
|
+
severity: 'medium',
|
|
67
|
+
level: 'warning',
|
|
68
|
+
message: `Repeated allocation inside loop (lines ${loop.start_line}–${loop.end_line}): ` +
|
|
69
|
+
`\`${allocLabel}\` creates GC pressure on every iteration`,
|
|
70
|
+
file,
|
|
71
|
+
line: ln,
|
|
72
|
+
snippet: src.trim(),
|
|
73
|
+
fix: 'Pre-allocate outside the loop and reset/reuse the collection each iteration',
|
|
74
|
+
evidence: {
|
|
75
|
+
allocation: allocLabel,
|
|
76
|
+
loop_start: loop.start_line,
|
|
77
|
+
loop_end: loop.end_line,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { allocationsInLoops };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=excessive-allocation-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excessive-allocation-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/excessive-allocation-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #87: feature-envy (CWE-1060, category: architecture)
|
|
3
|
+
*
|
|
4
|
+
* @deprecated NOT REGISTERED IN THE DEFAULT PIPELINE
|
|
5
|
+
*
|
|
6
|
+
* This pass was removed from the default AnalysisPipeline in v3.14.0 because
|
|
7
|
+
* the call-count heuristic (external_max ≥ 4 AND margin > 2) fires trivially
|
|
8
|
+
* on legitimate delegation patterns — facades, controllers, service classes —
|
|
9
|
+
* and its fix suggestion ("move this method to OtherClass") is incorrect when
|
|
10
|
+
* the method's design intent is orchestration rather than feature envy.
|
|
11
|
+
* Confirming true feature envy requires understanding design intent, which is
|
|
12
|
+
* LLM territory.
|
|
13
|
+
*
|
|
14
|
+
* The raw signals this pass relies on are already present in CircleIR:
|
|
15
|
+
* • ir.calls — per-callsite receiver, receiver_type, location.line
|
|
16
|
+
* • ir.types — per-method start_line / end_line to scope the calls
|
|
17
|
+
*
|
|
18
|
+
* This file is retained so that circle-ir-ai can consume the per-method
|
|
19
|
+
* call-count breakdown and apply semantic reasoning to distinguish genuine
|
|
20
|
+
* feature envy from intentional delegation.
|
|
21
|
+
*
|
|
22
|
+
* Detects methods that call another class's methods far more often than
|
|
23
|
+
* their own class's — a sign that the method "envies" the other class
|
|
24
|
+
* and should probably be moved there.
|
|
25
|
+
*
|
|
26
|
+
* Detection strategy:
|
|
27
|
+
* 1. For each method in each class, collect all call sites in its line range.
|
|
28
|
+
* 2. Separate calls into:
|
|
29
|
+
* internal — receiver is 'this' / 'self' / null (own class)
|
|
30
|
+
* external — receiver_type != own class name (other classes)
|
|
31
|
+
* 3. Find the external type with the most calls (external_max).
|
|
32
|
+
* 4. Emit a note when:
|
|
33
|
+
* external_max >= 4 (at least 4 calls to another class)
|
|
34
|
+
* AND
|
|
35
|
+
* external_max > internal + 2 (clearly prefers the other class)
|
|
36
|
+
*
|
|
37
|
+
* Languages: Java, TypeScript, Python. Bash / Rust — skipped.
|
|
38
|
+
*/
|
|
39
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
40
|
+
export interface FeatureEnvyResult {
|
|
41
|
+
envyMethods: Array<{
|
|
42
|
+
className: string;
|
|
43
|
+
methodName: string;
|
|
44
|
+
line: number;
|
|
45
|
+
enviedClass: string;
|
|
46
|
+
externalCalls: number;
|
|
47
|
+
internalCalls: number;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
export declare class FeatureEnvyPass implements AnalysisPass<FeatureEnvyResult> {
|
|
51
|
+
readonly name = "feature-envy";
|
|
52
|
+
readonly category: "architecture";
|
|
53
|
+
run(ctx: PassContext): FeatureEnvyResult;
|
|
54
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #87: feature-envy (CWE-1060, category: architecture)
|
|
3
|
+
*
|
|
4
|
+
* @deprecated NOT REGISTERED IN THE DEFAULT PIPELINE
|
|
5
|
+
*
|
|
6
|
+
* This pass was removed from the default AnalysisPipeline in v3.14.0 because
|
|
7
|
+
* the call-count heuristic (external_max ≥ 4 AND margin > 2) fires trivially
|
|
8
|
+
* on legitimate delegation patterns — facades, controllers, service classes —
|
|
9
|
+
* and its fix suggestion ("move this method to OtherClass") is incorrect when
|
|
10
|
+
* the method's design intent is orchestration rather than feature envy.
|
|
11
|
+
* Confirming true feature envy requires understanding design intent, which is
|
|
12
|
+
* LLM territory.
|
|
13
|
+
*
|
|
14
|
+
* The raw signals this pass relies on are already present in CircleIR:
|
|
15
|
+
* • ir.calls — per-callsite receiver, receiver_type, location.line
|
|
16
|
+
* • ir.types — per-method start_line / end_line to scope the calls
|
|
17
|
+
*
|
|
18
|
+
* This file is retained so that circle-ir-ai can consume the per-method
|
|
19
|
+
* call-count breakdown and apply semantic reasoning to distinguish genuine
|
|
20
|
+
* feature envy from intentional delegation.
|
|
21
|
+
*
|
|
22
|
+
* Detects methods that call another class's methods far more often than
|
|
23
|
+
* their own class's — a sign that the method "envies" the other class
|
|
24
|
+
* and should probably be moved there.
|
|
25
|
+
*
|
|
26
|
+
* Detection strategy:
|
|
27
|
+
* 1. For each method in each class, collect all call sites in its line range.
|
|
28
|
+
* 2. Separate calls into:
|
|
29
|
+
* internal — receiver is 'this' / 'self' / null (own class)
|
|
30
|
+
* external — receiver_type != own class name (other classes)
|
|
31
|
+
* 3. Find the external type with the most calls (external_max).
|
|
32
|
+
* 4. Emit a note when:
|
|
33
|
+
* external_max >= 4 (at least 4 calls to another class)
|
|
34
|
+
* AND
|
|
35
|
+
* external_max > internal + 2 (clearly prefers the other class)
|
|
36
|
+
*
|
|
37
|
+
* Languages: Java, TypeScript, Python. Bash / Rust — skipped.
|
|
38
|
+
*/
|
|
39
|
+
/** Minimum external calls to flag feature envy (avoids trivial utility calls). */
|
|
40
|
+
const MIN_EXTERNAL_CALLS = 4;
|
|
41
|
+
/** How many more calls to external type than own type to flag. */
|
|
42
|
+
const ENVY_MARGIN = 2;
|
|
43
|
+
export class FeatureEnvyPass {
|
|
44
|
+
name = 'feature-envy';
|
|
45
|
+
category = 'architecture';
|
|
46
|
+
run(ctx) {
|
|
47
|
+
const { graph, language } = ctx;
|
|
48
|
+
if (language === 'bash' || language === 'rust') {
|
|
49
|
+
return { envyMethods: [] };
|
|
50
|
+
}
|
|
51
|
+
const file = graph.ir.meta.file;
|
|
52
|
+
const envyMethods = [];
|
|
53
|
+
for (const type of graph.ir.types) {
|
|
54
|
+
if (type.kind !== 'class')
|
|
55
|
+
continue;
|
|
56
|
+
const ownName = type.name;
|
|
57
|
+
for (const method of type.methods) {
|
|
58
|
+
const start = method.start_line;
|
|
59
|
+
const end = method.end_line;
|
|
60
|
+
// Collect calls within this method's line range
|
|
61
|
+
const callsInMethod = graph.ir.calls.filter(c => c.location.line >= start && c.location.line <= end);
|
|
62
|
+
if (callsInMethod.length === 0)
|
|
63
|
+
continue;
|
|
64
|
+
let internalCalls = 0;
|
|
65
|
+
const externalCallCounts = new Map();
|
|
66
|
+
for (const call of callsInMethod) {
|
|
67
|
+
const receiver = call.receiver?.toLowerCase();
|
|
68
|
+
const receiverType = call.receiver_type;
|
|
69
|
+
// Internal: receiver is 'this'/'self', null, or the own type
|
|
70
|
+
if (receiver == null ||
|
|
71
|
+
receiver === 'this' ||
|
|
72
|
+
receiver === 'self' ||
|
|
73
|
+
receiverType == null ||
|
|
74
|
+
receiverType === ownName) {
|
|
75
|
+
internalCalls++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// External: strip generic parameters from receiver_type
|
|
79
|
+
const extType = receiverType.replace(/<.*>/, '').trim();
|
|
80
|
+
if (extType && extType !== ownName) {
|
|
81
|
+
externalCallCounts.set(extType, (externalCallCounts.get(extType) ?? 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (externalCallCounts.size === 0)
|
|
85
|
+
continue;
|
|
86
|
+
// Find the most-called external type
|
|
87
|
+
let enviedClass = '';
|
|
88
|
+
let externalMax = 0;
|
|
89
|
+
for (const [typeName, count] of externalCallCounts) {
|
|
90
|
+
if (count > externalMax) {
|
|
91
|
+
externalMax = count;
|
|
92
|
+
enviedClass = typeName;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (externalMax < MIN_EXTERNAL_CALLS)
|
|
96
|
+
continue;
|
|
97
|
+
if (externalMax <= internalCalls + ENVY_MARGIN)
|
|
98
|
+
continue;
|
|
99
|
+
envyMethods.push({
|
|
100
|
+
className: ownName,
|
|
101
|
+
methodName: method.name,
|
|
102
|
+
line: method.start_line,
|
|
103
|
+
enviedClass,
|
|
104
|
+
externalCalls: externalMax,
|
|
105
|
+
internalCalls,
|
|
106
|
+
});
|
|
107
|
+
ctx.addFinding({
|
|
108
|
+
id: `feature-envy-${file}-${method.start_line}`,
|
|
109
|
+
pass: this.name,
|
|
110
|
+
category: this.category,
|
|
111
|
+
rule_id: this.name,
|
|
112
|
+
cwe: 'CWE-1060',
|
|
113
|
+
severity: 'low',
|
|
114
|
+
level: 'note',
|
|
115
|
+
message: `Feature Envy: \`${ownName}.${method.name}()\` makes ${externalMax} calls to ` +
|
|
116
|
+
`\`${enviedClass}\` vs ${internalCalls} calls to own class`,
|
|
117
|
+
file,
|
|
118
|
+
line: method.start_line,
|
|
119
|
+
fix: `Consider moving \`${method.name}\` to \`${enviedClass}\`, ` +
|
|
120
|
+
`or introducing a collaborator object`,
|
|
121
|
+
evidence: {
|
|
122
|
+
envied_class: enviedClass,
|
|
123
|
+
external_calls: externalMax,
|
|
124
|
+
internal_calls: internalCalls,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { envyMethods };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=feature-envy-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-envy-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/feature-envy-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #86: god-class (CWE-1060, category: architecture)
|
|
3
|
+
*
|
|
4
|
+
* Detects "God Class" anti-pattern: a class that does too much, is poorly
|
|
5
|
+
* cohesive, and is heavily coupled to external types. These classes are hard
|
|
6
|
+
* to test, maintain, and evolve.
|
|
7
|
+
*
|
|
8
|
+
* Three CK metrics are computed inline (the metrics pipeline runs separately
|
|
9
|
+
* and its results are not available here):
|
|
10
|
+
*
|
|
11
|
+
* WMC — Weighted Methods per Class: Σ v(G) per method, where v(G) is
|
|
12
|
+
* McCabe cyclomatic complexity = CFG edges − nodes + 2.
|
|
13
|
+
* Fallback = 1 per method when CFG data is absent.
|
|
14
|
+
*
|
|
15
|
+
* LCOM2 — Lack of Cohesion of Methods (0–1 scale):
|
|
16
|
+
* (P − Q) / max(1, m*(m−1)/2)
|
|
17
|
+
* P = method pairs sharing no fields, Q = pairs sharing ≥1 field.
|
|
18
|
+
* Field access is inferred from DFG defs/uses whose variable name
|
|
19
|
+
* matches a declared field name (name-match heuristic).
|
|
20
|
+
*
|
|
21
|
+
* CBO — Coupling Between Objects: count of distinct external type names
|
|
22
|
+
* referenced in parameter types, field types, or call receiver_type
|
|
23
|
+
* within the class, excluding primitives and same-class references.
|
|
24
|
+
*
|
|
25
|
+
* A finding is emitted when at least 2 of the 3 thresholds are exceeded:
|
|
26
|
+
* WMC > 47 (SonarQube default)
|
|
27
|
+
* LCOM2 > 0.8
|
|
28
|
+
* CBO > 14 (SATD threshold)
|
|
29
|
+
*
|
|
30
|
+
* Languages: Java, TypeScript, Python. Bash / Rust — skipped.
|
|
31
|
+
*/
|
|
32
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
33
|
+
export interface GodClassResult {
|
|
34
|
+
godClasses: Array<{
|
|
35
|
+
className: string;
|
|
36
|
+
line: number;
|
|
37
|
+
wmc: number;
|
|
38
|
+
lcom2: number;
|
|
39
|
+
cbo: number;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare class GodClassPass implements AnalysisPass<GodClassResult> {
|
|
43
|
+
readonly name = "god-class";
|
|
44
|
+
readonly category: "architecture";
|
|
45
|
+
run(ctx: PassContext): GodClassResult;
|
|
46
|
+
/** Compute WMC = Σ v(G) for all methods. v(G) = edges − nodes + 2. */
|
|
47
|
+
private computeWMC;
|
|
48
|
+
/**
|
|
49
|
+
* Compute LCOM2 = (P − Q) / max(1, m*(m−1)/2) clamped to [0, 1].
|
|
50
|
+
* Uses DFG variable names intersected with declared field names.
|
|
51
|
+
*/
|
|
52
|
+
private computeLCOM2;
|
|
53
|
+
/**
|
|
54
|
+
* Compute CBO = count of distinct external type names referenced in the class.
|
|
55
|
+
* Sources: call receiver_type, method parameter types, field types.
|
|
56
|
+
*/
|
|
57
|
+
private computeCBO;
|
|
58
|
+
}
|