src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa4016.go (3261B)


      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) (any, 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 }