sa4004.go raw

   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) (interface{}, 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  }
 160