s1009.go raw
1 package s1009
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/constant"
7 "go/token"
8 "go/types"
9
10 "honnef.co/go/tools/analysis/code"
11 "honnef.co/go/tools/analysis/facts/generated"
12 "honnef.co/go/tools/analysis/lint"
13 "honnef.co/go/tools/analysis/report"
14 "honnef.co/go/tools/go/types/typeutil"
15 "honnef.co/go/tools/knowledge"
16
17 "golang.org/x/tools/go/analysis"
18 "golang.org/x/tools/go/analysis/passes/inspect"
19 )
20
21 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
22 Analyzer: &analysis.Analyzer{
23 Name: "S1009",
24 Run: run,
25 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
26 },
27 Doc: &lint.RawDocumentation{
28 Title: `Omit redundant nil check on slices, maps, and channels`,
29 Text: `The \'len\' function is defined for all slices, maps, and
30 channels, even nil ones, which have a length of zero. It is not necessary to
31 check for nil before checking that their length is not zero.`,
32 Before: `if x != nil && len(x) != 0 {}`,
33 After: `if len(x) != 0 {}`,
34 Since: "2017.1",
35 MergeIf: lint.MergeIfAny,
36 },
37 })
38
39 var Analyzer = SCAnalyzer.Analyzer
40
41 // run checks for the following redundant nil-checks:
42 //
43 // if x == nil || len(x) == 0 {}
44 // if x == nil || len(x) < N {} (where N != 0)
45 // if x == nil || len(x) <= N {}
46 // if x != nil && len(x) != 0 {}
47 // if x != nil && len(x) == N {} (where N != 0)
48 // if x != nil && len(x) > N {}
49 // if x != nil && len(x) >= N {} (where N != 0)
50 func run(pass *analysis.Pass) (interface{}, error) {
51 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) {
52 _, ok := expr.(*ast.BasicLit)
53 if ok {
54 return true, code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
55 }
56 id, ok := expr.(*ast.Ident)
57 if !ok {
58 return false, false
59 }
60 c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const)
61 if !ok {
62 return false, false
63 }
64 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0"
65 }
66
67 fn := func(node ast.Node) {
68 // check that expr is "x || y" or "x && y"
69 expr := node.(*ast.BinaryExpr)
70 if expr.Op != token.LOR && expr.Op != token.LAND {
71 return
72 }
73 eqNil := expr.Op == token.LOR
74
75 // check that x is "xx == nil" or "xx != nil"
76 x, ok := expr.X.(*ast.BinaryExpr)
77 if !ok {
78 return
79 }
80 if eqNil && x.Op != token.EQL {
81 return
82 }
83 if !eqNil && x.Op != token.NEQ {
84 return
85 }
86 var xx *ast.Ident
87 switch s := x.X.(type) {
88 case *ast.Ident:
89 xx = s
90 case *ast.SelectorExpr:
91 xx = s.Sel
92 default:
93 return
94 }
95 if !code.IsNil(pass, x.Y) {
96 return
97 }
98
99 // check that y is "len(xx) == 0" or "len(xx) ... "
100 y, ok := expr.Y.(*ast.BinaryExpr)
101 if !ok {
102 return
103 }
104 yx, ok := y.X.(*ast.CallExpr)
105 if !ok {
106 return
107 }
108 if !code.IsCallTo(pass, yx, "len") {
109 return
110 }
111 var yxArg *ast.Ident
112 switch s := yx.Args[knowledge.Arg("len.v")].(type) {
113 case *ast.Ident:
114 yxArg = s
115 case *ast.SelectorExpr:
116 yxArg = s.Sel
117 default:
118 return
119 }
120 if yxArg.Name != xx.Name {
121 return
122 }
123
124 isConst, isZero := isConstZero(y.Y)
125 if !isConst {
126 return
127 }
128
129 if eqNil {
130 switch y.Op {
131 case token.EQL:
132 // avoid false positive for "xx == nil || len(xx) == <non-zero>"
133 if !isZero {
134 return
135 }
136 case token.LEQ:
137 // ok
138 case token.LSS:
139 // avoid false positive for "xx == nil || len(xx) < 0"
140 if isZero {
141 return
142 }
143 default:
144 return
145 }
146 }
147
148 if !eqNil {
149 switch y.Op {
150 case token.EQL:
151 // avoid false positive for "xx != nil && len(xx) == 0"
152 if isZero {
153 return
154 }
155 case token.GEQ:
156 // avoid false positive for "xx != nil && len(xx) >= 0"
157 if isZero {
158 return
159 }
160 case token.NEQ:
161 // avoid false positive for "xx != nil && len(xx) != <non-zero>"
162 if !isZero {
163 return
164 }
165 case token.GTR:
166 // ok
167 default:
168 return
169 }
170 }
171
172 // finally check that xx type is one of array, slice, map or chan
173 // this is to prevent false positive in case if xx is a pointer to an array
174 typ := pass.TypesInfo.TypeOf(xx)
175 var nilType string
176 ok = typeutil.All(typ, func(term *types.Term) bool {
177 switch term.Type().Underlying().(type) {
178 case *types.Slice:
179 nilType = "nil slices"
180 return true
181 case *types.Map:
182 nilType = "nil maps"
183 return true
184 case *types.Chan:
185 nilType = "nil channels"
186 return true
187 case *types.Pointer:
188 return false
189 case *types.TypeParam:
190 return false
191 default:
192 lint.ExhaustiveTypeSwitch(term.Type().Underlying())
193 return false
194 }
195 })
196 if !ok {
197 return
198 }
199
200 report.Report(pass, expr, fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType), report.FilterGenerated())
201 }
202 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
203 return nil, nil
204 }
205