package sa4004 import ( "go/ast" "go/token" "go/types" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/types/typeutil" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA4004", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `The loop exits unconditionally after one iteration`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAll, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { // This check detects some, but not all unconditional loop exits. // We give up in the following cases: // // - a goto anywhere in the loop. The goto might skip over our // return, and we don't check that it doesn't. // // - any nested, unlabelled continue, even if it is in another // loop or closure. fn := func(node ast.Node) { var body *ast.BlockStmt switch fn := node.(type) { case *ast.FuncDecl: body = fn.Body case *ast.FuncLit: body = fn.Body default: lint.ExhaustiveTypeSwitch(node) } if body == nil { return } labels := map[types.Object]ast.Stmt{} ast.Inspect(body, func(node ast.Node) bool { label, ok := node.(*ast.LabeledStmt) if !ok { return true } labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt return true }) ast.Inspect(body, func(node ast.Node) bool { var loop ast.Node var body *ast.BlockStmt switch node := node.(type) { case *ast.ForStmt: body = node.Body loop = node case *ast.RangeStmt: ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool { switch term.Type().Underlying().(type) { case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array: return true case *types.Map: // looping once over a map is a valid pattern for // getting an arbitrary element. return false case *types.Signature: // we have no idea what semantics the function implements return false default: lint.ExhaustiveTypeSwitch(term.Type().Underlying()) return false } }) if !ok { return true } body = node.Body loop = node default: return true } if len(body.List) < 2 { // TODO(dh): is this check needed? when body.List < 2, // then we can't find both an unconditional exit and a // branching statement (if, ...). and we don't flag // unconditional exits if there has been no branching // in the loop body. // avoid flagging the somewhat common pattern of using // a range loop to get the first element in a slice, // or the first rune in a string. return true } var unconditionalExit ast.Node hasBranching := false for _, stmt := range body.List { switch stmt := stmt.(type) { case *ast.BranchStmt: switch stmt.Tok { case token.BREAK: if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { unconditionalExit = stmt } case token.CONTINUE: if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { unconditionalExit = nil return false } } case *ast.ReturnStmt: unconditionalExit = stmt case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt: hasBranching = true } } if unconditionalExit == nil || !hasBranching { return false } ast.Inspect(body, func(node ast.Node) bool { if branch, ok := node.(*ast.BranchStmt); ok { switch branch.Tok { case token.GOTO: unconditionalExit = nil return false case token.CONTINUE: if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop { return true } unconditionalExit = nil return false } } return true }) if unconditionalExit != nil { report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated") } return true }) } code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) return nil, nil }