sa4004.go (4321B)
1 package sa4004 2 3 import ( 4 "go/ast" 5 "go/token" 6 "go/types" 7 8 "honnef.co/go/tools/analysis/code" 9 "honnef.co/go/tools/analysis/lint" 10 "honnef.co/go/tools/analysis/report" 11 "honnef.co/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: "SA4004", 20 Run: run, 21 Requires: []*analysis.Analyzer{inspect.Analyzer}, 22 }, 23 Doc: &lint.RawDocumentation{ 24 Title: `The loop exits unconditionally after one iteration`, 25 Since: "2017.1", 26 Severity: lint.SeverityWarning, 27 MergeIf: lint.MergeIfAll, 28 }, 29 }) 30 31 var Analyzer = SCAnalyzer.Analyzer 32 33 func run(pass *analysis.Pass) (any, error) { 34 // This check detects some, but not all unconditional loop exits. 35 // We give up in the following cases: 36 // 37 // - a goto anywhere in the loop. The goto might skip over our 38 // return, and we don't check that it doesn't. 39 // 40 // - any nested, unlabelled continue, even if it is in another 41 // loop or closure. 42 fn := func(node ast.Node) { 43 var body *ast.BlockStmt 44 switch fn := node.(type) { 45 case *ast.FuncDecl: 46 body = fn.Body 47 case *ast.FuncLit: 48 body = fn.Body 49 default: 50 lint.ExhaustiveTypeSwitch(node) 51 } 52 if body == nil { 53 return 54 } 55 labels := map[types.Object]ast.Stmt{} 56 ast.Inspect(body, func(node ast.Node) bool { 57 label, ok := node.(*ast.LabeledStmt) 58 if !ok { 59 return true 60 } 61 labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt 62 return true 63 }) 64 65 ast.Inspect(body, func(node ast.Node) bool { 66 var loop ast.Node 67 var body *ast.BlockStmt 68 switch node := node.(type) { 69 case *ast.ForStmt: 70 body = node.Body 71 loop = node 72 case *ast.RangeStmt: 73 ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool { 74 switch term.Type().Underlying().(type) { 75 case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array: 76 return true 77 case *types.Map: 78 // looping once over a map is a valid pattern for 79 // getting an arbitrary element. 80 return false 81 case *types.Signature: 82 // we have no idea what semantics the function implements 83 return false 84 default: 85 lint.ExhaustiveTypeSwitch(term.Type().Underlying()) 86 return false 87 } 88 }) 89 if !ok { 90 return true 91 } 92 body = node.Body 93 loop = node 94 default: 95 return true 96 } 97 if len(body.List) < 2 { 98 // TODO(dh): is this check needed? when body.List < 2, 99 // then we can't find both an unconditional exit and a 100 // branching statement (if, ...). and we don't flag 101 // unconditional exits if there has been no branching 102 // in the loop body. 103 104 // avoid flagging the somewhat common pattern of using 105 // a range loop to get the first element in a slice, 106 // or the first rune in a string. 107 return true 108 } 109 var unconditionalExit ast.Node 110 hasBranching := false 111 for _, stmt := range body.List { 112 switch stmt := stmt.(type) { 113 case *ast.BranchStmt: 114 switch stmt.Tok { 115 case token.BREAK: 116 if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { 117 unconditionalExit = stmt 118 } 119 case token.CONTINUE: 120 if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { 121 unconditionalExit = nil 122 return false 123 } 124 } 125 case *ast.ReturnStmt: 126 unconditionalExit = stmt 127 case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt: 128 hasBranching = true 129 } 130 } 131 if unconditionalExit == nil || !hasBranching { 132 return false 133 } 134 ast.Inspect(body, func(node ast.Node) bool { 135 if branch, ok := node.(*ast.BranchStmt); ok { 136 137 switch branch.Tok { 138 case token.GOTO: 139 unconditionalExit = nil 140 return false 141 case token.CONTINUE: 142 if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop { 143 return true 144 } 145 unconditionalExit = nil 146 return false 147 } 148 } 149 return true 150 }) 151 if unconditionalExit != nil { 152 report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated") 153 } 154 return true 155 }) 156 } 157 code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) 158 return nil, nil 159 }