package sa2003 import ( "fmt" "go/types" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/internal/passes/buildir" "golang.org/x/tools/go/analysis" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA2003", Run: run, Requires: []*analysis.Analyzer{buildir.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { for _, block := range fn.Blocks { instrs := irutil.FilterDebug(block.Instrs) if len(instrs) < 2 { continue } for i, ins := range instrs[:len(instrs)-1] { call, ok := ins.(*ir.Call) if !ok { continue } if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { continue } nins, ok := instrs[i+1].(*ir.Defer) if !ok { continue } if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { continue } if call.Common().Args[0] != nins.Call.Args[0] { continue } name := shortCallName(call.Common()) alt := "" switch name { case "Lock": alt = "Unlock" case "RLock": alt = "RUnlock" } report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt)) } } } return nil, nil } func shortCallName(call *ir.CallCommon) string { if call.IsInvoke() { return "" } switch v := call.Value.(type) { case *ir.Function: fn, ok := v.Object().(*types.Func) if !ok { return "" } return fn.Name() case *ir.Builtin: return v.Name() } return "" }