1
2
3
4
5
6 package shadow
7
8 import (
9 "go/ast"
10 "go/token"
11 "go/types"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/ast/inspector"
16 )
17
18
19
20 const Doc = `check for possible unintended shadowing of variables
21
22 This analyzer check for shadowed variables.
23 A shadowed variable is a variable declared in an inner scope
24 with the same name and type as a variable in an outer scope,
25 and where the outer variable is mentioned after the inner one
26 is declared.
27
28 (This definition can be refined; the module generates too many
29 false positives and is not yet enabled by default.)
30
31 For example:
32
33 func BadRead(f *os.File, buf []byte) error {
34 var err error
35 for {
36 n, err := f.Read(buf) // shadows the function variable 'err'
37 if err != nil {
38 break // causes return of wrong value
39 }
40 foo(buf)
41 }
42 return err
43 }
44 `
45
46 var Analyzer = &analysis.Analyzer{
47 Name: "shadow",
48 Doc: Doc,
49 Requires: []*analysis.Analyzer{inspect.Analyzer},
50 Run: run,
51 }
52
53
54 var strict = false
55
56 func init() {
57 Analyzer.Flags.BoolVar(&strict, "strict", strict, "whether to be strict about shadowing; can be noisy")
58 }
59
60 func run(pass *analysis.Pass) (interface{}, error) {
61 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
62
63 spans := make(map[types.Object]span)
64 for id, obj := range pass.TypesInfo.Defs {
65
66
67
68 if obj != nil {
69 growSpan(spans, obj, id.Pos(), id.End())
70 }
71 }
72 for id, obj := range pass.TypesInfo.Uses {
73 growSpan(spans, obj, id.Pos(), id.End())
74 }
75 for node, obj := range pass.TypesInfo.Implicits {
76
77
78
79
80
81
82
83
84 if cc, ok := node.(*ast.CaseClause); ok {
85 growSpan(spans, obj, cc.Colon, cc.Colon)
86 }
87 }
88
89 nodeFilter := []ast.Node{
90 (*ast.AssignStmt)(nil),
91 (*ast.GenDecl)(nil),
92 }
93 inspect.Preorder(nodeFilter, func(n ast.Node) {
94 switch n := n.(type) {
95 case *ast.AssignStmt:
96 checkShadowAssignment(pass, spans, n)
97 case *ast.GenDecl:
98 checkShadowDecl(pass, spans, n)
99 }
100 })
101 return nil, nil
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 type span struct {
124 min token.Pos
125 max token.Pos
126 }
127
128
129 func (s span) contains(pos token.Pos) bool {
130 return s.min <= pos && pos < s.max
131 }
132
133
134 func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) {
135 if strict {
136 return
137 }
138 s, ok := spans[obj]
139 if ok {
140 if s.min > pos {
141 s.min = pos
142 }
143 if s.max < end {
144 s.max = end
145 }
146 } else {
147 s = span{pos, end}
148 }
149 spans[obj] = s
150 }
151
152
153 func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *ast.AssignStmt) {
154 if a.Tok != token.DEFINE {
155 return
156 }
157 if idiomaticShortRedecl(pass, a) {
158 return
159 }
160 for _, expr := range a.Lhs {
161 ident, ok := expr.(*ast.Ident)
162 if !ok {
163 pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
164 return
165 }
166 checkShadowing(pass, spans, ident)
167 }
168 }
169
170
171
172 func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool {
173
174
175
176
177
178 if len(a.Rhs) != len(a.Lhs) {
179 return false
180 }
181
182 for i, expr := range a.Lhs {
183 lhs, ok := expr.(*ast.Ident)
184 if !ok {
185 pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
186 return true
187 }
188 switch rhs := a.Rhs[i].(type) {
189 case *ast.Ident:
190 if lhs.Name != rhs.Name {
191 return false
192 }
193 case *ast.TypeAssertExpr:
194 if id, ok := rhs.X.(*ast.Ident); ok {
195 if lhs.Name != id.Name {
196 return false
197 }
198 }
199 default:
200 return false
201 }
202 }
203 return true
204 }
205
206
207
208 func idiomaticRedecl(d *ast.ValueSpec) bool {
209
210
211
212
213 if len(d.Names) != len(d.Values) {
214 return false
215 }
216 for i, lhs := range d.Names {
217 rhs, ok := d.Values[i].(*ast.Ident)
218 if !ok || lhs.Name != rhs.Name {
219 return false
220 }
221 }
222 return true
223 }
224
225
226 func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.GenDecl) {
227 if d.Tok != token.VAR {
228 return
229 }
230 for _, spec := range d.Specs {
231 valueSpec, ok := spec.(*ast.ValueSpec)
232 if !ok {
233 pass.ReportRangef(spec, "invalid AST: var GenDecl not ValueSpec")
234 return
235 }
236
237
238 if idiomaticRedecl(valueSpec) {
239 return
240 }
241 for _, ident := range valueSpec.Names {
242 checkShadowing(pass, spans, ident)
243 }
244 }
245 }
246
247
248 func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) {
249 if ident.Name == "_" {
250
251 return
252 }
253 obj := pass.TypesInfo.Defs[ident]
254 if obj == nil {
255 return
256 }
257
258
259 _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos())
260 if shadowed == nil {
261 return
262 }
263
264 if shadowed.Parent() == types.Universe {
265 return
266 }
267 if strict {
268
269 if shadowed.Pos() > ident.Pos() {
270 return
271 }
272 } else {
273
274
275 span, ok := spans[shadowed]
276 if !ok {
277 pass.ReportRangef(ident, "internal error: no range for %q", ident.Name)
278 return
279 }
280 if !span.contains(ident.Pos()) {
281 return
282 }
283 }
284
285 if types.Identical(obj.Type(), shadowed.Type()) {
286 line := pass.Fset.Position(shadowed.Pos()).Line
287 pass.ReportRangef(ident, "declaration of %q shadows declaration at line %d", obj.Name(), line)
288 }
289 }
290
View as plain text