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