GoPLS Viewer

Home|gopls/go/analysis/passes/shadow/shadow.go
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.
6package shadow
7
8import (
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
20const Doc = `check for possible unintended shadowing of variables
21
22This analyzer check for shadowed variables.
23A shadowed variable is a variable declared in an inner scope
24with the same name and type as a variable in an outer scope,
25and where the outer variable is mentioned after the inner one
26is declared.
27
28(This definition can be refined; the module generates too many
29false positives and is not yet enabled by default.)
30
31For 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
46var Analyzer = &analysis.Analyzer{
47    Name:     "shadow",
48    Doc:      Doc,
49    Requires: []*analysis.Analyzer{inspect.Analyzer},
50    Run:      run,
51}
52
53// flags
54var strict = false
55
56func init() {
57    Analyzer.Flags.BoolVar(&strict"strict"strict"whether to be strict about shadowing; can be noisy")
58}
59
60func run(pass *analysis.Pass) (interface{}, error) {
61    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
62
63    spans := make(map[types.Object]span)
64    for idobj := 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(spansobjid.Pos(), id.End())
70        }
71    }
72    for idobj := range pass.TypesInfo.Uses {
73        growSpan(spansobjid.Pos(), id.End())
74    }
75    for nodeobj := 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 ccok := node.(*ast.CaseClause); ok {
85            growSpan(spansobjcc.Coloncc.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(passspansn)
97        case *ast.GenDecl:
98            checkShadowDecl(passspansn)
99        }
100    })
101    return nilnil
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.
123type span struct {
124    min token.Pos
125    max token.Pos
126}
127
128// contains reports whether the position is inside the span.
129func (s spancontains(pos token.Posbool {
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).
134func growSpan(spans map[types.Object]spanobj types.Objectposend token.Pos) {
135    if strict {
136        return // No need
137    }
138    sok := 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{posend}
148    }
149    spans[obj] = s
150}
151
152// checkShadowAssignment checks for shadowing in a short variable declaration.
153func checkShadowAssignment(pass *analysis.Passspans map[types.Object]spana *ast.AssignStmt) {
154    if a.Tok != token.DEFINE {
155        return
156    }
157    if idiomaticShortRedecl(passa) {
158        return
159    }
160    for _expr := range a.Lhs {
161        identok := expr.(*ast.Ident)
162        if !ok {
163            pass.ReportRangef(expr"invalid AST: short variable declaration of non-identifier")
164            return
165        }
166        checkShadowing(passspansident)
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.
172func idiomaticShortRedecl(pass *analysis.Passa *ast.AssignStmtbool {
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 iexpr := range a.Lhs {
183        lhsok := 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 idok := 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.
208func idiomaticRedecl(d *ast.ValueSpecbool {
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 ilhs := range d.Names {
217        rhsok := 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.
226func checkShadowDecl(pass *analysis.Passspans map[types.Object]spand *ast.GenDecl) {
227    if d.Tok != token.VAR {
228        return
229    }
230    for _spec := range d.Specs {
231        valueSpecok := 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(passspansident)
243        }
244    }
245}
246
247// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
248func checkShadowing(pass *analysis.Passspans map[types.Object]spanident *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        spanok := 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
MembersX
inspect
run.nodeFilter
idiomaticRedecl.RangeStmt_6500.lhs
checkShadowDecl.d
checkShadowing.shadowed
types
run
run.RangeStmt_1948.obj
idiomaticShortRedecl.a
idiomaticRedecl.d
checkShadowDecl.pass
inspector
growSpan.spans
run.pass
growSpan.obj
checkShadowAssignment.a
checkShadowDecl.RangeStmt_6844.spec
checkShadowDecl.RangeStmt_6844.BlockStmt.RangeStmt_7139.ident
strict
span.max
run.RangeStmt_1858.id
span
span.contains
checkShadowing.pass
run.RangeStmt_1598.id
run.RangeStmt_1598.obj
init
run.spans
idiomaticShortRedecl.RangeStmt_5599.expr
checkShadowing.BlockStmt.line
analysis
growSpan.end
checkShadowAssignment.RangeStmt_4782.expr
checkShadowing.spans
checkShadowing.ident
Doc
span.min
span.contains.s
span.contains.pos
checkShadowAssignment.spans
idiomaticRedecl
run.RangeStmt_1858.obj
growSpan
idiomaticShortRedecl
ast
growSpan.pos
checkShadowAssignment
checkShadowDecl
checkShadowDecl.spans
token
run.RangeStmt_1948.node
checkShadowAssignment.pass
idiomaticShortRedecl.RangeStmt_5599.i
checkShadowing
idiomaticShortRedecl.pass
idiomaticRedecl.RangeStmt_6500.i
checkShadowing._
Members
X