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.
Files changed (159) hide show
  1. package/README.md +82 -5
  2. package/dist/analysis/config-loader.js +1 -1
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/dfg-verifier.d.ts +3 -14
  5. package/dist/analysis/dfg-verifier.js +43 -74
  6. package/dist/analysis/dfg-verifier.js.map +1 -1
  7. package/dist/analysis/interprocedural.d.ts +5 -1
  8. package/dist/analysis/interprocedural.js +62 -60
  9. package/dist/analysis/interprocedural.js.map +1 -1
  10. package/dist/analysis/metrics/index.d.ts +2 -0
  11. package/dist/analysis/metrics/index.js +2 -0
  12. package/dist/analysis/metrics/index.js.map +1 -0
  13. package/dist/analysis/metrics/metric-pass.d.ts +27 -0
  14. package/dist/analysis/metrics/metric-pass.js +2 -0
  15. package/dist/analysis/metrics/metric-pass.js.map +1 -0
  16. package/dist/analysis/metrics/metric-runner.d.ts +21 -0
  17. package/dist/analysis/metrics/metric-runner.js +47 -0
  18. package/dist/analysis/metrics/metric-runner.js.map +1 -0
  19. package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
  20. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
  21. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
  22. package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
  23. package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
  24. package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
  25. package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
  26. package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
  27. package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
  28. package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
  29. package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
  30. package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
  31. package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
  32. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
  33. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
  34. package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
  35. package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
  36. package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
  37. package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
  38. package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
  39. package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
  40. package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
  41. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
  42. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
  43. package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
  44. package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
  45. package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
  46. package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
  47. package/dist/analysis/passes/circular-dependency-pass.js +39 -0
  48. package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
  49. package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
  50. package/dist/analysis/passes/constant-propagation-pass.js +44 -0
  51. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
  52. package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
  53. package/dist/analysis/passes/cross-file-pass.js +102 -0
  54. package/dist/analysis/passes/cross-file-pass.js.map +1 -0
  55. package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
  56. package/dist/analysis/passes/dead-code-pass.js +117 -0
  57. package/dist/analysis/passes/dead-code-pass.js.map +1 -0
  58. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  59. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  60. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  61. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  62. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  63. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  64. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  65. package/dist/analysis/passes/language-sources-pass.js +491 -0
  66. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  67. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  68. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  69. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  70. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  71. package/dist/analysis/passes/missing-await-pass.js +90 -0
  72. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  73. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  74. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  75. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  76. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  77. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  78. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  79. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  80. package/dist/analysis/passes/null-deref-pass.js +130 -0
  81. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  82. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  83. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  84. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  85. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  86. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  87. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  88. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  89. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  90. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  91. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  92. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  93. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  94. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  95. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  96. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  97. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  98. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  99. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  100. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  101. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  102. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  103. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  104. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  105. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  106. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  107. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  108. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  109. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  110. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  111. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  112. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  113. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  114. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  115. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  116. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  117. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  118. package/dist/analysis/path-finder.d.ts +3 -13
  119. package/dist/analysis/path-finder.js +48 -63
  120. package/dist/analysis/path-finder.js.map +1 -1
  121. package/dist/analysis/taint-matcher.js +8 -1
  122. package/dist/analysis/taint-matcher.js.map +1 -1
  123. package/dist/analysis/taint-propagation.d.ts +5 -1
  124. package/dist/analysis/taint-propagation.js +44 -41
  125. package/dist/analysis/taint-propagation.js.map +1 -1
  126. package/dist/analyzer.d.ts +42 -1
  127. package/dist/analyzer.js +234 -1476
  128. package/dist/analyzer.js.map +1 -1
  129. package/dist/browser/circle-ir.js +3414 -1272
  130. package/dist/core/circle-ir-core.cjs +361 -107
  131. package/dist/core/circle-ir-core.js +361 -107
  132. package/dist/core/extractors/imports.js +18 -0
  133. package/dist/core/extractors/imports.js.map +1 -1
  134. package/dist/graph/analysis-pass.d.ts +68 -0
  135. package/dist/graph/analysis-pass.js +51 -0
  136. package/dist/graph/analysis-pass.js.map +1 -0
  137. package/dist/graph/code-graph.d.ts +92 -0
  138. package/dist/graph/code-graph.js +262 -0
  139. package/dist/graph/code-graph.js.map +1 -0
  140. package/dist/graph/import-graph.d.ts +33 -0
  141. package/dist/graph/import-graph.js +170 -0
  142. package/dist/graph/import-graph.js.map +1 -0
  143. package/dist/graph/index.d.ts +4 -0
  144. package/dist/graph/index.js +5 -0
  145. package/dist/graph/index.js.map +1 -0
  146. package/dist/graph/project-graph.d.ts +43 -0
  147. package/dist/graph/project-graph.js +80 -0
  148. package/dist/graph/project-graph.js.map +1 -0
  149. package/dist/graph/scope-graph.d.ts +63 -0
  150. package/dist/graph/scope-graph.js +89 -0
  151. package/dist/graph/scope-graph.js.map +1 -0
  152. package/dist/index.d.ts +2 -2
  153. package/dist/index.js +1 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/resolution/cross-file.js +52 -19
  156. package/dist/resolution/cross-file.js.map +1 -1
  157. package/dist/types/index.d.ts +151 -0
  158. package/docs/SPEC.md +10 -6
  159. 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":"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"}
@@ -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 dfg;
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(dfg: DFG, calls: CallInfo[], sources: TaintSource[], sinks: TaintSink[], sanitizers: TaintSanitizer[], config?: PathFinderConfig);
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
  */