github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4016/sa4016.go (about) 1 package sa4016 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/constant" 7 "go/token" 8 "go/types" 9 10 "github.com/amarpal/go-tools/analysis/code" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/go-tools/analysis/report" 13 "github.com/amarpal/go-tools/go/ast/astutil" 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: "SA4016", 23 Run: run, 24 Requires: []*analysis.Analyzer{inspect.Analyzer}, 25 }, 26 Doc: &lint.Documentation{ 27 Title: `Certain bitwise operations, such as \'x ^ 0\', do not do anything useful`, 28 Since: "2017.1", 29 Severity: lint.SeverityWarning, 30 MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants 31 }, 32 }) 33 34 var Analyzer = SCAnalyzer.Analyzer 35 36 func run(pass *analysis.Pass) (interface{}, error) { 37 fn := func(node ast.Node) { 38 binop := node.(*ast.BinaryExpr) 39 if !typeutil.All(pass.TypesInfo.TypeOf(binop), func(term *types.Term) bool { 40 b, ok := term.Type().Underlying().(*types.Basic) 41 if !ok { 42 return false 43 } 44 return (b.Info() & types.IsInteger) != 0 45 }) { 46 return 47 } 48 switch binop.Op { 49 case token.AND, token.OR, token.XOR: 50 default: 51 // we do not flag shifts because too often, x<<0 is part 52 // of a pattern, x<<0, x<<8, x<<16, ... 53 return 54 } 55 if y, ok := binop.Y.(*ast.Ident); ok { 56 obj, ok := pass.TypesInfo.ObjectOf(y).(*types.Const) 57 if !ok { 58 return 59 } 60 if obj.Pkg() != pass.Pkg { 61 // identifier was dot-imported 62 return 63 } 64 if v, _ := constant.Int64Val(obj.Val()); v != 0 { 65 return 66 } 67 path, _ := astutil.PathEnclosingInterval(code.File(pass, obj), obj.Pos(), obj.Pos()) 68 if len(path) < 2 { 69 return 70 } 71 spec, ok := path[1].(*ast.ValueSpec) 72 if !ok { 73 return 74 } 75 if len(spec.Names) != 1 || len(spec.Values) != 1 { 76 // TODO(dh): we could support this 77 return 78 } 79 ident, ok := spec.Values[0].(*ast.Ident) 80 if !ok { 81 return 82 } 83 if !isIota(pass.TypesInfo.ObjectOf(ident)) { 84 return 85 } 86 switch binop.Op { 87 case token.AND: 88 report.Report(pass, node, 89 fmt.Sprintf("%s always equals 0; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.Y), report.Render(pass, binop.Y))) 90 case token.OR, token.XOR: 91 report.Report(pass, node, 92 fmt.Sprintf("%s always equals %s; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.X), report.Render(pass, binop.Y), report.Render(pass, binop.Y))) 93 } 94 } else if code.IsIntegerLiteral(pass, binop.Y, constant.MakeInt64(0)) { 95 switch binop.Op { 96 case token.AND: 97 report.Report(pass, node, fmt.Sprintf("%s always equals 0", report.Render(pass, binop))) 98 case token.OR, token.XOR: 99 report.Report(pass, node, fmt.Sprintf("%s always equals %s", report.Render(pass, binop), report.Render(pass, binop.X))) 100 } 101 } 102 } 103 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) 104 return nil, nil 105 } 106 107 func isIota(obj types.Object) bool { 108 if obj.Name() != "iota" { 109 return false 110 } 111 c, ok := obj.(*types.Const) 112 if !ok { 113 return false 114 } 115 return c.Pkg() == nil 116 }