src

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

sa6001.go (3104B)


      1 package sa6001
      2 
      3 import (
      4 	"go/ast"
      5 	"go/types"
      6 
      7 	"honnef.co/go/tools/analysis/lint"
      8 	"honnef.co/go/tools/analysis/report"
      9 	"honnef.co/go/tools/go/ir"
     10 	"honnef.co/go/tools/go/types/typeutil"
     11 	"honnef.co/go/tools/internal/passes/buildir"
     12 
     13 	"golang.org/x/tools/go/analysis"
     14 )
     15 
     16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     17 	Analyzer: &analysis.Analyzer{
     18 		Name:     "SA6001",
     19 		Run:      run,
     20 		Requires: []*analysis.Analyzer{buildir.Analyzer},
     21 	},
     22 	Doc: &lint.RawDocumentation{
     23 		Title: `Missing an optimization opportunity when indexing maps by byte slices`,
     24 
     25 		Text: `Map keys must be comparable, which precludes the use of byte slices.
     26 This usually leads to using string keys and converting byte slices to
     27 strings.
     28 
     29 Normally, a conversion of a byte slice to a string needs to copy the data and
     30 causes allocations. The compiler, however, recognizes \'m[string(b)]\' and
     31 uses the data of \'b\' directly, without copying it, because it knows that
     32 the data can't change during the map lookup. This leads to the
     33 counter-intuitive situation that
     34 
     35     k := string(b)
     36     println(m[k])
     37     println(m[k])
     38 
     39 will be less efficient than
     40 
     41     println(m[string(b)])
     42     println(m[string(b)])
     43 
     44 because the first version needs to copy and allocate, while the second
     45 one does not.
     46 
     47 For some history on this optimization, check out commit
     48 f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`,
     49 		Since:    "2017.1",
     50 		Severity: lint.SeverityWarning,
     51 		MergeIf:  lint.MergeIfAny,
     52 	},
     53 })
     54 
     55 var Analyzer = SCAnalyzer.Analyzer
     56 
     57 func run(pass *analysis.Pass) (any, error) {
     58 	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
     59 		for _, b := range fn.Blocks {
     60 		insLoop:
     61 			for _, ins := range b.Instrs {
     62 				var fromType types.Type
     63 				var toType types.Type
     64 
     65 				// find []byte -> string conversions
     66 				switch ins := ins.(type) {
     67 				case *ir.Convert:
     68 					fromType = ins.X.Type()
     69 					toType = ins.Type()
     70 				case *ir.MultiConvert:
     71 					fromType = ins.X.Type()
     72 					toType = ins.Type()
     73 				default:
     74 					continue
     75 				}
     76 				if toType != types.Universe.Lookup("string").Type() {
     77 					continue
     78 				}
     79 				tset := typeutil.NewTypeSet(fromType)
     80 				// If at least one of the types is []byte, then it's more efficient to inline the conversion
     81 				if !tset.Any(func(term *types.Term) bool {
     82 					s, ok := term.Type().Underlying().(*types.Slice)
     83 					return ok && s.Elem().Underlying() == types.Universe.Lookup("byte").Type()
     84 				}) {
     85 					continue
     86 				}
     87 				refs := ins.Referrers()
     88 				anyUses := false
     89 				for _, ref := range *refs {
     90 					switch ref := ref.(type) {
     91 					case *ir.MapLookup:
     92 						anyUses = true
     93 						if src, ok := ref.Source().(*ast.IndexExpr); ok {
     94 							if _, ok := src.Index.(*ast.Ident); !ok {
     95 								continue insLoop
     96 							}
     97 						} else {
     98 							continue insLoop
     99 						}
    100 					default:
    101 						// the string is used somewhere else than a
    102 						// map lookup
    103 						continue insLoop
    104 					}
    105 				}
    106 
    107 				if !anyUses {
    108 					continue
    109 				}
    110 				report.Report(pass, ins, "m[string(key)] would be more efficient than k := string(key); m[k]")
    111 			}
    112 		}
    113 	}
    114 	return nil, nil
    115 }