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  }