src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }