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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConstantPropagationPass
|
|
3
|
+
*
|
|
4
|
+
* Runs constant propagation + dead-code detection over the AST.
|
|
5
|
+
*
|
|
6
|
+
* Depends on: taint-matcher (to extract inter-procedural tainted parameters
|
|
7
|
+
* before propagation, so method-parameter taint is seeded correctly).
|
|
8
|
+
*
|
|
9
|
+
* Receives the parsed Tree via constructor because it needs the raw AST for
|
|
10
|
+
* node-level analysis — the CodeGraph contains only extracted IR.
|
|
11
|
+
*/
|
|
12
|
+
import { analyzeConstantPropagation } from '../constant-propagation.js';
|
|
13
|
+
export class ConstantPropagationPass {
|
|
14
|
+
tree;
|
|
15
|
+
name = 'constant-propagation';
|
|
16
|
+
category = 'security';
|
|
17
|
+
constructor(tree) {
|
|
18
|
+
this.tree = tree;
|
|
19
|
+
}
|
|
20
|
+
run(ctx) {
|
|
21
|
+
const { code } = ctx;
|
|
22
|
+
const taintMatcher = ctx.getResult('taint-matcher');
|
|
23
|
+
// Extract inter-procedural parameter sources from the preliminary taint results.
|
|
24
|
+
// These seeds ensure that method parameters receiving tainted data are tracked.
|
|
25
|
+
const taintedParameters = [];
|
|
26
|
+
for (const source of taintMatcher.sources) {
|
|
27
|
+
if (source.type === 'interprocedural_param') {
|
|
28
|
+
// Location format: "ParamType paramName in methodName"
|
|
29
|
+
const match = source.location.match(/(\S+)\s+(\S+)\s+in\s+(\S+)/);
|
|
30
|
+
if (match) {
|
|
31
|
+
taintedParameters.push({
|
|
32
|
+
methodName: match[3],
|
|
33
|
+
paramName: match[2],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return analyzeConstantPropagation(this.tree, code, {
|
|
39
|
+
sanitizerMethods: taintMatcher.sanitizerMethods,
|
|
40
|
+
taintedParameters,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=constant-propagation-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constant-propagation-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/constant-propagation-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,0BAA0B,EAAiC,MAAM,4BAA4B,CAAC;AAKvG,MAAM,OAAO,uBAAuB;IAIL;IAHpB,IAAI,GAAG,sBAAsB,CAAC;IAC9B,QAAQ,GAAG,UAAmB,CAAC;IAExC,YAA6B,IAAU;QAAV,SAAI,GAAJ,IAAI,CAAM;IAAG,CAAC;IAE3C,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;QACrB,MAAM,YAAY,GAAG,GAAG,CAAC,SAAS,CAAqB,eAAe,CAAC,CAAC;QAExE,iFAAiF;QACjF,gFAAgF;QAChF,MAAM,iBAAiB,GAAqD,EAAE,CAAC;QAC/E,KAAK,MAAM,MAAM,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;gBAC5C,uDAAuD;gBACvD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBAClE,IAAI,KAAK,EAAE,CAAC;oBACV,iBAAiB,CAAC,IAAI,CAAC;wBACrB,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;wBACpB,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;qBACpB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,0BAA0B,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YACjD,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;YAC/C,iBAAiB;SAClB,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossFilePass
|
|
3
|
+
*
|
|
4
|
+
* Project-level pass that uses the CrossFileResolver to surface taint flows
|
|
5
|
+
* that cross file boundaries and to map resolved inter-file calls.
|
|
6
|
+
*
|
|
7
|
+
* Unlike the single-file AnalysisPass instances, CrossFilePass operates across
|
|
8
|
+
* the full set of files in a ProjectGraph and is invoked once after all
|
|
9
|
+
* per-file analyses are complete.
|
|
10
|
+
*
|
|
11
|
+
* Depends on: ProjectGraph (with all files registered)
|
|
12
|
+
*/
|
|
13
|
+
import type { CrossFileCall, TaintPath, TypeHierarchy } from '../../types/index.js';
|
|
14
|
+
import type { ProjectGraph } from '../../graph/project-graph.js';
|
|
15
|
+
export interface CrossFilePassResult {
|
|
16
|
+
/** Inter-file method calls (source file → target file). */
|
|
17
|
+
crossFileCalls: CrossFileCall[];
|
|
18
|
+
/** Taint paths that cross file boundaries. */
|
|
19
|
+
taintPaths: TaintPath[];
|
|
20
|
+
/** Type hierarchy across all files. */
|
|
21
|
+
typeHierarchy: TypeHierarchy;
|
|
22
|
+
}
|
|
23
|
+
export declare class CrossFilePass {
|
|
24
|
+
run(projectGraph: ProjectGraph,
|
|
25
|
+
/** Raw source lines per file (used to populate `code` fields in paths). */
|
|
26
|
+
sourceLines: Map<string, string[]>): CrossFilePassResult;
|
|
27
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossFilePass
|
|
3
|
+
*
|
|
4
|
+
* Project-level pass that uses the CrossFileResolver to surface taint flows
|
|
5
|
+
* that cross file boundaries and to map resolved inter-file calls.
|
|
6
|
+
*
|
|
7
|
+
* Unlike the single-file AnalysisPass instances, CrossFilePass operates across
|
|
8
|
+
* the full set of files in a ProjectGraph and is invoked once after all
|
|
9
|
+
* per-file analyses are complete.
|
|
10
|
+
*
|
|
11
|
+
* Depends on: ProjectGraph (with all files registered)
|
|
12
|
+
*/
|
|
13
|
+
export class CrossFilePass {
|
|
14
|
+
run(projectGraph,
|
|
15
|
+
/** Raw source lines per file (used to populate `code` fields in paths). */
|
|
16
|
+
sourceLines) {
|
|
17
|
+
const resolver = projectGraph.resolver;
|
|
18
|
+
// --- 1. Cross-file taint flows → TaintPath[] ----------------------------
|
|
19
|
+
const flows = resolver.findCrossFileTaintFlows();
|
|
20
|
+
const taintPaths = flows.flatMap((flow, idx) => {
|
|
21
|
+
const srcLines = sourceLines.get(flow.sourceFile) ?? [];
|
|
22
|
+
const tgtLines = sourceLines.get(flow.targetFile) ?? [];
|
|
23
|
+
// Look up matched sink from the target file's IR to get type + cwe.
|
|
24
|
+
// Skip flows where no known sink exists at the target line — we never
|
|
25
|
+
// default to 'sql_injection' because that produces massive false positives
|
|
26
|
+
// for any TypeScript project that uses string manipulation helpers.
|
|
27
|
+
const targetIR = projectGraph.getIR(flow.targetFile);
|
|
28
|
+
if (!targetIR || targetIR.taint.sinks.length === 0)
|
|
29
|
+
return [];
|
|
30
|
+
const matchedSink = targetIR.taint.sinks.find(s => s.line === flow.targetLine);
|
|
31
|
+
if (!matchedSink)
|
|
32
|
+
return [];
|
|
33
|
+
return [{
|
|
34
|
+
id: `cf-${idx}`,
|
|
35
|
+
source: {
|
|
36
|
+
file: flow.sourceFile,
|
|
37
|
+
line: flow.sourceLine,
|
|
38
|
+
type: flow.sourceType,
|
|
39
|
+
code: srcLines[flow.sourceLine - 1] ?? '',
|
|
40
|
+
},
|
|
41
|
+
sink: {
|
|
42
|
+
file: flow.targetFile,
|
|
43
|
+
line: flow.targetLine,
|
|
44
|
+
type: matchedSink.type,
|
|
45
|
+
cwe: matchedSink.cwe,
|
|
46
|
+
code: tgtLines[flow.targetLine - 1] ?? '',
|
|
47
|
+
},
|
|
48
|
+
hops: [
|
|
49
|
+
{
|
|
50
|
+
file: flow.sourceFile,
|
|
51
|
+
method: '',
|
|
52
|
+
line: flow.sourceLine,
|
|
53
|
+
code: srcLines[flow.sourceLine - 1] ?? '',
|
|
54
|
+
variable: '',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
file: flow.targetFile,
|
|
58
|
+
method: flow.targetMethod,
|
|
59
|
+
line: flow.targetLine,
|
|
60
|
+
code: tgtLines[flow.targetLine - 1] ?? '',
|
|
61
|
+
variable: '',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
sanitizers_in_path: [],
|
|
65
|
+
path_exists: true,
|
|
66
|
+
confidence: 0.7,
|
|
67
|
+
}];
|
|
68
|
+
});
|
|
69
|
+
// --- 2. Resolved inter-file calls → CrossFileCall[] --------------------
|
|
70
|
+
const crossFileCalls = [];
|
|
71
|
+
for (const filePath of projectGraph.filePaths) {
|
|
72
|
+
const resolved = resolver.getResolvedCallsFromFile(filePath);
|
|
73
|
+
for (const rc of resolved) {
|
|
74
|
+
if (rc.sourceFile === rc.targetFile)
|
|
75
|
+
continue; // same-file, skip
|
|
76
|
+
crossFileCalls.push({
|
|
77
|
+
id: `${rc.sourceFile}:${rc.call.location.line}:${rc.targetMethod}`,
|
|
78
|
+
from: {
|
|
79
|
+
file: rc.sourceFile,
|
|
80
|
+
method: rc.call.in_method ?? '',
|
|
81
|
+
line: rc.call.location.line,
|
|
82
|
+
},
|
|
83
|
+
to: {
|
|
84
|
+
file: rc.targetFile,
|
|
85
|
+
method: rc.targetMethod,
|
|
86
|
+
line: 0, // target line resolved via symbol table if needed
|
|
87
|
+
},
|
|
88
|
+
args_mapping: (rc.call.arguments ?? []).map((_, i) => ({
|
|
89
|
+
caller_arg: i,
|
|
90
|
+
callee_param: i,
|
|
91
|
+
taint_propagates: false,
|
|
92
|
+
})),
|
|
93
|
+
resolved: rc.resolution === 'exact',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// --- 3. Type hierarchy --------------------------------------------------
|
|
98
|
+
const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
|
|
99
|
+
return { crossFileCalls, taintPaths, typeHierarchy };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=cross-file-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-file-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/cross-file-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,MAAM,OAAO,aAAa;IACxB,GAAG,CACD,YAA0B;IAC1B,2EAA2E;IAC3E,WAAkC;QAElC,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QAEvC,2EAA2E;QAC3E,MAAM,KAAK,GAAG,QAAQ,CAAC,uBAAuB,EAAE,CAAC;QACjD,MAAM,UAAU,GAAgB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YAC1D,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YAExD,oEAAoE;YACpE,sEAAsE;YACtE,2EAA2E;YAC3E,oEAAoE;YACpE,MAAM,QAAQ,GAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAE9D,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/E,IAAI,CAAC,WAAW;gBAAE,OAAO,EAAE,CAAC;YAE5B,OAAO,CAAC;oBACN,EAAE,EAAE,MAAM,GAAG,EAAE;oBACf,MAAM,EAAE;wBACN,IAAI,EAAE,IAAI,CAAC,UAAU;wBACrB,IAAI,EAAE,IAAI,CAAC,UAAU;wBACrB,IAAI,EAAE,IAAI,CAAC,UAAwB;wBACnC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE;qBAC1C;oBACD,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,UAAU;wBACrB,IAAI,EAAE,IAAI,CAAC,UAAU;wBACrB,IAAI,EAAE,WAAW,CAAC,IAAgB;wBAClC,GAAG,EAAG,WAAW,CAAC,GAAG;wBACrB,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE;qBAC1C;oBACD,IAAI,EAAE;wBACJ;4BACE,IAAI,EAAM,IAAI,CAAC,UAAU;4BACzB,MAAM,EAAI,EAAE;4BACZ,IAAI,EAAM,IAAI,CAAC,UAAU;4BACzB,IAAI,EAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE;4BAC7C,QAAQ,EAAE,EAAE;yBACb;wBACD;4BACE,IAAI,EAAM,IAAI,CAAC,UAAU;4BACzB,MAAM,EAAI,IAAI,CAAC,YAAY;4BAC3B,IAAI,EAAM,IAAI,CAAC,UAAU;4BACzB,IAAI,EAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE;4BAC7C,QAAQ,EAAE,EAAE;yBACb;qBACF;oBACD,kBAAkB,EAAE,EAAE;oBACtB,WAAW,EAAE,IAAI;oBACjB,UAAU,EAAE,GAAG;iBAChB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,MAAM,cAAc,GAAoB,EAAE,CAAC;QAC3C,KAAK,MAAM,QAAQ,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;YAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC;YAC7D,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,IAAI,EAAE,CAAC,UAAU,KAAK,EAAE,CAAC,UAAU;oBAAE,SAAS,CAAC,kBAAkB;gBACjE,cAAc,CAAC,IAAI,CAAC;oBAClB,EAAE,EAAE,GAAG,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,YAAY,EAAE;oBAClE,IAAI,EAAE;wBACJ,IAAI,EAAI,EAAE,CAAC,UAAU;wBACrB,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE;wBAC/B,IAAI,EAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI;qBAC9B;oBACD,EAAE,EAAE;wBACF,IAAI,EAAI,EAAE,CAAC,UAAU;wBACrB,MAAM,EAAE,EAAE,CAAC,YAAY;wBACvB,IAAI,EAAI,CAAC,EAAG,kDAAkD;qBAC/D;oBACD,YAAY,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;wBACrD,UAAU,EAAQ,CAAC;wBACnB,YAAY,EAAM,CAAC;wBACnB,gBAAgB,EAAE,KAAK;qBACxB,CAAC,CAAC;oBACH,QAAQ,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO;iBACpC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,2EAA2E;QAC3E,MAAM,aAAa,GAAkB,YAAY,CAAC,aAAa,CAAC,mBAAmB,EAAE,CAAC;QAEtF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #22: dead-code (CWE-561, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects CFG blocks that are structurally unreachable from the entry block
|
|
5
|
+
* (i.e., no path of control-flow edges leads to them). This is pure CFG
|
|
6
|
+
* reachability — independent of constant-propagation or taint analysis.
|
|
7
|
+
*
|
|
8
|
+
* Examples: code after an unconditional `return`/`throw`, branches of
|
|
9
|
+
* `if (false)` where the condition is a literal (compiler-level dead code).
|
|
10
|
+
*
|
|
11
|
+
* Note: semantic dead code eliminated by constant propagation (e.g.,
|
|
12
|
+
* `if (DEBUG_MODE) { ... }` where DEBUG_MODE is a compile-time constant)
|
|
13
|
+
* is handled by ConstantPropagationPass, not this pass.
|
|
14
|
+
*/
|
|
15
|
+
import type { CFGBlock } from '../../types/index.js';
|
|
16
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
17
|
+
export interface DeadCodePassResult {
|
|
18
|
+
/** CFG blocks with no incoming reachable path from the entry block. */
|
|
19
|
+
deadBlocks: CFGBlock[];
|
|
20
|
+
}
|
|
21
|
+
export declare class DeadCodePass implements AnalysisPass<DeadCodePassResult> {
|
|
22
|
+
readonly name = "dead-code";
|
|
23
|
+
readonly category: "reliability";
|
|
24
|
+
run(ctx: PassContext): DeadCodePassResult;
|
|
25
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #22: dead-code (CWE-561, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects CFG blocks that are structurally unreachable from the entry block
|
|
5
|
+
* (i.e., no path of control-flow edges leads to them). This is pure CFG
|
|
6
|
+
* reachability — independent of constant-propagation or taint analysis.
|
|
7
|
+
*
|
|
8
|
+
* Examples: code after an unconditional `return`/`throw`, branches of
|
|
9
|
+
* `if (false)` where the condition is a literal (compiler-level dead code).
|
|
10
|
+
*
|
|
11
|
+
* Note: semantic dead code eliminated by constant propagation (e.g.,
|
|
12
|
+
* `if (DEBUG_MODE) { ... }` where DEBUG_MODE is a compile-time constant)
|
|
13
|
+
* is handled by ConstantPropagationPass, not this pass.
|
|
14
|
+
*/
|
|
15
|
+
export class DeadCodePass {
|
|
16
|
+
name = 'dead-code';
|
|
17
|
+
category = 'reliability';
|
|
18
|
+
run(ctx) {
|
|
19
|
+
const { graph, code } = ctx;
|
|
20
|
+
const { blocks, edges } = graph.ir.cfg;
|
|
21
|
+
const file = graph.ir.meta.file;
|
|
22
|
+
const language = graph.ir.meta.language ?? '';
|
|
23
|
+
if (blocks.length === 0)
|
|
24
|
+
return { deadBlocks: [] };
|
|
25
|
+
// Build outgoing adjacency: block id → reachable block ids
|
|
26
|
+
const outgoing = new Map();
|
|
27
|
+
for (const edge of edges) {
|
|
28
|
+
let list = outgoing.get(edge.from);
|
|
29
|
+
if (!list) {
|
|
30
|
+
list = [];
|
|
31
|
+
outgoing.set(edge.from, list);
|
|
32
|
+
}
|
|
33
|
+
list.push(edge.to);
|
|
34
|
+
}
|
|
35
|
+
// Find ALL root blocks: blocks with no incoming edges AND at least one
|
|
36
|
+
// outgoing edge. Each function body, arrow function, and class method
|
|
37
|
+
// creates its own disconnected sub-graph in the CFG (intra-procedural
|
|
38
|
+
// CFGs don't model call edges). These sub-graph roots have no incoming
|
|
39
|
+
// edges but DO have outgoing edges into their body blocks.
|
|
40
|
+
//
|
|
41
|
+
// A completely isolated block (no incoming, no outgoing) is the canonical
|
|
42
|
+
// shape of dead code after an unconditional return/throw — it is NOT
|
|
43
|
+
// treated as a root so it gets correctly reported.
|
|
44
|
+
const hasIncoming = new Set(edges.map(e => e.to));
|
|
45
|
+
const hasOutgoing = new Set(edges.map(e => e.from));
|
|
46
|
+
const roots = blocks.filter(b => !hasIncoming.has(b.id) && hasOutgoing.has(b.id));
|
|
47
|
+
// Always have at least one root: prefer type='entry', then any root,
|
|
48
|
+
// then the lowest-id block.
|
|
49
|
+
if (roots.length === 0) {
|
|
50
|
+
const fallback = blocks.find(b => b.type === 'entry') ??
|
|
51
|
+
blocks.find(b => !hasIncoming.has(b.id)) ??
|
|
52
|
+
blocks.reduce((a, b) => (a.id < b.id ? a : b));
|
|
53
|
+
roots.push(fallback);
|
|
54
|
+
}
|
|
55
|
+
// BFS from ALL roots to mark reachable block ids.
|
|
56
|
+
const reachable = new Set(roots.map(r => r.id));
|
|
57
|
+
const queue = roots.map(r => r.id);
|
|
58
|
+
while (queue.length > 0) {
|
|
59
|
+
const id = queue.shift();
|
|
60
|
+
for (const next of outgoing.get(id) ?? []) {
|
|
61
|
+
if (!reachable.has(next)) {
|
|
62
|
+
reachable.add(next);
|
|
63
|
+
queue.push(next);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Collect unreachable blocks that are worth reporting:
|
|
68
|
+
// - not the entry or exit sentinel blocks
|
|
69
|
+
// - have a positive start line (skip synthetic 0-line blocks)
|
|
70
|
+
const isJsTs = language === 'javascript' || language === 'typescript';
|
|
71
|
+
const codeLines = isJsTs ? code.split('\n') : [];
|
|
72
|
+
const deadBlocks = [];
|
|
73
|
+
for (const block of blocks) {
|
|
74
|
+
if (reachable.has(block.id))
|
|
75
|
+
continue;
|
|
76
|
+
if (block.type === 'entry' || block.type === 'exit')
|
|
77
|
+
continue;
|
|
78
|
+
if (block.start_line <= 0)
|
|
79
|
+
continue;
|
|
80
|
+
// In JS/TS, completely isolated blocks (no incoming AND no outgoing edges)
|
|
81
|
+
// are often arrow function expression bodies or simple function bodies — the
|
|
82
|
+
// intra-procedural CFG extractor gives them no edges. Suppress these to avoid
|
|
83
|
+
// false positives. Real dead code (post-return) always has a preceding block
|
|
84
|
+
// with an outgoing edge, so it is not affected by this check.
|
|
85
|
+
if (isJsTs && !hasIncoming.has(block.id) && !hasOutgoing.has(block.id)) {
|
|
86
|
+
const prevLine = codeLines[block.start_line - 2]?.trimEnd() ?? '';
|
|
87
|
+
const startLine = codeLines[block.start_line - 1]?.trimEnd() ?? '';
|
|
88
|
+
// Arrow function body: the line before OR the block's own first line
|
|
89
|
+
// contains '=>' (handles both multi-line and inline arrow functions).
|
|
90
|
+
// Regular function/method body: the preceding line ends with '{'.
|
|
91
|
+
if (prevLine.includes('=>') || prevLine.endsWith('{') ||
|
|
92
|
+
startLine.includes('=>'))
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
deadBlocks.push(block);
|
|
96
|
+
const loc = block.start_line === block.end_line
|
|
97
|
+
? `line ${block.start_line}`
|
|
98
|
+
: `lines ${block.start_line}–${block.end_line}`;
|
|
99
|
+
ctx.addFinding({
|
|
100
|
+
id: `dead-code-${file}-${block.start_line}`,
|
|
101
|
+
pass: this.name,
|
|
102
|
+
category: this.category,
|
|
103
|
+
rule_id: this.name,
|
|
104
|
+
cwe: 'CWE-561',
|
|
105
|
+
severity: 'low',
|
|
106
|
+
level: 'warning',
|
|
107
|
+
message: `Dead code at ${loc}: block is unreachable from any entry point`,
|
|
108
|
+
file,
|
|
109
|
+
line: block.start_line,
|
|
110
|
+
end_line: block.end_line > block.start_line ? block.end_line : undefined,
|
|
111
|
+
fix: 'Remove the unreachable block or fix the control flow that precedes it',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return { deadBlocks };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=dead-code-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-code-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/dead-code-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #29: deep-inheritance (CWE-1086, category: architecture)
|
|
3
|
+
*
|
|
4
|
+
* Detects class inheritance chains deeper than the configured threshold
|
|
5
|
+
* (default: 5). Deep inheritance hierarchies increase coupling, make code
|
|
6
|
+
* harder to understand, and indicate a design smell (violation of composition
|
|
7
|
+
* over inheritance).
|
|
8
|
+
*
|
|
9
|
+
* Detection strategy:
|
|
10
|
+
* 1. Build a parentMap: className → parentName from `ir.types[*].extends`.
|
|
11
|
+
* 2. For each class with a known `start_line`, walk up the parentMap
|
|
12
|
+
* counting depth. Stop at depth 20 to defend against cycles.
|
|
13
|
+
* 3. If depth > THRESHOLD, emit a finding at the class `start_line`.
|
|
14
|
+
*
|
|
15
|
+
* Languages: Java, JavaScript/TypeScript, Python. Rust/Bash do not have
|
|
16
|
+
* class inheritance — skipped.
|
|
17
|
+
*/
|
|
18
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
19
|
+
export interface DeepInheritanceResult {
|
|
20
|
+
deepClasses: Array<{
|
|
21
|
+
className: string;
|
|
22
|
+
depth: number;
|
|
23
|
+
line: number;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare class DeepInheritancePass implements AnalysisPass<DeepInheritanceResult> {
|
|
27
|
+
readonly name = "deep-inheritance";
|
|
28
|
+
readonly category: "architecture";
|
|
29
|
+
run(ctx: PassContext): DeepInheritanceResult;
|
|
30
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #29: deep-inheritance (CWE-1086, category: architecture)
|
|
3
|
+
*
|
|
4
|
+
* Detects class inheritance chains deeper than the configured threshold
|
|
5
|
+
* (default: 5). Deep inheritance hierarchies increase coupling, make code
|
|
6
|
+
* harder to understand, and indicate a design smell (violation of composition
|
|
7
|
+
* over inheritance).
|
|
8
|
+
*
|
|
9
|
+
* Detection strategy:
|
|
10
|
+
* 1. Build a parentMap: className → parentName from `ir.types[*].extends`.
|
|
11
|
+
* 2. For each class with a known `start_line`, walk up the parentMap
|
|
12
|
+
* counting depth. Stop at depth 20 to defend against cycles.
|
|
13
|
+
* 3. If depth > THRESHOLD, emit a finding at the class `start_line`.
|
|
14
|
+
*
|
|
15
|
+
* Languages: Java, JavaScript/TypeScript, Python. Rust/Bash do not have
|
|
16
|
+
* class inheritance — skipped.
|
|
17
|
+
*/
|
|
18
|
+
const DEPTH_THRESHOLD = 5;
|
|
19
|
+
const CYCLE_GUARD = 20;
|
|
20
|
+
export class DeepInheritancePass {
|
|
21
|
+
name = 'deep-inheritance';
|
|
22
|
+
category = 'architecture';
|
|
23
|
+
run(ctx) {
|
|
24
|
+
const { graph, language } = ctx;
|
|
25
|
+
if (language === 'rust' || language === 'bash') {
|
|
26
|
+
return { deepClasses: [] };
|
|
27
|
+
}
|
|
28
|
+
const file = graph.ir.meta.file;
|
|
29
|
+
const types = graph.ir.types;
|
|
30
|
+
if (types.length === 0)
|
|
31
|
+
return { deepClasses: [] };
|
|
32
|
+
// Build parentMap: class name → parent class name
|
|
33
|
+
const parentMap = new Map();
|
|
34
|
+
for (const typeInfo of types) {
|
|
35
|
+
if (typeInfo.extends) {
|
|
36
|
+
// Strip generic parameters (e.g., "BaseRepo<User>" → "BaseRepo")
|
|
37
|
+
const parentName = typeInfo.extends.replace(/<.*>/, '').trim();
|
|
38
|
+
if (parentName) {
|
|
39
|
+
parentMap.set(typeInfo.name, parentName);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const deepClasses = [];
|
|
44
|
+
for (const typeInfo of types) {
|
|
45
|
+
if (typeInfo.kind !== 'class')
|
|
46
|
+
continue;
|
|
47
|
+
if (typeInfo.start_line <= 0)
|
|
48
|
+
continue;
|
|
49
|
+
// Walk up the inheritance chain
|
|
50
|
+
let depth = 0;
|
|
51
|
+
let current = parentMap.get(typeInfo.name);
|
|
52
|
+
const visited = new Set([typeInfo.name]);
|
|
53
|
+
while (current !== undefined && depth < CYCLE_GUARD) {
|
|
54
|
+
depth++;
|
|
55
|
+
if (visited.has(current))
|
|
56
|
+
break; // cycle guard
|
|
57
|
+
visited.add(current);
|
|
58
|
+
current = parentMap.get(current);
|
|
59
|
+
}
|
|
60
|
+
if (depth > DEPTH_THRESHOLD) {
|
|
61
|
+
deepClasses.push({ className: typeInfo.name, depth, line: typeInfo.start_line });
|
|
62
|
+
ctx.addFinding({
|
|
63
|
+
id: `deep-inheritance-${file}-${typeInfo.start_line}`,
|
|
64
|
+
pass: this.name,
|
|
65
|
+
category: this.category,
|
|
66
|
+
rule_id: this.name,
|
|
67
|
+
cwe: 'CWE-1086',
|
|
68
|
+
severity: 'low',
|
|
69
|
+
level: 'warning',
|
|
70
|
+
message: `Deep inheritance: class \`${typeInfo.name}\` has inheritance depth ${depth} (threshold: ${DEPTH_THRESHOLD})`,
|
|
71
|
+
file,
|
|
72
|
+
line: typeInfo.start_line,
|
|
73
|
+
fix: `Refactor to prefer composition over inheritance. ` +
|
|
74
|
+
`Consider extracting shared behaviour into interfaces or mixins.`,
|
|
75
|
+
evidence: { depth, threshold: DEPTH_THRESHOLD },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { deepClasses };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=deep-inheritance-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-inheritance-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/deep-inheritance-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #72: dependency-fan-out
|
|
3
|
+
*
|
|
4
|
+
* Flags modules that import an excessive number of other modules (≥20).
|
|
5
|
+
* High fan-out is a coupling smell that makes modules hard to test and modify
|
|
6
|
+
* independently.
|
|
7
|
+
*
|
|
8
|
+
* Category: architecture | Severity: low | Level: note | CWE: none
|
|
9
|
+
*/
|
|
10
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
11
|
+
export interface DependencyFanOutResult {
|
|
12
|
+
importCount: number;
|
|
13
|
+
exceeded: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class DependencyFanOutPass implements AnalysisPass<DependencyFanOutResult> {
|
|
16
|
+
readonly name = "dependency-fan-out";
|
|
17
|
+
readonly category: "architecture";
|
|
18
|
+
run(ctx: PassContext): DependencyFanOutResult;
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #72: dependency-fan-out
|
|
3
|
+
*
|
|
4
|
+
* Flags modules that import an excessive number of other modules (≥20).
|
|
5
|
+
* High fan-out is a coupling smell that makes modules hard to test and modify
|
|
6
|
+
* independently.
|
|
7
|
+
*
|
|
8
|
+
* Category: architecture | Severity: low | Level: note | CWE: none
|
|
9
|
+
*/
|
|
10
|
+
const FAN_OUT_THRESHOLD = 20;
|
|
11
|
+
export class DependencyFanOutPass {
|
|
12
|
+
name = 'dependency-fan-out';
|
|
13
|
+
category = 'architecture';
|
|
14
|
+
run(ctx) {
|
|
15
|
+
const importCount = ctx.graph.ir.imports.length;
|
|
16
|
+
const exceeded = importCount >= FAN_OUT_THRESHOLD;
|
|
17
|
+
if (exceeded) {
|
|
18
|
+
const finding = {
|
|
19
|
+
id: `dependency-fan-out-${ctx.graph.ir.meta.file.replace(/[^a-z0-9]/gi, '-')}`,
|
|
20
|
+
pass: 'dependency-fan-out',
|
|
21
|
+
category: 'architecture',
|
|
22
|
+
rule_id: 'dependency-fan-out',
|
|
23
|
+
severity: 'low',
|
|
24
|
+
level: 'note',
|
|
25
|
+
message: `Module imports ${importCount} dependencies (threshold: ${FAN_OUT_THRESHOLD}). High fan-out increases coupling and makes the module harder to test.`,
|
|
26
|
+
file: ctx.graph.ir.meta.file,
|
|
27
|
+
line: 1,
|
|
28
|
+
evidence: { importCount, threshold: FAN_OUT_THRESHOLD },
|
|
29
|
+
};
|
|
30
|
+
ctx.addFinding(finding);
|
|
31
|
+
}
|
|
32
|
+
return { importCount, exceeded };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=dependency-fan-out-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-fan-out-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/dependency-fan-out-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,OAAO,oBAAoB;IACtB,IAAI,GAAG,oBAAoB,CAAC;IAC5B,QAAQ,GAAG,cAAuB,CAAC;IAE5C,GAAG,CAAC,GAAgB;QAClB,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;QAChD,MAAM,QAAQ,GAAM,WAAW,IAAI,iBAAiB,CAAC;QAErD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,OAAO,GAAgB;gBAC3B,EAAE,EAAQ,sBAAsB,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE;gBACpF,IAAI,EAAM,oBAAoB;gBAC9B,QAAQ,EAAE,cAAc;gBACxB,OAAO,EAAG,oBAAoB;gBAC9B,QAAQ,EAAE,KAAK;gBACf,KAAK,EAAK,MAAM;gBAChB,OAAO,EAAG,kBAAkB,WAAW,6BAA6B,iBAAiB,yEAAyE;gBAC9J,IAAI,EAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI;gBAChC,IAAI,EAAM,CAAC;gBACX,QAAQ,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE;aACxD,CAAC;YACF,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QAED,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #28: infinite-loop (CWE-835, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects loops with no reachable exit edge — i.e., loops that can run
|
|
5
|
+
* forever because every execution path through the loop body leads back to
|
|
6
|
+
* the loop header without a break, return, throw, or continue-to-outer.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Identify loop headers: back-edge targets in the CFG (`edge.type === 'back'`).
|
|
10
|
+
* 2. For each loop, collect the loop body blocks via BFS from the header,
|
|
11
|
+
* stopping at back-edge sources (the "tail" blocks).
|
|
12
|
+
* 3. Check whether any block in the body has an outgoing edge that exits
|
|
13
|
+
* the loop (target not in body set and not back to header).
|
|
14
|
+
* 4. As a text-level fallback, scan source lines in the loop body for
|
|
15
|
+
* `return`, `throw`/`raise`, `break`, `System.exit` keywords.
|
|
16
|
+
* 5. Emit a finding at the loop header's start_line if no exit is found.
|
|
17
|
+
*
|
|
18
|
+
* Languages: Java, JavaScript, TypeScript, Python, Rust. Skip Bash.
|
|
19
|
+
*/
|
|
20
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
21
|
+
export interface InfiniteLoopResult {
|
|
22
|
+
potentialInfiniteLoops: Array<{
|
|
23
|
+
headerLine: number;
|
|
24
|
+
bodyEndLine: number;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare class InfiniteLoopPass implements AnalysisPass<InfiniteLoopResult> {
|
|
28
|
+
readonly name = "infinite-loop";
|
|
29
|
+
readonly category: "reliability";
|
|
30
|
+
run(ctx: PassContext): InfiniteLoopResult;
|
|
31
|
+
}
|