sa1029.go raw

   1  package sa1029
   2  
   3  import (
   4  	"fmt"
   5  	"go/types"
   6  
   7  	"honnef.co/go/tools/analysis/callcheck"
   8  	"honnef.co/go/tools/analysis/lint"
   9  	"honnef.co/go/tools/internal/passes/buildir"
  10  
  11  	"golang.org/x/tools/go/analysis"
  12  )
  13  
  14  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  15  	Analyzer: &analysis.Analyzer{
  16  		Name:     "SA1029",
  17  		Requires: []*analysis.Analyzer{buildir.Analyzer},
  18  		Run:      callcheck.Analyzer(checkWithValueKeyRules),
  19  	},
  20  	Doc: &lint.RawDocumentation{
  21  		Title: `Inappropriate key in call to \'context.WithValue\'`,
  22  		Text: `The provided key must be comparable and should not be
  23  of type \'string\' or any other built-in type to avoid collisions between
  24  packages using context. Users of \'WithValue\' should define their own
  25  types for keys.
  26  
  27  To avoid allocating when assigning to an \'interface{}\',
  28  context keys often have concrete type \'struct{}\'. Alternatively,
  29  exported context key variables' static type should be a pointer or
  30  interface.`,
  31  		Since:    "2020.1",
  32  		Severity: lint.SeverityWarning,
  33  		MergeIf:  lint.MergeIfAny,
  34  	},
  35  })
  36  
  37  var Analyzer = SCAnalyzer.Analyzer
  38  
  39  var checkWithValueKeyRules = map[string]callcheck.Check{
  40  	"context.WithValue": checkWithValueKey,
  41  }
  42  
  43  func checkWithValueKey(call *callcheck.Call) {
  44  	arg := call.Args[1]
  45  	T := arg.Value.Value.Type()
  46  	if typ, ok := types.Unalias(T).(*types.Basic); ok {
  47  		if _, ok := T.(*types.Alias); ok {
  48  			arg.Invalid(
  49  				fmt.Sprintf("should not use built-in type %s (via alias %s) as key for value; define your own type to avoid collisions", typ, types.TypeString(T, types.RelativeTo(call.Pass.Pkg))))
  50  		} else {
  51  			arg.Invalid(
  52  				fmt.Sprintf("should not use built-in type %s as key for value; define your own type to avoid collisions", typ))
  53  		}
  54  	}
  55  	// TODO(dh): we should probably flag all anonymous structs, as they all risk collisions
  56  	if s, ok := T.(*types.Struct); ok && s.NumFields() == 0 {
  57  		arg.Invalid("should not use empty anonymous struct as key for value; define your own type to avoid collisions")
  58  	} else if !types.Comparable(T) {
  59  		arg.Invalid(fmt.Sprintf("keys used with context.WithValue must be comparable, but type %s is not comparable", T))
  60  	}
  61  }
  62