github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa9001/sa9001.go (about)

     1  package sa9001
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/go/types/typeutil"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  )
    16  
    17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    18  	Analyzer: &analysis.Analyzer{
    19  		Name:     "SA9001",
    20  		Run:      run,
    21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    22  	},
    23  	Doc: &lint.Documentation{
    24  		Title:    `Defers in range loops may not run when you expect them to`,
    25  		Since:    "2017.1",
    26  		Severity: lint.SeverityWarning,
    27  		MergeIf:  lint.MergeIfAny,
    28  	},
    29  })
    30  
    31  var Analyzer = SCAnalyzer.Analyzer
    32  
    33  func run(pass *analysis.Pass) (interface{}, error) {
    34  	fn := func(node ast.Node) {
    35  		loop := node.(*ast.RangeStmt)
    36  		typ := pass.TypesInfo.TypeOf(loop.X)
    37  		_, ok := typeutil.CoreType(typ).(*types.Chan)
    38  		if !ok {
    39  			return
    40  		}
    41  
    42  		stmts := []*ast.DeferStmt{}
    43  		exits := false
    44  		fn2 := func(node ast.Node) bool {
    45  			switch stmt := node.(type) {
    46  			case *ast.DeferStmt:
    47  				stmts = append(stmts, stmt)
    48  			case *ast.FuncLit:
    49  				// Don't look into function bodies
    50  				return false
    51  			case *ast.ReturnStmt:
    52  				exits = true
    53  			case *ast.BranchStmt:
    54  				exits = node.(*ast.BranchStmt).Tok == token.BREAK
    55  			}
    56  			return true
    57  		}
    58  		ast.Inspect(loop.Body, fn2)
    59  
    60  		if exits {
    61  			return
    62  		}
    63  		for _, stmt := range stmts {
    64  			report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed")
    65  		}
    66  	}
    67  	code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
    68  	return nil, nil
    69  }