sa2001.go (3172B)
1 package sa2001 2 3 import ( 4 "go/ast" 5 "go/types" 6 7 "honnef.co/go/tools/analysis/code" 8 "honnef.co/go/tools/analysis/lint" 9 "honnef.co/go/tools/analysis/report" 10 11 "golang.org/x/tools/go/analysis" 12 "golang.org/x/tools/go/analysis/passes/inspect" 13 ) 14 15 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 16 Analyzer: &analysis.Analyzer{ 17 Name: "SA2001", 18 Run: run, 19 Requires: []*analysis.Analyzer{inspect.Analyzer}, 20 }, 21 Doc: &lint.RawDocumentation{ 22 Title: `Empty critical section, did you mean to defer the unlock?`, 23 Text: `Empty critical sections of the kind 24 25 mu.Lock() 26 mu.Unlock() 27 28 are very often a typo, and the following was intended instead: 29 30 mu.Lock() 31 defer mu.Unlock() 32 33 Do note that sometimes empty critical sections can be useful, as a 34 form of signaling to wait on another goroutine. Many times, there are 35 simpler ways of achieving the same effect. When that isn't the case, 36 the code should be amply commented to avoid confusion. Combining such 37 comments with a \'//lint:ignore\' directive can be used to suppress this 38 rare false positive.`, 39 Since: "2017.1", 40 Severity: lint.SeverityWarning, 41 MergeIf: lint.MergeIfAny, 42 }, 43 }) 44 45 var Analyzer = SCAnalyzer.Analyzer 46 47 func run(pass *analysis.Pass) (any, error) { 48 if pass.Pkg.Path() == "sync_test" { 49 // exception for the sync package's tests 50 return nil, nil 51 } 52 53 // Initially it might seem like this check would be easier to 54 // implement using IR. After all, we're only checking for two 55 // consecutive method calls. In reality, however, there may be any 56 // number of other instructions between the lock and unlock, while 57 // still constituting an empty critical section. For example, 58 // given `m.x().Lock(); m.x().Unlock()`, there will be a call to 59 // x(). In the AST-based approach, this has a tiny potential for a 60 // false positive (the second call to x might be doing work that 61 // is protected by the mutex). In an IR-based approach, however, 62 // it would miss a lot of real bugs. 63 64 mutexParams := func(s ast.Stmt) (x ast.Expr, funcName string, ok bool) { 65 expr, ok := s.(*ast.ExprStmt) 66 if !ok { 67 return nil, "", false 68 } 69 call, ok := ast.Unparen(expr.X).(*ast.CallExpr) 70 if !ok { 71 return nil, "", false 72 } 73 sel, ok := call.Fun.(*ast.SelectorExpr) 74 if !ok { 75 return nil, "", false 76 } 77 78 fn, ok := pass.TypesInfo.ObjectOf(sel.Sel).(*types.Func) 79 if !ok { 80 return nil, "", false 81 } 82 sig := fn.Type().(*types.Signature) 83 if sig.Params().Len() != 0 || sig.Results().Len() != 0 { 84 return nil, "", false 85 } 86 87 return sel.X, fn.Name(), true 88 } 89 90 fn := func(node ast.Node) { 91 block := node.(*ast.BlockStmt) 92 if len(block.List) < 2 { 93 return 94 } 95 for i := range block.List[:len(block.List)-1] { 96 sel1, method1, ok1 := mutexParams(block.List[i]) 97 sel2, method2, ok2 := mutexParams(block.List[i+1]) 98 99 if !ok1 || !ok2 || report.Render(pass, sel1) != report.Render(pass, sel2) { 100 continue 101 } 102 if (method1 == "Lock" && method2 == "Unlock") || 103 (method1 == "RLock" && method2 == "RUnlock") { 104 report.Report(pass, block.List[i+1], "empty critical section") 105 } 106 } 107 } 108 code.Preorder(pass, fn, (*ast.BlockStmt)(nil)) 109 return nil, nil 110 }