github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4000/sa4000.go (about) 1 package sa4000 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "go/types" 8 "reflect" 9 10 "github.com/amarpal/go-tools/analysis/code" 11 "github.com/amarpal/go-tools/analysis/facts/generated" 12 "github.com/amarpal/go-tools/analysis/lint" 13 "github.com/amarpal/go-tools/analysis/report" 14 "github.com/amarpal/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: "SA4000", 23 Run: run, 24 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 25 }, 26 Doc: &lint.Documentation{ 27 Title: `Binary operator has identical expressions on both sides`, 28 Since: "2017.1", 29 Severity: lint.SeverityWarning, 30 MergeIf: lint.MergeIfAny, 31 }, 32 }) 33 34 var Analyzer = SCAnalyzer.Analyzer 35 36 func run(pass *analysis.Pass) (interface{}, error) { 37 var isFloat func(T types.Type) bool 38 isFloat = func(T types.Type) bool { 39 tset := typeutil.NewTypeSet(T) 40 if len(tset.Terms) == 0 { 41 // no terms, so floats are a possibility 42 return true 43 } 44 return tset.Any(func(term *types.Term) bool { 45 switch typ := term.Type().Underlying().(type) { 46 case *types.Basic: 47 kind := typ.Kind() 48 return kind == types.Float32 || kind == types.Float64 49 case *types.Array: 50 return isFloat(typ.Elem()) 51 case *types.Struct: 52 for i := 0; i < typ.NumFields(); i++ { 53 if !isFloat(typ.Field(i).Type()) { 54 return false 55 } 56 } 57 return true 58 default: 59 return false 60 } 61 }) 62 } 63 64 // TODO(dh): this check ignores the existence of side-effects and 65 // happily flags fn() == fn() – so far, we've had nobody complain 66 // about a false positive, and it's caught several bugs in real 67 // code. 68 // 69 // We special case functions from the math/rand package. Someone ran 70 // 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." 71 fn := func(node ast.Node) { 72 op := node.(*ast.BinaryExpr) 73 switch op.Op { 74 case token.EQL, token.NEQ: 75 case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT, 76 token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ: 77 default: 78 // For some ops, such as + and *, it can make sense to 79 // have identical operands 80 return 81 } 82 83 if isFloat(pass.TypesInfo.TypeOf(op.X)) { 84 // 'float <op> float' makes sense for several operators. 85 // We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead. 86 return 87 } 88 89 if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) { 90 return 91 } 92 if report.Render(pass, op.X) != report.Render(pass, op.Y) { 93 return 94 } 95 l1, ok1 := op.X.(*ast.BasicLit) 96 l2, ok2 := op.Y.(*ast.BasicLit) 97 if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) { 98 // cgo generates the following function call: 99 // _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0 100 // instead of true in case the user shadowed the 101 // identifier. Ideally we'd restrict this exception to 102 // calls of _cgoCheckPointer, but it's not worth the 103 // hassle of keeping track of the stack. <lit> <op> <lit> 104 // are very rare to begin with, and we're mostly checking 105 // for them to catch typos such as 1 == 1 where the user 106 // meant to type i == 1. The odds of a false negative for 107 // 0 == 0 are slim. 108 return 109 } 110 111 if expr, ok := op.X.(*ast.CallExpr); ok { 112 call := code.CallName(pass, expr) 113 switch call { 114 case "math/rand.Int", 115 "math/rand.Int31", 116 "math/rand.Int31n", 117 "math/rand.Int63", 118 "math/rand.Int63n", 119 "math/rand.Intn", 120 "math/rand.Uint32", 121 "math/rand.Uint64", 122 "math/rand.ExpFloat64", 123 "math/rand.Float32", 124 "math/rand.Float64", 125 "math/rand.NormFloat64", 126 "(*math/rand.Rand).Int", 127 "(*math/rand.Rand).Int31", 128 "(*math/rand.Rand).Int31n", 129 "(*math/rand.Rand).Int63", 130 "(*math/rand.Rand).Int63n", 131 "(*math/rand.Rand).Intn", 132 "(*math/rand.Rand).Uint32", 133 "(*math/rand.Rand).Uint64", 134 "(*math/rand.Rand).ExpFloat64", 135 "(*math/rand.Rand).Float32", 136 "(*math/rand.Rand).Float64", 137 "(*math/rand.Rand).NormFloat64": 138 return 139 } 140 } 141 142 report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op)) 143 } 144 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) 145 return nil, nil 146 }