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 }