labels.go raw

   1  // Copyright 2013 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package types
   6  
   7  import (
   8  	"go/ast"
   9  	"go/token"
  10  	. "internal/types/errors"
  11  	"slices"
  12  )
  13  
  14  // labels checks correct label use in body.
  15  func (check *Checker) labels(body *ast.BlockStmt) {
  16  	// set of all labels in this body
  17  	all := NewScope(nil, body.Pos(), body.End(), "label")
  18  
  19  	fwdJumps := check.blockBranches(all, nil, nil, body.List)
  20  
  21  	// If there are any forward jumps left, no label was found for
  22  	// the corresponding goto statements. Either those labels were
  23  	// never defined, or they are inside blocks and not reachable
  24  	// for the respective gotos.
  25  	for _, jmp := range fwdJumps {
  26  		var msg string
  27  		var code Code
  28  		name := jmp.Label.Name
  29  		if alt := all.Lookup(name); alt != nil {
  30  			msg = "goto %s jumps into block"
  31  			code = JumpIntoBlock
  32  			alt.(*Label).used = true // avoid another error
  33  		} else {
  34  			msg = "label %s not declared"
  35  			code = UndeclaredLabel
  36  		}
  37  		check.errorf(jmp.Label, code, msg, name)
  38  	}
  39  
  40  	// spec: "It is illegal to define a label that is never used."
  41  	for name, obj := range all.elems {
  42  		obj = resolve(name, obj)
  43  		if lbl := obj.(*Label); !lbl.used {
  44  			check.softErrorf(lbl, UnusedLabel, "label %s declared and not used", lbl.name)
  45  		}
  46  	}
  47  }
  48  
  49  // A block tracks label declarations in a block and its enclosing blocks.
  50  type block struct {
  51  	parent *block                      // enclosing block
  52  	lstmt  *ast.LabeledStmt            // labeled statement to which this block belongs, or nil
  53  	labels map[string]*ast.LabeledStmt // allocated lazily
  54  }
  55  
  56  // insert records a new label declaration for the current block.
  57  // The label must not have been declared before in any block.
  58  func (b *block) insert(s *ast.LabeledStmt) {
  59  	name := s.Label.Name
  60  	if debug {
  61  		assert(b.gotoTarget(name) == nil)
  62  	}
  63  	labels := b.labels
  64  	if labels == nil {
  65  		labels = make(map[string]*ast.LabeledStmt)
  66  		b.labels = labels
  67  	}
  68  	labels[name] = s
  69  }
  70  
  71  // gotoTarget returns the labeled statement in the current
  72  // or an enclosing block with the given label name, or nil.
  73  func (b *block) gotoTarget(name string) *ast.LabeledStmt {
  74  	for s := b; s != nil; s = s.parent {
  75  		if t := s.labels[name]; t != nil {
  76  			return t
  77  		}
  78  	}
  79  	return nil
  80  }
  81  
  82  // enclosingTarget returns the innermost enclosing labeled
  83  // statement with the given label name, or nil.
  84  func (b *block) enclosingTarget(name string) *ast.LabeledStmt {
  85  	for s := b; s != nil; s = s.parent {
  86  		if t := s.lstmt; t != nil && t.Label.Name == name {
  87  			return t
  88  		}
  89  	}
  90  	return nil
  91  }
  92  
  93  // blockBranches processes a block's statement list and returns the set of outgoing forward jumps.
  94  // all is the scope of all declared labels, parent the set of labels declared in the immediately
  95  // enclosing block, and lstmt is the labeled statement this block is associated with (or nil).
  96  func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *ast.LabeledStmt, list []ast.Stmt) []*ast.BranchStmt {
  97  	b := &block{parent: parent, lstmt: lstmt}
  98  
  99  	var (
 100  		varDeclPos         token.Pos
 101  		fwdJumps, badJumps []*ast.BranchStmt
 102  	)
 103  
 104  	// All forward jumps jumping over a variable declaration are possibly
 105  	// invalid (they may still jump out of the block and be ok).
 106  	// recordVarDecl records them for the given position.
 107  	recordVarDecl := func(pos token.Pos) {
 108  		varDeclPos = pos
 109  		badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps
 110  	}
 111  
 112  	jumpsOverVarDecl := func(jmp *ast.BranchStmt) bool {
 113  		return varDeclPos.IsValid() && slices.Contains(badJumps, jmp)
 114  	}
 115  
 116  	blockBranches := func(lstmt *ast.LabeledStmt, list []ast.Stmt) {
 117  		// Unresolved forward jumps inside the nested block
 118  		// become forward jumps in the current block.
 119  		fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, list)...)
 120  	}
 121  
 122  	var stmtBranches func(*ast.LabeledStmt, ast.Stmt)
 123  	stmtBranches = func(lstmt *ast.LabeledStmt, s ast.Stmt) {
 124  		switch s := s.(type) {
 125  		case *ast.DeclStmt:
 126  			if d, _ := s.Decl.(*ast.GenDecl); d != nil && d.Tok == token.VAR {
 127  				recordVarDecl(d.Pos())
 128  			}
 129  
 130  		case *ast.LabeledStmt:
 131  			// declare non-blank label
 132  			if name := s.Label.Name; name != "_" {
 133  				lbl := NewLabel(s.Label.Pos(), check.pkg, name)
 134  				if alt := all.Insert(lbl); alt != nil {
 135  					err := check.newError(DuplicateLabel)
 136  					err.soft = true
 137  					err.addf(lbl, "label %s already declared", name)
 138  					err.addAltDecl(alt)
 139  					err.report()
 140  					// ok to continue
 141  				} else {
 142  					b.insert(s)
 143  					check.recordDef(s.Label, lbl)
 144  				}
 145  				// resolve matching forward jumps and remove them from fwdJumps
 146  				i := 0
 147  				for _, jmp := range fwdJumps {
 148  					if jmp.Label.Name == name {
 149  						// match
 150  						lbl.used = true
 151  						check.recordUse(jmp.Label, lbl)
 152  						if jumpsOverVarDecl(jmp) {
 153  							check.softErrorf(
 154  								jmp.Label,
 155  								JumpOverDecl,
 156  								"goto %s jumps over variable declaration at line %d",
 157  								name,
 158  								check.fset.Position(varDeclPos).Line,
 159  							)
 160  							// ok to continue
 161  						}
 162  					} else {
 163  						// no match - record new forward jump
 164  						fwdJumps[i] = jmp
 165  						i++
 166  					}
 167  				}
 168  				fwdJumps = fwdJumps[:i]
 169  				lstmt = s
 170  			}
 171  			stmtBranches(lstmt, s.Stmt)
 172  
 173  		case *ast.BranchStmt:
 174  			if s.Label == nil {
 175  				return // checked in 1st pass (check.stmt)
 176  			}
 177  
 178  			// determine and validate target
 179  			name := s.Label.Name
 180  			switch s.Tok {
 181  			case token.BREAK:
 182  				// spec: "If there is a label, it must be that of an enclosing
 183  				// "for", "switch", or "select" statement, and that is the one
 184  				// whose execution terminates."
 185  				valid := false
 186  				if t := b.enclosingTarget(name); t != nil {
 187  					switch t.Stmt.(type) {
 188  					case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt:
 189  						valid = true
 190  					}
 191  				}
 192  				if !valid {
 193  					check.errorf(s.Label, MisplacedLabel, "invalid break label %s", name)
 194  					return
 195  				}
 196  
 197  			case token.CONTINUE:
 198  				// spec: "If there is a label, it must be that of an enclosing
 199  				// "for" statement, and that is the one whose execution advances."
 200  				valid := false
 201  				if t := b.enclosingTarget(name); t != nil {
 202  					switch t.Stmt.(type) {
 203  					case *ast.ForStmt, *ast.RangeStmt:
 204  						valid = true
 205  					}
 206  				}
 207  				if !valid {
 208  					check.errorf(s.Label, MisplacedLabel, "invalid continue label %s", name)
 209  					return
 210  				}
 211  
 212  			case token.GOTO:
 213  				if b.gotoTarget(name) == nil {
 214  					// label may be declared later - add branch to forward jumps
 215  					fwdJumps = append(fwdJumps, s)
 216  					return
 217  				}
 218  
 219  			default:
 220  				check.errorf(s, InvalidSyntaxTree, "branch statement: %s %s", s.Tok, name)
 221  				return
 222  			}
 223  
 224  			// record label use
 225  			obj := all.Lookup(name)
 226  			obj.(*Label).used = true
 227  			check.recordUse(s.Label, obj)
 228  
 229  		case *ast.AssignStmt:
 230  			if s.Tok == token.DEFINE {
 231  				recordVarDecl(s.Pos())
 232  			}
 233  
 234  		case *ast.BlockStmt:
 235  			blockBranches(lstmt, s.List)
 236  
 237  		case *ast.IfStmt:
 238  			stmtBranches(lstmt, s.Body)
 239  			if s.Else != nil {
 240  				stmtBranches(lstmt, s.Else)
 241  			}
 242  
 243  		case *ast.CaseClause:
 244  			blockBranches(nil, s.Body)
 245  
 246  		case *ast.SwitchStmt:
 247  			stmtBranches(lstmt, s.Body)
 248  
 249  		case *ast.TypeSwitchStmt:
 250  			stmtBranches(lstmt, s.Body)
 251  
 252  		case *ast.CommClause:
 253  			blockBranches(nil, s.Body)
 254  
 255  		case *ast.SelectStmt:
 256  			stmtBranches(lstmt, s.Body)
 257  
 258  		case *ast.ForStmt:
 259  			stmtBranches(lstmt, s.Body)
 260  
 261  		case *ast.RangeStmt:
 262  			stmtBranches(lstmt, s.Body)
 263  		}
 264  	}
 265  
 266  	for _, s := range list {
 267  		stmtBranches(nil, s)
 268  	}
 269  
 270  	return fwdJumps
 271  }
 272