1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // Package shadow defines an Analyzer that checks for shadowed variables. |
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 | // NOTE: Experimental. Not part of the vet suite. |
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 | // flags |
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 | // Ignore identifiers that don't denote objects |
66 | // (package names, symbolic variables such as t |
67 | // in t := x.(type) of type switch headers). |
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 | // A type switch with a short variable declaration |
77 | // such as t := x.(type) doesn't declare the symbolic |
78 | // variable (t in the example) at the switch header; |
79 | // instead a new variable t (with specific type) is |
80 | // declared implicitly for each case. Such variables |
81 | // are found in the types.Info.Implicits (not Defs) |
82 | // map. Add them here, assuming they are declared at |
83 | // the type cases' colon ":". |
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 | // A span stores the minimum range of byte positions in the file in which a |
105 | // given variable (types.Object) is mentioned. It is lexically defined: it spans |
106 | // from the beginning of its first mention to the end of its last mention. |
107 | // A variable is considered shadowed (if strict is off) only if the |
108 | // shadowing variable is declared within the span of the shadowed variable. |
109 | // In other words, if a variable is shadowed but not used after the shadowed |
110 | // variable is declared, it is inconsequential and not worth complaining about. |
111 | // This simple check dramatically reduces the nuisance rate for the shadowing |
112 | // check, at least until something cleverer comes along. |
113 | // |
114 | // One wrinkle: A "naked return" is a silent use of a variable that the Span |
115 | // will not capture, but the compilers catch naked returns of shadowed |
116 | // variables so we don't need to. |
117 | // |
118 | // Cases this gets wrong (TODO): |
119 | // - If a for loop's continuation statement mentions a variable redeclared in |
120 | // the block, we should complain about it but don't. |
121 | // - A variable declared inside a function literal can falsely be identified |
122 | // as shadowing a variable in the outer function. |
123 | type span struct { |
124 | min token.Pos |
125 | max token.Pos |
126 | } |
127 | |
128 | // contains reports whether the position is inside the span. |
129 | func (s span) contains(pos token.Pos) bool { |
130 | return s.min <= pos && pos < s.max |
131 | } |
132 | |
133 | // growSpan expands the span for the object to contain the source range [pos, end). |
134 | func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) { |
135 | if strict { |
136 | return // No need |
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 | // checkShadowAssignment checks for shadowing in a short variable declaration. |
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 | // idiomaticShortRedecl reports whether this short declaration can be ignored for |
171 | // the purposes of shadowing, that is, that any redeclarations it contains are deliberate. |
172 | func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool { |
173 | // Don't complain about deliberate redeclarations of the form |
174 | // i := i |
175 | // Such constructs are idiomatic in range loops to create a new variable |
176 | // for each iteration. Another example is |
177 | // switch n := n.(type) |
178 | if len(a.Rhs) != len(a.Lhs) { |
179 | return false |
180 | } |
181 | // We know it's an assignment, so the LHS must be all identifiers. (We check anyway.) |
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 // Don't do any more processing. |
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 | // idiomaticRedecl reports whether this declaration spec can be ignored for |
207 | // the purposes of shadowing, that is, that any redeclarations it contains are deliberate. |
208 | func idiomaticRedecl(d *ast.ValueSpec) bool { |
209 | // Don't complain about deliberate redeclarations of the form |
210 | // var i, j = i, j |
211 | // Don't ignore redeclarations of the form |
212 | // var i = 3 |
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 | // checkShadowDecl checks for shadowing in a general variable declaration. |
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 | // Don't complain about deliberate redeclarations of the form |
237 | // var i = i |
238 | if idiomaticRedecl(valueSpec) { |
239 | return |
240 | } |
241 | for _, ident := range valueSpec.Names { |
242 | checkShadowing(pass, spans, ident) |
243 | } |
244 | } |
245 | } |
246 | |
247 | // checkShadowing checks whether the identifier shadows an identifier in an outer scope. |
248 | func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) { |
249 | if ident.Name == "_" { |
250 | // Can't shadow the blank identifier. |
251 | return |
252 | } |
253 | obj := pass.TypesInfo.Defs[ident] |
254 | if obj == nil { |
255 | return |
256 | } |
257 | // obj.Parent.Parent is the surrounding scope. If we can find another declaration |
258 | // starting from there, we have a shadowed identifier. |
259 | _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos()) |
260 | if shadowed == nil { |
261 | return |
262 | } |
263 | // Don't complain if it's shadowing a universe-declared identifier; that's fine. |
264 | if shadowed.Parent() == types.Universe { |
265 | return |
266 | } |
267 | if strict { |
268 | // The shadowed identifier must appear before this one to be an instance of shadowing. |
269 | if shadowed.Pos() > ident.Pos() { |
270 | return |
271 | } |
272 | } else { |
273 | // Don't complain if the span of validity of the shadowed identifier doesn't include |
274 | // the shadowing identifier. |
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 | // Don't complain if the types differ: that implies the programmer really wants two different things. |
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 |
Members