src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }