sa4016.go raw
1 package sa4016
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/constant"
7 "go/token"
8 "go/types"
9
10 "honnef.co/go/tools/analysis/code"
11 "honnef.co/go/tools/analysis/lint"
12 "honnef.co/go/tools/analysis/report"
13 "honnef.co/go/tools/go/ast/astutil"
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 )
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.RawDocumentation{
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 }
117