src

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

sa4000.go (6389B)


      1 package sa4000
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/token"
      7 	"go/types"
      8 	"reflect"
      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 
     16 	"golang.org/x/tools/go/analysis"
     17 	"golang.org/x/tools/go/analysis/passes/inspect"
     18 	"golang.org/x/tools/go/ast/edge"
     19 	"golang.org/x/tools/go/ast/inspector"
     20 )
     21 
     22 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     23 	Analyzer: &analysis.Analyzer{
     24 		Name:     "SA4000",
     25 		Run:      run,
     26 		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
     27 	},
     28 	Doc: &lint.RawDocumentation{
     29 		Title:    `Binary operator has identical expressions on both sides`,
     30 		Since:    "2017.1",
     31 		Severity: lint.SeverityWarning,
     32 		MergeIf:  lint.MergeIfAny,
     33 	},
     34 })
     35 
     36 var Analyzer = SCAnalyzer.Analyzer
     37 
     38 func run(pass *analysis.Pass) (any, error) {
     39 	var isFloat func(T types.Type) bool
     40 	isFloat = func(T types.Type) bool {
     41 		tset := typeutil.NewTypeSet(T)
     42 		if len(tset.Terms) == 0 {
     43 			// no terms, so floats are a possibility
     44 			return true
     45 		}
     46 		return tset.Any(func(term *types.Term) bool {
     47 			switch typ := term.Type().Underlying().(type) {
     48 			case *types.Basic:
     49 				kind := typ.Kind()
     50 				return kind == types.Float32 || kind == types.Float64
     51 			case *types.Array:
     52 				return isFloat(typ.Elem())
     53 			case *types.Struct:
     54 				for field := range typ.Fields() {
     55 					if isFloat(field.Type()) {
     56 						return true
     57 					}
     58 				}
     59 				return false
     60 			default:
     61 				return false
     62 			}
     63 		})
     64 	}
     65 
     66 	// TODO(dh): this check ignores the existence of side-effects and
     67 	// happily flags fn() == fn() – so far, we've had only two complains
     68 	// about false positives, and it's caught several bugs in real
     69 	// code.
     70 	//
     71 	// We special case functions from the math/rand package. Someone ran
     72 	// into the following false positive: "rand.Intn(2) - rand.Intn(2), which I wrote to generate values {-1, 0, 1} with {0.25, 0.5, 0.25} probability."
     73 
     74 	skipComparableCheck := func(c inspector.Cursor) bool {
     75 		op, ok := c.Node().(*ast.BinaryExpr)
     76 		if !ok {
     77 			return false
     78 		}
     79 		if clit, ok := op.X.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
     80 			return false
     81 		}
     82 		if clit, ok := op.Y.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
     83 			return false
     84 		}
     85 
     86 		// TODO(dh): we should probably skip ParenExprs, but users should
     87 		// probably not use unnecessary ParenExprs.
     88 		vspec, ok := c.Parent().Node().(*ast.ValueSpec)
     89 		if !ok {
     90 			return false
     91 		}
     92 		e, i := c.ParentEdge()
     93 		if e != edge.ValueSpec_Values {
     94 			return false
     95 		}
     96 		if vspec.Names[i].Name == "_" {
     97 			// `var _ = T{} == T{}` is permitted, as a compile-time
     98 			// check that T implements comparable.
     99 			return true
    100 		}
    101 		return false
    102 	}
    103 
    104 	for c := range code.Cursor(pass).Preorder((*ast.BinaryExpr)(nil)) {
    105 		node := c.Node()
    106 		op := node.(*ast.BinaryExpr)
    107 		switch op.Op {
    108 		case token.EQL, token.NEQ:
    109 			if skipComparableCheck(c) {
    110 				continue
    111 			}
    112 		case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
    113 			token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
    114 		default:
    115 			// For some ops, such as + and *, it can make sense to
    116 			// have identical operands
    117 			continue
    118 		}
    119 
    120 		if isFloat(pass.TypesInfo.TypeOf(op.X)) {
    121 			// 'float <op> float' makes sense for several operators.
    122 			// We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead.
    123 			continue
    124 		}
    125 
    126 		if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
    127 			continue
    128 		}
    129 		if report.Render(pass, op.X) != report.Render(pass, op.Y) {
    130 			continue
    131 		}
    132 		l1, ok1 := op.X.(*ast.BasicLit)
    133 		l2, ok2 := op.Y.(*ast.BasicLit)
    134 		if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
    135 			// cgo generates the following function call:
    136 			// _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0
    137 			// instead of true in case the user shadowed the
    138 			// identifier. Ideally we'd restrict this exception to
    139 			// calls of _cgoCheckPointer, but it's not worth the
    140 			// hassle of keeping track of the stack. <lit> <op> <lit>
    141 			// are very rare to begin with, and we're mostly checking
    142 			// for them to catch typos such as 1 == 1 where the user
    143 			// meant to type i == 1. The odds of a false negative for
    144 			// 0 == 0 are slim.
    145 			continue
    146 		}
    147 
    148 		if expr, ok := op.X.(*ast.CallExpr); ok {
    149 			call := code.CallName(pass, expr)
    150 			switch call {
    151 			case "math/rand.Int",
    152 				"math/rand.Int31",
    153 				"math/rand.Int31n",
    154 				"math/rand.Int63",
    155 				"math/rand.Int63n",
    156 				"math/rand.Intn",
    157 				"math/rand.Uint32",
    158 				"math/rand.Uint64",
    159 				"math/rand.ExpFloat64",
    160 				"math/rand.Float32",
    161 				"math/rand.Float64",
    162 				"math/rand.NormFloat64",
    163 				"(*math/rand.Rand).Int",
    164 				"(*math/rand.Rand).Int31",
    165 				"(*math/rand.Rand).Int31n",
    166 				"(*math/rand.Rand).Int63",
    167 				"(*math/rand.Rand).Int63n",
    168 				"(*math/rand.Rand).Intn",
    169 				"(*math/rand.Rand).Uint32",
    170 				"(*math/rand.Rand).Uint64",
    171 				"(*math/rand.Rand).ExpFloat64",
    172 				"(*math/rand.Rand).Float32",
    173 				"(*math/rand.Rand).Float64",
    174 				"(*math/rand.Rand).NormFloat64",
    175 				"math/rand/v2.Int",
    176 				"math/rand/v2.Int32",
    177 				"math/rand/v2.Int32N",
    178 				"math/rand/v2.Int64",
    179 				"math/rand/v2.Int64N",
    180 				"math/rand/v2.IntN",
    181 				"math/rand/v2.N",
    182 				"math/rand/v2.Uint",
    183 				"math/rand/v2.Uint32",
    184 				"math/rand/v2.Uint32N",
    185 				"math/rand/v2.Uint64",
    186 				"math/rand/v2.Uint64N",
    187 				"math/rand/v2.UintN",
    188 				"math/rand/v2.ExpFloat64",
    189 				"math/rand/v2.Float32",
    190 				"math/rand/v2.Float64",
    191 				"math/rand/v2.NormFloat64",
    192 				"(*math/rand/v2.Rand).Int",
    193 				"(*math/rand/v2.Rand).Int32",
    194 				"(*math/rand/v2.Rand).Int32N",
    195 				"(*math/rand/v2.Rand).Int64",
    196 				"(*math/rand/v2.Rand).Int64N",
    197 				"(*math/rand/v2.Rand).IntN",
    198 				"(*math/rand/v2.Rand).N",
    199 				"(*math/rand/v2.Rand).Uint",
    200 				"(*math/rand/v2.Rand).Uint32",
    201 				"(*math/rand/v2.Rand).Uint32N",
    202 				"(*math/rand/v2.Rand).Uint64",
    203 				"(*math/rand/v2.Rand).Uint64N",
    204 				"(*math/rand/v2.Rand).UintN",
    205 				"(*math/rand/v2.Rand).ExpFloat64",
    206 				"(*math/rand/v2.Rand).Float32",
    207 				"(*math/rand/v2.Rand).Float64",
    208 				"(*math/rand/v2.Rand).NormFloat64":
    209 				continue
    210 			}
    211 		}
    212 
    213 		report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
    214 	}
    215 
    216 	return nil, nil
    217 }