sa4003.go raw

   1  package sa4003
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/constant"
   7  	"go/token"
   8  	"go/types"
   9  	"math"
  10  
  11  	"honnef.co/go/tools/analysis/code"
  12  	"honnef.co/go/tools/analysis/lint"
  13  	"honnef.co/go/tools/analysis/report"
  14  	"honnef.co/go/tools/go/types/typeutil"
  15  
  16  	"golang.org/x/tools/go/analysis"
  17  	"golang.org/x/tools/go/analysis/passes/inspect"
  18  )
  19  
  20  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  21  	Analyzer: &analysis.Analyzer{
  22  		Name:     "SA4003",
  23  		Run:      run,
  24  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  25  	},
  26  	Doc: &lint.RawDocumentation{
  27  		Title:    `Comparing unsigned values against negative values is pointless`,
  28  		Since:    "2017.1",
  29  		Severity: lint.SeverityWarning,
  30  		MergeIf:  lint.MergeIfAll,
  31  	},
  32  })
  33  
  34  var Analyzer = SCAnalyzer.Analyzer
  35  
  36  func run(pass *analysis.Pass) (interface{}, error) {
  37  	isobj := func(expr ast.Expr, name string) bool {
  38  		if name == "" {
  39  			return false
  40  		}
  41  		sel, ok := expr.(*ast.SelectorExpr)
  42  		if !ok {
  43  			return false
  44  		}
  45  		return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
  46  	}
  47  
  48  	fn := func(node ast.Node) {
  49  		expr := node.(*ast.BinaryExpr)
  50  		tx := pass.TypesInfo.TypeOf(expr.X)
  51  		basic, ok := tx.Underlying().(*types.Basic)
  52  		if !ok {
  53  			return
  54  		}
  55  
  56  		// We only check for the math constants and integer literals, not for all constant expressions. This is to avoid
  57  		// false positives when constant values differ under different build tags.
  58  		var (
  59  			maxMathConst string
  60  			minMathConst string
  61  			maxLiteral   constant.Value
  62  			minLiteral   constant.Value
  63  		)
  64  
  65  		switch basic.Kind() {
  66  		case types.Uint8:
  67  			maxMathConst = "math.MaxUint8"
  68  			minLiteral = constant.MakeUint64(0)
  69  			maxLiteral = constant.MakeUint64(math.MaxUint8)
  70  		case types.Uint16:
  71  			maxMathConst = "math.MaxUint16"
  72  			minLiteral = constant.MakeUint64(0)
  73  			maxLiteral = constant.MakeUint64(math.MaxUint16)
  74  		case types.Uint32:
  75  			maxMathConst = "math.MaxUint32"
  76  			minLiteral = constant.MakeUint64(0)
  77  			maxLiteral = constant.MakeUint64(math.MaxUint32)
  78  		case types.Uint64:
  79  			maxMathConst = "math.MaxUint64"
  80  			minLiteral = constant.MakeUint64(0)
  81  			maxLiteral = constant.MakeUint64(math.MaxUint64)
  82  		case types.Uint:
  83  			// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
  84  			maxMathConst = "math.MaxUint64"
  85  			minLiteral = constant.MakeUint64(0)
  86  			maxLiteral = constant.MakeUint64(math.MaxUint64)
  87  
  88  		case types.Int8:
  89  			minMathConst = "math.MinInt8"
  90  			maxMathConst = "math.MaxInt8"
  91  			minLiteral = constant.MakeInt64(math.MinInt8)
  92  			maxLiteral = constant.MakeInt64(math.MaxInt8)
  93  		case types.Int16:
  94  			minMathConst = "math.MinInt16"
  95  			maxMathConst = "math.MaxInt16"
  96  			minLiteral = constant.MakeInt64(math.MinInt16)
  97  			maxLiteral = constant.MakeInt64(math.MaxInt16)
  98  		case types.Int32:
  99  			minMathConst = "math.MinInt32"
 100  			maxMathConst = "math.MaxInt32"
 101  			minLiteral = constant.MakeInt64(math.MinInt32)
 102  			maxLiteral = constant.MakeInt64(math.MaxInt32)
 103  		case types.Int64:
 104  			minMathConst = "math.MinInt64"
 105  			maxMathConst = "math.MaxInt64"
 106  			minLiteral = constant.MakeInt64(math.MinInt64)
 107  			maxLiteral = constant.MakeInt64(math.MaxInt64)
 108  		case types.Int:
 109  			// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
 110  			minMathConst = "math.MinInt64"
 111  			maxMathConst = "math.MaxInt64"
 112  			minLiteral = constant.MakeInt64(math.MinInt64)
 113  			maxLiteral = constant.MakeInt64(math.MaxInt64)
 114  		}
 115  
 116  		isLiteral := func(expr ast.Expr, c constant.Value) bool {
 117  			if c == nil {
 118  				return false
 119  			}
 120  			return code.IsIntegerLiteral(pass, expr, c)
 121  		}
 122  		isZeroLiteral := func(expr ast.Expr) bool {
 123  			return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
 124  		}
 125  
 126  		if (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
 127  			(expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
 128  			report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, maxMathConst))
 129  		}
 130  
 131  		if expr.Op == token.LEQ && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
 132  			expr.Op == token.GEQ && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
 133  			report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, maxMathConst))
 134  		}
 135  
 136  		if (basic.Info() & types.IsUnsigned) != 0 {
 137  			if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) ||
 138  				(expr.Op == token.GTR && isZeroLiteral(expr.X)) {
 139  				report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic))
 140  			}
 141  			if expr.Op == token.GEQ && isZeroLiteral(expr.Y) ||
 142  				expr.Op == token.LEQ && isZeroLiteral(expr.X) {
 143  				report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic))
 144  			}
 145  		} else {
 146  			if (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
 147  				(expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
 148  				report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, minMathConst))
 149  			}
 150  			if expr.Op == token.GEQ && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
 151  				expr.Op == token.LEQ && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
 152  				report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, minMathConst))
 153  			}
 154  		}
 155  
 156  	}
 157  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
 158  	return nil, nil
 159  }
 160