src

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

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 }