src

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

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 }