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,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #82: unused-variable (CWE-561, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects local variables that are declared but whose value is never read.
|
|
5
|
+
* This includes variables whose value is overwritten before any read
|
|
6
|
+
* (the initial assignment is "dead" from a data-flow perspective).
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. For each `kind='local'` DFG def:
|
|
10
|
+
* - Skip intentional throwaway names (`_`, `err`, `e`, loop variables…).
|
|
11
|
+
* - Skip variables in `catch` blocks (common pattern to capture but ignore
|
|
12
|
+
* exceptions: `catch (err) { ... }`).
|
|
13
|
+
* - Call `graph.usesOfDef(def.id)` — returns uses with `def_id === defId`.
|
|
14
|
+
* - If the result is empty, no code ever reads the value stored by this
|
|
15
|
+
* definition → flag as unused.
|
|
16
|
+
*
|
|
17
|
+
* Notes:
|
|
18
|
+
* - Test files are excluded to reduce noise (test helpers often define
|
|
19
|
+
* variables for side-effect checks).
|
|
20
|
+
* - Parameters (`kind='param'`) are excluded — unused parameters are common
|
|
21
|
+
* in callbacks and overriding methods and produce too many false positives.
|
|
22
|
+
* - Fields (`kind='field'`) are excluded — class fields are often read via
|
|
23
|
+
* `this.x` in ways the DFG may not track precisely.
|
|
24
|
+
*/
|
|
25
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
26
|
+
export interface UnusedVariableResult {
|
|
27
|
+
unusedVars: Array<{
|
|
28
|
+
line: number;
|
|
29
|
+
variable: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare class UnusedVariablePass implements AnalysisPass<UnusedVariableResult> {
|
|
33
|
+
readonly name = "unused-variable";
|
|
34
|
+
readonly category: "reliability";
|
|
35
|
+
run(ctx: PassContext): UnusedVariableResult;
|
|
36
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #82: unused-variable (CWE-561, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects local variables that are declared but whose value is never read.
|
|
5
|
+
* This includes variables whose value is overwritten before any read
|
|
6
|
+
* (the initial assignment is "dead" from a data-flow perspective).
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. For each `kind='local'` DFG def:
|
|
10
|
+
* - Skip intentional throwaway names (`_`, `err`, `e`, loop variables…).
|
|
11
|
+
* - Skip variables in `catch` blocks (common pattern to capture but ignore
|
|
12
|
+
* exceptions: `catch (err) { ... }`).
|
|
13
|
+
* - Call `graph.usesOfDef(def.id)` — returns uses with `def_id === defId`.
|
|
14
|
+
* - If the result is empty, no code ever reads the value stored by this
|
|
15
|
+
* definition → flag as unused.
|
|
16
|
+
*
|
|
17
|
+
* Notes:
|
|
18
|
+
* - Test files are excluded to reduce noise (test helpers often define
|
|
19
|
+
* variables for side-effect checks).
|
|
20
|
+
* - Parameters (`kind='param'`) are excluded — unused parameters are common
|
|
21
|
+
* in callbacks and overriding methods and produce too many false positives.
|
|
22
|
+
* - Fields (`kind='field'`) are excluded — class fields are often read via
|
|
23
|
+
* `this.x` in ways the DFG may not track precisely.
|
|
24
|
+
*/
|
|
25
|
+
/** Variable names that are intentionally declared-but-not-read. */
|
|
26
|
+
const SKIP_NAMES = new Set([
|
|
27
|
+
'_', 'unused',
|
|
28
|
+
'e', 'err', 'error', 'ex', 'exception',
|
|
29
|
+
'i', 'j', 'k', 'n', 'idx', 'index',
|
|
30
|
+
// TypeScript/JavaScript built-in type keywords that the DFG extractor may
|
|
31
|
+
// accidentally surface as phantom variable defs from type annotations.
|
|
32
|
+
'boolean', 'string', 'number', 'object', 'symbol', 'undefined',
|
|
33
|
+
'null', 'never', 'void', 'any', 'unknown', 'bigint',
|
|
34
|
+
]);
|
|
35
|
+
export class UnusedVariablePass {
|
|
36
|
+
name = 'unused-variable';
|
|
37
|
+
category = 'reliability';
|
|
38
|
+
run(ctx) {
|
|
39
|
+
const { graph, code } = ctx;
|
|
40
|
+
const file = graph.ir.meta.file;
|
|
41
|
+
// Skip test files — test scaffolding often has unused vars intentionally
|
|
42
|
+
if (/[./](?:test|spec)[./]/.test(file) || /\.(?:test|spec)\.[jt]s$/.test(file)) {
|
|
43
|
+
return { unusedVars: [] };
|
|
44
|
+
}
|
|
45
|
+
const codeLines = code.split('\n');
|
|
46
|
+
const unusedVars = [];
|
|
47
|
+
const reported = new Set(); // deduplicate by variable+line
|
|
48
|
+
for (const def of graph.ir.dfg.defs) {
|
|
49
|
+
if (def.kind !== 'local')
|
|
50
|
+
continue;
|
|
51
|
+
const variable = def.variable;
|
|
52
|
+
// Skip intentional throwaway / loop variable names
|
|
53
|
+
if (variable.startsWith('_'))
|
|
54
|
+
continue;
|
|
55
|
+
if (SKIP_NAMES.has(variable))
|
|
56
|
+
continue;
|
|
57
|
+
// Skip catch-block variables (e.g. `catch (err)`)
|
|
58
|
+
const lineText = codeLines[def.line - 1] ?? '';
|
|
59
|
+
if (/\bcatch\s*\(/.test(lineText))
|
|
60
|
+
continue;
|
|
61
|
+
// Skip exported symbols — they are consumed by other modules and
|
|
62
|
+
// single-file DFG analysis cannot see cross-file uses.
|
|
63
|
+
if (/\bexport\b/.test(lineText))
|
|
64
|
+
continue;
|
|
65
|
+
// No uses of this specific definition → unused (per DFG)
|
|
66
|
+
const uses = graph.usesOfDef(def.id);
|
|
67
|
+
if (uses.length > 0)
|
|
68
|
+
continue;
|
|
69
|
+
// Text-search fallback: covers cross-scope uses that the DFG misses (e.g.
|
|
70
|
+
// module-level constants referenced inside a class method body, or
|
|
71
|
+
// conditional reassignments where the DFG links the final read only to
|
|
72
|
+
// the last def so earlier defs appear unused).
|
|
73
|
+
const otherDefs = graph.ir.dfg.defs.filter(d => d.variable === variable && d.id !== def.id);
|
|
74
|
+
const otherDefLines = new Set(otherDefs.map(d => d.line));
|
|
75
|
+
const escapedName = variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
76
|
+
const namePattern = new RegExp(`\\b${escapedName}\\b`);
|
|
77
|
+
if (otherDefLines.size === 0) {
|
|
78
|
+
// Single def — simple text search: any occurrence on another line suppresses.
|
|
79
|
+
const usedElsewhere = codeLines.some((line, idx) => idx !== def.line - 1 && namePattern.test(line));
|
|
80
|
+
if (usedElsewhere)
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Multiple defs exist.
|
|
85
|
+
//
|
|
86
|
+
// Case A — current def IS a true declaration (let/const/var, Java type, Rust let):
|
|
87
|
+
// Only suppress if a non-def use is found that precedes ALL intermediate defs.
|
|
88
|
+
// This preserves "overwrite-before-read" detection: if a later def appears
|
|
89
|
+
// between this def and the text-found use, the value was overwritten and the
|
|
90
|
+
// use belongs to the later def.
|
|
91
|
+
//
|
|
92
|
+
// Case B — current def is a bare reassignment (part of a conditional pattern):
|
|
93
|
+
// Suppress if the variable appears anywhere on a non-def line. This handles
|
|
94
|
+
// sibling-if-block assignments (`if (x) { fw = 'a'; } if (y) { fw = 'b'; }
|
|
95
|
+
// return fw;`) where the DFG links the return only to the last branch def.
|
|
96
|
+
const lineText = codeLines[def.line - 1] ?? '';
|
|
97
|
+
const isTrueDeclaration = /\b(?:let|const|var)\s+[\w{[]/.test(lineText)
|
|
98
|
+
|| /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText)
|
|
99
|
+
|| /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText)
|
|
100
|
+
|| /\blet\s+(?:mut\s+)?\w/.test(lineText);
|
|
101
|
+
if (isTrueDeclaration) {
|
|
102
|
+
// Case A: suppress only if use appears before any intermediate def.
|
|
103
|
+
const usedBeforeNextDef = codeLines.some((line, idx) => {
|
|
104
|
+
const lineNum = idx + 1;
|
|
105
|
+
if (lineNum === def.line || otherDefLines.has(lineNum))
|
|
106
|
+
return false;
|
|
107
|
+
if (!namePattern.test(line))
|
|
108
|
+
return false;
|
|
109
|
+
// If any other def for this variable sits between our def and this use,
|
|
110
|
+
// the value was overwritten → this use belongs to the later def → don't suppress.
|
|
111
|
+
return !otherDefs.some(d => d.line > def.line && d.line < lineNum);
|
|
112
|
+
});
|
|
113
|
+
if (usedBeforeNextDef)
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Case B: bare reassignment — suppress if used on any non-def line.
|
|
118
|
+
const usedOnNonDefLine = codeLines.some((line, idx) => {
|
|
119
|
+
const lineNum = idx + 1;
|
|
120
|
+
return lineNum !== def.line && !otherDefLines.has(lineNum) && namePattern.test(line);
|
|
121
|
+
});
|
|
122
|
+
if (usedOnNonDefLine)
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const key = `${variable}-${def.line}`;
|
|
127
|
+
if (reported.has(key))
|
|
128
|
+
continue;
|
|
129
|
+
reported.add(key);
|
|
130
|
+
unusedVars.push({ line: def.line, variable });
|
|
131
|
+
ctx.addFinding({
|
|
132
|
+
id: `unused-variable-${file}-${def.line}`,
|
|
133
|
+
pass: this.name,
|
|
134
|
+
category: this.category,
|
|
135
|
+
rule_id: this.name,
|
|
136
|
+
cwe: 'CWE-561',
|
|
137
|
+
severity: 'low',
|
|
138
|
+
level: 'note',
|
|
139
|
+
message: `'${variable}' is assigned but its value is never read`,
|
|
140
|
+
file,
|
|
141
|
+
line: def.line,
|
|
142
|
+
snippet: lineText.trim(),
|
|
143
|
+
fix: `Remove the assignment or use the value of '${variable}'`,
|
|
144
|
+
evidence: { variable },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return { unusedVars };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=unused-variable-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unused-variable-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/unused-variable-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #79: variable-shadowing (CWE-1109, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects when an inner scope declares a variable with the same name as an
|
|
5
|
+
* outer-scope declaration or function parameter, hiding the outer binding and
|
|
6
|
+
* making code harder to reason about.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Build a ScopeGraph to identify which defs are true declarations vs
|
|
10
|
+
* bare reassignments.
|
|
11
|
+
* 2. For each method, group DFG defs by variable name.
|
|
12
|
+
* 3. Flag two kinds of shadowing within the same method:
|
|
13
|
+
* - Param shadow : a `kind='param'` def + a later `kind='local'` def
|
|
14
|
+
* that is a real declaration (has a decl keyword, or Python which has
|
|
15
|
+
* no keywords but every local assignment implicitly shadows a param).
|
|
16
|
+
* - Outer-local shadow : two or more `kind='local'` defs that both have
|
|
17
|
+
* a declaration keyword (e.g. `let x = 1` then `let x = 2` in a
|
|
18
|
+
* nested block).
|
|
19
|
+
*
|
|
20
|
+
* Note on Python: Python variables have function scope (not block scope), so
|
|
21
|
+
* two assignments to the same name within a function do NOT shadow each other.
|
|
22
|
+
* However, a local assignment that shares a name with a parameter DOES shadow
|
|
23
|
+
* the parameter (from the assignment point onward). The pass flags that case
|
|
24
|
+
* for Python regardless of `hasDeclKeyword` (since Python has no decl keywords).
|
|
25
|
+
*/
|
|
26
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
27
|
+
export interface VariableShadowingResult {
|
|
28
|
+
shadows: Array<{
|
|
29
|
+
/** Line of the shadowing (inner) declaration. */
|
|
30
|
+
line: number;
|
|
31
|
+
variable: string;
|
|
32
|
+
/** Line of the shadowed (outer) declaration or parameter. */
|
|
33
|
+
shadowedAt: number;
|
|
34
|
+
kind: 'param' | 'outer-local';
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export declare class VariableShadowingPass implements AnalysisPass<VariableShadowingResult> {
|
|
38
|
+
readonly name = "variable-shadowing";
|
|
39
|
+
readonly category: "reliability";
|
|
40
|
+
run(ctx: PassContext): VariableShadowingResult;
|
|
41
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #79: variable-shadowing (CWE-1109, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects when an inner scope declares a variable with the same name as an
|
|
5
|
+
* outer-scope declaration or function parameter, hiding the outer binding and
|
|
6
|
+
* making code harder to reason about.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Build a ScopeGraph to identify which defs are true declarations vs
|
|
10
|
+
* bare reassignments.
|
|
11
|
+
* 2. For each method, group DFG defs by variable name.
|
|
12
|
+
* 3. Flag two kinds of shadowing within the same method:
|
|
13
|
+
* - Param shadow : a `kind='param'` def + a later `kind='local'` def
|
|
14
|
+
* that is a real declaration (has a decl keyword, or Python which has
|
|
15
|
+
* no keywords but every local assignment implicitly shadows a param).
|
|
16
|
+
* - Outer-local shadow : two or more `kind='local'` defs that both have
|
|
17
|
+
* a declaration keyword (e.g. `let x = 1` then `let x = 2` in a
|
|
18
|
+
* nested block).
|
|
19
|
+
*
|
|
20
|
+
* Note on Python: Python variables have function scope (not block scope), so
|
|
21
|
+
* two assignments to the same name within a function do NOT shadow each other.
|
|
22
|
+
* However, a local assignment that shares a name with a parameter DOES shadow
|
|
23
|
+
* the parameter (from the assignment point onward). The pass flags that case
|
|
24
|
+
* for Python regardless of `hasDeclKeyword` (since Python has no decl keywords).
|
|
25
|
+
*/
|
|
26
|
+
import { ScopeGraph } from '../../graph/scope-graph.js';
|
|
27
|
+
/**
|
|
28
|
+
* Variable names that should never be flagged as shadowing — either because
|
|
29
|
+
* they are JS/TS keywords that the DFG extractor may phantom-extract, or
|
|
30
|
+
* because they are built-in TypeScript primitive type names that appear in
|
|
31
|
+
* type annotations and get incorrectly treated as variable defs.
|
|
32
|
+
*/
|
|
33
|
+
const SKIP_NAMES = new Set([
|
|
34
|
+
// JS/TS declaration keywords (phantom defs from DFG parsing keywords as vars)
|
|
35
|
+
'let', 'const', 'var',
|
|
36
|
+
// TypeScript primitive type names (phantom defs from type annotations)
|
|
37
|
+
'boolean', 'string', 'number', 'object', 'symbol', 'undefined',
|
|
38
|
+
'null', 'never', 'void', 'any', 'unknown', 'bigint',
|
|
39
|
+
]);
|
|
40
|
+
/**
|
|
41
|
+
* Returns true when `innerLine` is inside a block that is nested within the
|
|
42
|
+
* block containing `outerLine`. Returns false when the outer block was closed
|
|
43
|
+
* before `innerLine` (i.e. they are in sibling scopes, not nested scopes).
|
|
44
|
+
*
|
|
45
|
+
* Strategy: scan lines from `outerLine` to `innerLine - 1` (1-based) counting
|
|
46
|
+
* brace pairs. A relative balance below zero means the outer block was closed —
|
|
47
|
+
* the two declarations are siblings, not a real shadowing relationship.
|
|
48
|
+
*
|
|
49
|
+
* Note: ignores braces inside string literals or comments, which may produce
|
|
50
|
+
* occasional false negatives (missed shadows) but never false positives.
|
|
51
|
+
*/
|
|
52
|
+
function isInNestedScope(codeLines, outerLine, innerLine) {
|
|
53
|
+
let balance = 0;
|
|
54
|
+
let hasOpened = false; // true once we've seen the outer block's opening {
|
|
55
|
+
for (let ln = outerLine; ln < innerLine; ln++) {
|
|
56
|
+
const text = codeLines[ln - 1] ?? ''; // ln is 1-based
|
|
57
|
+
for (const ch of text) {
|
|
58
|
+
if (ch === '{') {
|
|
59
|
+
balance++;
|
|
60
|
+
hasOpened = true;
|
|
61
|
+
}
|
|
62
|
+
else if (ch === '}') {
|
|
63
|
+
balance--;
|
|
64
|
+
if (balance < 0)
|
|
65
|
+
return false; // outer block closed before opening — sibling
|
|
66
|
+
// If the outer block opened and has now fully closed, the inner def is
|
|
67
|
+
// outside it (sibling scope, not nested).
|
|
68
|
+
if (hasOpened && balance === 0)
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
export class VariableShadowingPass {
|
|
76
|
+
name = 'variable-shadowing';
|
|
77
|
+
category = 'reliability';
|
|
78
|
+
run(ctx) {
|
|
79
|
+
const { graph, code, language } = ctx;
|
|
80
|
+
const file = graph.ir.meta.file;
|
|
81
|
+
const codeLines = code.split('\n');
|
|
82
|
+
const scope = new ScopeGraph(graph, code, language);
|
|
83
|
+
const shadows = [];
|
|
84
|
+
const reported = new Set(); // deduplicate by variable+line
|
|
85
|
+
for (const type of graph.ir.types) {
|
|
86
|
+
for (const method of type.methods) {
|
|
87
|
+
const entries = scope.defsInMethod(method.start_line, method.end_line);
|
|
88
|
+
// Group entries by variable name
|
|
89
|
+
const byVar = new Map();
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const existing = byVar.get(entry.def.variable);
|
|
92
|
+
if (existing) {
|
|
93
|
+
existing.push(entry);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
byVar.set(entry.def.variable, [entry]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const [variable, varEntries] of byVar) {
|
|
100
|
+
if (varEntries.length < 2)
|
|
101
|
+
continue;
|
|
102
|
+
// Skip keywords, TS primitive types, and common throwaway names
|
|
103
|
+
if (SKIP_NAMES.has(variable))
|
|
104
|
+
continue;
|
|
105
|
+
// Skip PascalCase identifiers — these are almost always type annotation
|
|
106
|
+
// phantoms (class names, interface names, generic type params) that the
|
|
107
|
+
// DFG extractor incorrectly surfaces as variable defs.
|
|
108
|
+
if (variable.length > 0 && variable[0] >= 'A' && variable[0] <= 'Z')
|
|
109
|
+
continue;
|
|
110
|
+
const params = varEntries.filter(e => e.def.kind === 'param');
|
|
111
|
+
const locals = varEntries.filter(e => e.def.kind === 'local');
|
|
112
|
+
// -------------------------------------------------------
|
|
113
|
+
// Case 1: Param shadowed by a local declaration
|
|
114
|
+
// -------------------------------------------------------
|
|
115
|
+
if (params.length > 0 && locals.length > 0) {
|
|
116
|
+
const paramEntry = params[0];
|
|
117
|
+
for (const local of locals) {
|
|
118
|
+
// For Python: every assignment to a param name shadows it.
|
|
119
|
+
// For other languages: only flag if the line is a real declaration.
|
|
120
|
+
if (language !== 'python' && !local.hasDeclKeyword)
|
|
121
|
+
continue;
|
|
122
|
+
if (local.def.line <= paramEntry.def.line)
|
|
123
|
+
continue;
|
|
124
|
+
const key = `${variable}-${local.def.line}`;
|
|
125
|
+
if (reported.has(key))
|
|
126
|
+
continue;
|
|
127
|
+
reported.add(key);
|
|
128
|
+
shadows.push({
|
|
129
|
+
line: local.def.line,
|
|
130
|
+
variable,
|
|
131
|
+
shadowedAt: paramEntry.def.line,
|
|
132
|
+
kind: 'param',
|
|
133
|
+
});
|
|
134
|
+
ctx.addFinding({
|
|
135
|
+
id: `variable-shadowing-${file}-${local.def.line}`,
|
|
136
|
+
pass: this.name,
|
|
137
|
+
category: this.category,
|
|
138
|
+
rule_id: this.name,
|
|
139
|
+
cwe: 'CWE-1109',
|
|
140
|
+
severity: 'medium',
|
|
141
|
+
level: 'warning',
|
|
142
|
+
message: `'${variable}' shadows the parameter declared at line ${paramEntry.def.line}`,
|
|
143
|
+
file,
|
|
144
|
+
line: local.def.line,
|
|
145
|
+
fix: `Rename the inner variable to avoid hiding the parameter '${variable}'`,
|
|
146
|
+
evidence: {
|
|
147
|
+
variable,
|
|
148
|
+
outer_kind: 'param',
|
|
149
|
+
outer_line: paramEntry.def.line,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
continue; // skip outer-local check when params are involved
|
|
154
|
+
}
|
|
155
|
+
// -------------------------------------------------------
|
|
156
|
+
// Case 2: Outer local shadowed by an inner local declaration
|
|
157
|
+
// -------------------------------------------------------
|
|
158
|
+
if (locals.length >= 2) {
|
|
159
|
+
// Python has no decl keywords → skip outer-local shadow for Python
|
|
160
|
+
if (language === 'python')
|
|
161
|
+
continue;
|
|
162
|
+
// Only consider entries that are true declarations
|
|
163
|
+
const declLocals = locals
|
|
164
|
+
.filter(e => e.hasDeclKeyword)
|
|
165
|
+
.sort((a, b) => a.def.line - b.def.line);
|
|
166
|
+
if (declLocals.length < 2)
|
|
167
|
+
continue;
|
|
168
|
+
const outerEntry = declLocals[0];
|
|
169
|
+
for (let i = 1; i < declLocals.length; i++) {
|
|
170
|
+
const inner = declLocals[i];
|
|
171
|
+
// Skip if the outer block was already closed before the inner
|
|
172
|
+
// declaration — those are sibling scopes, not nested scopes.
|
|
173
|
+
if (!isInNestedScope(codeLines, outerEntry.def.line, inner.def.line))
|
|
174
|
+
continue;
|
|
175
|
+
const key = `${variable}-${inner.def.line}`;
|
|
176
|
+
if (reported.has(key))
|
|
177
|
+
continue;
|
|
178
|
+
reported.add(key);
|
|
179
|
+
shadows.push({
|
|
180
|
+
line: inner.def.line,
|
|
181
|
+
variable,
|
|
182
|
+
shadowedAt: outerEntry.def.line,
|
|
183
|
+
kind: 'outer-local',
|
|
184
|
+
});
|
|
185
|
+
ctx.addFinding({
|
|
186
|
+
id: `variable-shadowing-${file}-${inner.def.line}`,
|
|
187
|
+
pass: this.name,
|
|
188
|
+
category: this.category,
|
|
189
|
+
rule_id: this.name,
|
|
190
|
+
cwe: 'CWE-1109',
|
|
191
|
+
severity: 'medium',
|
|
192
|
+
level: 'warning',
|
|
193
|
+
message: `'${variable}' shadows the outer declaration at line ${outerEntry.def.line}`,
|
|
194
|
+
file,
|
|
195
|
+
line: inner.def.line,
|
|
196
|
+
fix: `Rename the inner variable to avoid hiding the outer '${variable}'`,
|
|
197
|
+
evidence: {
|
|
198
|
+
variable,
|
|
199
|
+
outer_kind: 'local',
|
|
200
|
+
outer_line: outerEntry.def.line,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { shadows };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=variable-shadowing-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variable-shadowing-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/variable-shadowing-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,EAAE,KAAK;IACrB,uEAAuE;IACvE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW;IAC9D,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ;CACpD,CAAC,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,SAAS,eAAe,CACtB,SAAmB,EACnB,SAAiB,EACjB,SAAiB;IAEjB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,SAAS,GAAG,KAAK,CAAC,CAAC,mDAAmD;IAC1E,KAAK,IAAI,EAAE,GAAG,SAAS,EAAE,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,gBAAgB;QACtD,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;gBACV,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACtB,OAAO,EAAE,CAAC;gBACV,IAAI,OAAO,GAAG,CAAC;oBAAE,OAAO,KAAK,CAAC,CAAC,8CAA8C;gBAC7E,uEAAuE;gBACvE,0CAA0C;gBAC1C,IAAI,SAAS,IAAI,OAAO,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAaD,MAAM,OAAO,qBAAqB;IACvB,IAAI,GAAG,oBAAoB,CAAC;IAC5B,QAAQ,GAAG,aAAsB,CAAC;IAE3C,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,MAAM,OAAO,GAAuC,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC,CAAC,+BAA+B;QAEnE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAClC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAEvE,iCAAiC;gBACjC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;gBAChD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;oBAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oBAC/C,IAAI,QAAQ,EAAE,CAAC;wBACb,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACvB,CAAC;yBAAM,CAAC;wBACN,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;oBACzC,CAAC;gBACH,CAAC;gBAED,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,KAAK,EAAE,CAAC;oBAC3C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;wBAAE,SAAS;oBAEpC,gEAAgE;oBAChE,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;wBAAE,SAAS;oBAEvC,wEAAwE;oBACxE,wEAAwE;oBACxE,uDAAuD;oBACvD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAE,IAAI,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAE,IAAI,GAAG;wBAAE,SAAS;oBAEhF,MAAM,MAAM,GAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;oBAC/D,MAAM,MAAM,GAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;oBAE/D,0DAA0D;oBAC1D,gDAAgD;oBAChD,0DAA0D;oBAC1D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;wBAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;4BAC3B,2DAA2D;4BAC3D,oEAAoE;4BACpE,IAAI,QAAQ,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,cAAc;gCAAE,SAAS;4BAC7D,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI;gCAAE,SAAS;4BAEpD,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;4BAC5C,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gCAAE,SAAS;4BAChC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;4BAElB,OAAO,CAAC,IAAI,CAAC;gCACX,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI;gCACpB,QAAQ;gCACR,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI;gCAC/B,IAAI,EAAE,OAAO;6BACd,CAAC,CAAC;4BAEH,GAAG,CAAC,UAAU,CAAC;gCACb,EAAE,EAAE,sBAAsB,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;gCAClD,IAAI,EAAE,IAAI,CAAC,IAAI;gCACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gCACvB,OAAO,EAAE,IAAI,CAAC,IAAI;gCAClB,GAAG,EAAE,UAAU;gCACf,QAAQ,EAAE,QAAQ;gCAClB,KAAK,EAAE,SAAS;gCAChB,OAAO,EACL,IAAI,QAAQ,4CAA4C,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE;gCAC/E,IAAI;gCACJ,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI;gCACpB,GAAG,EAAE,4DAA4D,QAAQ,GAAG;gCAC5E,QAAQ,EAAE;oCACR,QAAQ;oCACR,UAAU,EAAE,OAAO;oCACnB,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI;iCAChC;6BACF,CAAC,CAAC;wBACL,CAAC;wBACD,SAAS,CAAC,kDAAkD;oBAC9D,CAAC;oBAED,0DAA0D;oBAC1D,6DAA6D;oBAC7D,0DAA0D;oBAC1D,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;wBACvB,mEAAmE;wBACnE,IAAI,QAAQ,KAAK,QAAQ;4BAAE,SAAS;wBAEpC,mDAAmD;wBACnD,MAAM,UAAU,GAAG,MAAM;6BACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;6BAC7B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAE3C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;4BAAE,SAAS;wBAEpC,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;wBAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;4BAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;4BAC7B,8DAA8D;4BAC9D,6DAA6D;4BAC7D,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gCAAE,SAAS;4BAC/E,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;4BAC5C,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gCAAE,SAAS;4BAChC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;4BAElB,OAAO,CAAC,IAAI,CAAC;gCACX,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI;gCACpB,QAAQ;gCACR,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI;gCAC/B,IAAI,EAAE,aAAa;6BACpB,CAAC,CAAC;4BAEH,GAAG,CAAC,UAAU,CAAC;gCACb,EAAE,EAAE,sBAAsB,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;gCAClD,IAAI,EAAE,IAAI,CAAC,IAAI;gCACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gCACvB,OAAO,EAAE,IAAI,CAAC,IAAI;gCAClB,GAAG,EAAE,UAAU;gCACf,QAAQ,EAAE,QAAQ;gCAClB,KAAK,EAAE,SAAS;gCAChB,OAAO,EACL,IAAI,QAAQ,2CAA2C,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE;gCAC9E,IAAI;gCACJ,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI;gCACpB,GAAG,EAAE,wDAAwD,QAAQ,GAAG;gCACxE,QAAQ,EAAE;oCACR,QAAQ;oCACR,UAAU,EAAE,OAAO;oCACnB,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI;iCAChC;6BACF,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* through variable assignments, method calls, and returns.
|
|
6
6
|
*/
|
|
7
7
|
import type { DFG, CallInfo, TaintSource, TaintSink, TaintSanitizer, SourceType, SinkType } from '../types/index.js';
|
|
8
|
+
import { CodeGraph } from '../graph/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* A single hop in the taint path
|
|
10
11
|
*/
|
|
@@ -68,24 +69,13 @@ export interface PathFinderConfig {
|
|
|
68
69
|
* PathFinder - Enumerate taint paths through the DFG
|
|
69
70
|
*/
|
|
70
71
|
export declare class PathFinder {
|
|
71
|
-
private
|
|
72
|
-
private calls;
|
|
72
|
+
private graph;
|
|
73
73
|
private sources;
|
|
74
74
|
private sinks;
|
|
75
75
|
private sanitizers;
|
|
76
76
|
private config;
|
|
77
|
-
private defById;
|
|
78
|
-
private defsByLine;
|
|
79
|
-
private defsByVar;
|
|
80
|
-
private usesByLine;
|
|
81
|
-
private usesByDefId;
|
|
82
|
-
private callsByLine;
|
|
83
77
|
private sanitizerLines;
|
|
84
|
-
constructor(
|
|
85
|
-
/**
|
|
86
|
-
* Build all lookup maps for efficient querying
|
|
87
|
-
*/
|
|
88
|
-
private buildLookupMaps;
|
|
78
|
+
constructor(graphOrDfg: CodeGraph | DFG, callsOrSources: CallInfo[] | TaintSource[], sourcesOrSinks: TaintSource[] | TaintSink[], sinksOrSanitizers: TaintSink[] | TaintSanitizer[], sanitizersOrConfig?: TaintSanitizer[] | PathFinderConfig, config?: PathFinderConfig);
|
|
89
79
|
/**
|
|
90
80
|
* Find all taint paths from sources to sinks
|
|
91
81
|
*/
|