Files
odidere/vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go
2026-06-06 01:17:29 +00:00

218 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package sa4000
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4000",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Binary operator has identical expressions on both sides`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
var isFloat func(T types.Type) bool
isFloat = func(T types.Type) bool {
tset := typeutil.NewTypeSet(T)
if len(tset.Terms) == 0 {
// no terms, so floats are a possibility
return true
}
return tset.Any(func(term *types.Term) bool {
switch typ := term.Type().Underlying().(type) {
case *types.Basic:
kind := typ.Kind()
return kind == types.Float32 || kind == types.Float64
case *types.Array:
return isFloat(typ.Elem())
case *types.Struct:
for field := range typ.Fields() {
if isFloat(field.Type()) {
return true
}
}
return false
default:
return false
}
})
}
// TODO(dh): this check ignores the existence of side-effects and
// happily flags fn() == fn() so far, we've had only two complains
// about false positives, and it's caught several bugs in real
// code.
//
// We special case functions from the math/rand package. Someone ran
// 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."
skipComparableCheck := func(c inspector.Cursor) bool {
op, ok := c.Node().(*ast.BinaryExpr)
if !ok {
return false
}
if clit, ok := op.X.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
return false
}
if clit, ok := op.Y.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
return false
}
// TODO(dh): we should probably skip ParenExprs, but users should
// probably not use unnecessary ParenExprs.
vspec, ok := c.Parent().Node().(*ast.ValueSpec)
if !ok {
return false
}
e, i := c.ParentEdge()
if e != edge.ValueSpec_Values {
return false
}
if vspec.Names[i].Name == "_" {
// `var _ = T{} == T{}` is permitted, as a compile-time
// check that T implements comparable.
return true
}
return false
}
for c := range code.Cursor(pass).Preorder((*ast.BinaryExpr)(nil)) {
node := c.Node()
op := node.(*ast.BinaryExpr)
switch op.Op {
case token.EQL, token.NEQ:
if skipComparableCheck(c) {
continue
}
case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
default:
// For some ops, such as + and *, it can make sense to
// have identical operands
continue
}
if isFloat(pass.TypesInfo.TypeOf(op.X)) {
// 'float <op> float' makes sense for several operators.
// We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead.
continue
}
if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
continue
}
if report.Render(pass, op.X) != report.Render(pass, op.Y) {
continue
}
l1, ok1 := op.X.(*ast.BasicLit)
l2, ok2 := op.Y.(*ast.BasicLit)
if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
// cgo generates the following function call:
// _cgoCheckPointer(_cgoBase0, 0 == 0) it uses 0 == 0
// instead of true in case the user shadowed the
// identifier. Ideally we'd restrict this exception to
// calls of _cgoCheckPointer, but it's not worth the
// hassle of keeping track of the stack. <lit> <op> <lit>
// are very rare to begin with, and we're mostly checking
// for them to catch typos such as 1 == 1 where the user
// meant to type i == 1. The odds of a false negative for
// 0 == 0 are slim.
continue
}
if expr, ok := op.X.(*ast.CallExpr); ok {
call := code.CallName(pass, expr)
switch call {
case "math/rand.Int",
"math/rand.Int31",
"math/rand.Int31n",
"math/rand.Int63",
"math/rand.Int63n",
"math/rand.Intn",
"math/rand.Uint32",
"math/rand.Uint64",
"math/rand.ExpFloat64",
"math/rand.Float32",
"math/rand.Float64",
"math/rand.NormFloat64",
"(*math/rand.Rand).Int",
"(*math/rand.Rand).Int31",
"(*math/rand.Rand).Int31n",
"(*math/rand.Rand).Int63",
"(*math/rand.Rand).Int63n",
"(*math/rand.Rand).Intn",
"(*math/rand.Rand).Uint32",
"(*math/rand.Rand).Uint64",
"(*math/rand.Rand).ExpFloat64",
"(*math/rand.Rand).Float32",
"(*math/rand.Rand).Float64",
"(*math/rand.Rand).NormFloat64",
"math/rand/v2.Int",
"math/rand/v2.Int32",
"math/rand/v2.Int32N",
"math/rand/v2.Int64",
"math/rand/v2.Int64N",
"math/rand/v2.IntN",
"math/rand/v2.N",
"math/rand/v2.Uint",
"math/rand/v2.Uint32",
"math/rand/v2.Uint32N",
"math/rand/v2.Uint64",
"math/rand/v2.Uint64N",
"math/rand/v2.UintN",
"math/rand/v2.ExpFloat64",
"math/rand/v2.Float32",
"math/rand/v2.Float64",
"math/rand/v2.NormFloat64",
"(*math/rand/v2.Rand).Int",
"(*math/rand/v2.Rand).Int32",
"(*math/rand/v2.Rand).Int32N",
"(*math/rand/v2.Rand).Int64",
"(*math/rand/v2.Rand).Int64N",
"(*math/rand/v2.Rand).IntN",
"(*math/rand/v2.Rand).N",
"(*math/rand/v2.Rand).Uint",
"(*math/rand/v2.Rand).Uint32",
"(*math/rand/v2.Rand).Uint32N",
"(*math/rand/v2.Rand).Uint64",
"(*math/rand/v2.Rand).Uint64N",
"(*math/rand/v2.Rand).UintN",
"(*math/rand/v2.Rand).ExpFloat64",
"(*math/rand/v2.Rand).Float32",
"(*math/rand/v2.Rand).Float64",
"(*math/rand/v2.Rand).NormFloat64":
continue
}
}
report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
}
return nil, nil
}