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