s1009.go (4262B)
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/pattern" 16 17 "golang.org/x/tools/go/analysis" 18 ) 19 20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 21 Analyzer: &analysis.Analyzer{ 22 Name: "S1009", 23 Run: run, 24 Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), 25 }, 26 Doc: &lint.RawDocumentation{ 27 Title: `Omit redundant nil check on slices, maps, and channels`, 28 Text: `The \'len\' function is defined for all slices, maps, and 29 channels, even nil ones, which have a length of zero. It is not necessary to 30 check for nil before checking that their length is not zero.`, 31 Before: `if x != nil && len(x) != 0 {}`, 32 After: `if len(x) != 0 {}`, 33 Since: "2017.1", 34 MergeIf: lint.MergeIfAny, 35 }, 36 }) 37 38 var Analyzer = SCAnalyzer.Analyzer 39 40 var query = pattern.MustParse(` 41 (BinaryExpr 42 (BinaryExpr 43 x 44 lhsOp@(Or "==" "!=") 45 nilly) 46 outerOp@(Or "&&" "||") 47 (BinaryExpr 48 (CallExpr (Builtin "len") [x]) 49 rhsOp 50 k))`) 51 52 // run checks for the following redundant nil-checks: 53 // 54 // if x == nil || len(x) == 0 {} 55 // if x == nil || len(x) < N {} (where N != 0) 56 // if x == nil || len(x) <= N {} 57 // if x != nil && len(x) != 0 {} 58 // if x != nil && len(x) == N {} (where N != 0) 59 // if x != nil && len(x) > N {} 60 // if x != nil && len(x) >= N {} (where N != 0) 61 func run(pass *analysis.Pass) (any, error) { 62 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) { 63 _, ok := expr.(*ast.BasicLit) 64 if ok { 65 return true, code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) 66 } 67 id, ok := expr.(*ast.Ident) 68 if !ok { 69 return false, false 70 } 71 c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const) 72 if !ok { 73 return false, false 74 } 75 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0" 76 } 77 78 for node, m := range code.Matches(pass, query) { 79 x := m.State["x"].(ast.Expr) 80 outerOp := m.State["outerOp"].(token.Token) 81 lhsOp := m.State["lhsOp"].(token.Token) 82 rhsOp := m.State["rhsOp"].(token.Token) 83 nilly := m.State["nilly"].(ast.Expr) 84 k := m.State["k"].(ast.Expr) 85 eqNil := outerOp == token.LOR 86 87 if code.MayHaveSideEffects(pass, x, nil) { 88 continue 89 } 90 91 if eqNil && lhsOp != token.EQL { 92 continue 93 } 94 if !eqNil && lhsOp != token.NEQ { 95 continue 96 } 97 if !code.IsNil(pass, nilly) { 98 continue 99 } 100 isConst, isZero := isConstZero(k) 101 if !isConst { 102 continue 103 } 104 105 if eqNil { 106 switch rhsOp { 107 case token.EQL: 108 // avoid false positive for "xx == nil || len(xx) == <non-zero>" 109 if !isZero { 110 continue 111 } 112 case token.LEQ: 113 // ok 114 case token.LSS: 115 // avoid false positive for "xx == nil || len(xx) < 0" 116 if isZero { 117 continue 118 } 119 default: 120 continue 121 } 122 } else { 123 switch rhsOp { 124 case token.EQL: 125 // avoid false positive for "xx != nil && len(xx) == 0" 126 if isZero { 127 continue 128 } 129 case token.GEQ: 130 // avoid false positive for "xx != nil && len(xx) >= 0" 131 if isZero { 132 continue 133 } 134 case token.NEQ: 135 // avoid false positive for "xx != nil && len(xx) != <non-zero>" 136 if !isZero { 137 continue 138 } 139 case token.GTR: 140 // ok 141 default: 142 continue 143 } 144 } 145 146 // finally check that xx type is one of array, slice, map or chan 147 // this is to prevent false positive in case if xx is a pointer to an array 148 typ := pass.TypesInfo.TypeOf(x) 149 var nilType string 150 ok := typeutil.All(typ, func(term *types.Term) bool { 151 switch term.Type().Underlying().(type) { 152 case *types.Slice: 153 nilType = "nil slices" 154 return true 155 case *types.Map: 156 nilType = "nil maps" 157 return true 158 case *types.Chan: 159 nilType = "nil channels" 160 return true 161 case *types.Pointer: 162 return false 163 case *types.TypeParam: 164 return false 165 default: 166 lint.ExhaustiveTypeSwitch(term.Type().Underlying()) 167 return false 168 } 169 }) 170 if !ok { 171 continue 172 } 173 174 report.Report(pass, node, 175 fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType), 176 report.FilterGenerated()) 177 } 178 179 return nil, nil 180 }