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