sa1032.go raw

   1  package sa1032
   2  
   3  import (
   4  	"honnef.co/go/tools/analysis/callcheck"
   5  	"honnef.co/go/tools/analysis/lint"
   6  	"honnef.co/go/tools/go/ir"
   7  	"honnef.co/go/tools/internal/passes/buildir"
   8  
   9  	"golang.org/x/tools/go/analysis"
  10  )
  11  
  12  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  13  	Analyzer: &analysis.Analyzer{
  14  		Name:     "SA1032",
  15  		Requires: []*analysis.Analyzer{buildir.Analyzer},
  16  		Run:      callcheck.Analyzer(rules),
  17  	},
  18  	Doc: &lint.RawDocumentation{
  19  		Title: `Wrong order of arguments to \'errors.Is\'`,
  20  		Text: `
  21  The first argument of the function \'errors.Is\' is the error
  22  that we have and the second argument is the error we're trying to match against.
  23  For example:
  24  
  25  	if errors.Is(err, io.EOF) { ... }
  26  
  27  This check detects some cases where the two arguments have been swapped. It
  28  flags any calls where the first argument is referring to a package-level error
  29  variable, such as
  30  
  31  	if errors.Is(io.EOF, err) { /* this is wrong */ }`,
  32  		Since:    "2024.1",
  33  		Severity: lint.SeverityError,
  34  		MergeIf:  lint.MergeIfAny,
  35  	},
  36  })
  37  
  38  var Analyzer = SCAnalyzer.Analyzer
  39  
  40  var rules = map[string]callcheck.Check{
  41  	"errors.Is": validateIs,
  42  }
  43  
  44  func validateIs(call *callcheck.Call) {
  45  	if len(call.Args) != 2 {
  46  		return
  47  	}
  48  
  49  	global := func(arg *callcheck.Argument) *ir.Global {
  50  		v, ok := arg.Value.Value.(*ir.Load)
  51  		if !ok {
  52  			return nil
  53  		}
  54  		g, _ := v.X.(*ir.Global)
  55  		return g
  56  	}
  57  
  58  	x, y := call.Args[0], call.Args[1]
  59  	gx := global(x)
  60  	if gx == nil {
  61  		return
  62  	}
  63  
  64  	if pkgx := gx.Package().Pkg; pkgx != nil && pkgx.Path() != call.Pass.Pkg.Path() {
  65  		// x is a global that's not in this package
  66  
  67  		if gy := global(y); gy != nil {
  68  			if pkgy := gy.Package().Pkg; pkgy != nil && pkgy.Path() != call.Pass.Pkg.Path() {
  69  				// Both arguments refer to globals that aren't in this package. This can
  70  				// genuinely happen for external tests that check that one error "is"
  71  				// another one. net/http's external tests, for example, do
  72  				// `errors.Is(http.ErrNotSupported, errors.ErrUnsupported)`.
  73  				return
  74  			}
  75  		}
  76  
  77  		call.Invalid("arguments have the wrong order")
  78  	}
  79  }
  80