sa2003.go raw

   1  package sa2003
   2  
   3  import (
   4  	"fmt"
   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/ir/irutil"
  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:     "SA2003",
  19  		Run:      run,
  20  		Requires: []*analysis.Analyzer{buildir.Analyzer},
  21  	},
  22  	Doc: &lint.RawDocumentation{
  23  		Title:    `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`,
  24  		Since:    "2017.1",
  25  		Severity: lint.SeverityWarning,
  26  		MergeIf:  lint.MergeIfAny,
  27  	},
  28  })
  29  
  30  var Analyzer = SCAnalyzer.Analyzer
  31  
  32  func run(pass *analysis.Pass) (interface{}, error) {
  33  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  34  		for _, block := range fn.Blocks {
  35  			instrs := irutil.FilterDebug(block.Instrs)
  36  			if len(instrs) < 2 {
  37  				continue
  38  			}
  39  			for i, ins := range instrs[:len(instrs)-1] {
  40  				call, ok := ins.(*ir.Call)
  41  				if !ok {
  42  					continue
  43  				}
  44  				if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
  45  					continue
  46  				}
  47  				nins, ok := instrs[i+1].(*ir.Defer)
  48  				if !ok {
  49  					continue
  50  				}
  51  				if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
  52  					continue
  53  				}
  54  				if call.Common().Args[0] != nins.Call.Args[0] {
  55  					continue
  56  				}
  57  				name := shortCallName(call.Common())
  58  				alt := ""
  59  				switch name {
  60  				case "Lock":
  61  					alt = "Unlock"
  62  				case "RLock":
  63  					alt = "RUnlock"
  64  				}
  65  				report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt))
  66  			}
  67  		}
  68  	}
  69  	return nil, nil
  70  }
  71  
  72  func shortCallName(call *ir.CallCommon) string {
  73  	if call.IsInvoke() {
  74  		return ""
  75  	}
  76  	switch v := call.Value.(type) {
  77  	case *ir.Function:
  78  		fn, ok := v.Object().(*types.Func)
  79  		if !ok {
  80  			return ""
  81  		}
  82  		return fn.Name()
  83  	case *ir.Builtin:
  84  		return v.Name()
  85  	}
  86  	return ""
  87  }
  88