package sa5002 import ( "go/ast" "go/constant" "go/types" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA5002", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `The empty for loop (\"for {}\") spins and can block the scheduler`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { fn := func(node ast.Node) { loop := node.(*ast.ForStmt) if len(loop.Body.List) != 0 || loop.Post != nil { return } if loop.Init != nil { // TODO(dh): this isn't strictly necessary, it just makes // the check easier. return } // An empty loop is bad news in two cases: 1) The loop has no // condition. In that case, it's just a loop that spins // forever and as fast as it can, keeping a core busy. 2) The // loop condition only consists of variable or field reads and // operators on those. The only way those could change their // value is with unsynchronised access, which constitutes a // data race. // // If the condition contains any function calls, its behaviour // is dynamic and the loop might terminate. Similarly for // channel receives. if loop.Cond != nil { if code.MayHaveSideEffects(pass, loop.Cond, nil) { return } if ident, ok := loop.Cond.(*ast.Ident); ok { if k, ok := pass.TypesInfo.ObjectOf(ident).(*types.Const); ok { if !constant.BoolVal(k.Val()) { // don't flag `for false {}` loops. They're a debug aid. return } } } report.Report(pass, loop, "loop condition never changes or has a race condition") } report.Report(pass, loop, "this loop will spin, using 100% CPU", report.ShortRange()) } code.Preorder(pass, fn, (*ast.ForStmt)(nil)) return nil, nil }