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 }