sa2003.go (2348B)
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 Text: `Deferring a call to \'Lock\' immediately after locking is almost always 25 a typo. For example: 26 27 mu.Lock() 28 defer mu.Lock() 29 30 While this does not strictly guarantee a deadlock depending on how the 31 surrounding code is structured, it is highly likely to be a mistake. 32 The intended code was likely this: 33 34 mu.Lock() 35 defer mu.Unlock()`, 36 Since: "2017.1", 37 Severity: lint.SeverityWarning, 38 MergeIf: lint.MergeIfAny, 39 }, 40 }) 41 42 var Analyzer = SCAnalyzer.Analyzer 43 44 func run(pass *analysis.Pass) (any, error) { 45 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 46 for _, block := range fn.Blocks { 47 instrs := block.Instrs 48 if len(instrs) < 2 { 49 continue 50 } 51 for i, ins := range instrs[:len(instrs)-1] { 52 call, ok := ins.(*ir.Call) 53 if !ok { 54 continue 55 } 56 if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { 57 continue 58 } 59 nins, ok := instrs[i+1].(*ir.Defer) 60 if !ok { 61 continue 62 } 63 if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { 64 continue 65 } 66 if call.Common().Args[0] != nins.Call.Args[0] { 67 continue 68 } 69 name := shortCallName(call.Common()) 70 alt := "" 71 switch name { 72 case "Lock": 73 alt = "Unlock" 74 case "RLock": 75 alt = "RUnlock" 76 } 77 report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt)) 78 } 79 } 80 } 81 return nil, nil 82 } 83 84 func shortCallName(call *ir.CallCommon) string { 85 if call.IsInvoke() { 86 return "" 87 } 88 switch v := call.Value.(type) { 89 case *ir.Function: 90 fn, ok := v.Object().(*types.Func) 91 if !ok { 92 return "" 93 } 94 return fn.Name() 95 case *ir.Builtin: 96 return v.Name() 97 } 98 return "" 99 }