Source file src/go/types/labels.go

     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.Stmt)
   123  	stmtBranches = func(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(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(s.Body)
   239  			if s.Else != nil {
   240  				stmtBranches(s.Else)
   241  			}
   242  
   243  		case *ast.CaseClause:
   244  			blockBranches(nil, s.Body)
   245  
   246  		case *ast.SwitchStmt:
   247  			stmtBranches(s.Body)
   248  
   249  		case *ast.TypeSwitchStmt:
   250  			stmtBranches(s.Body)
   251  
   252  		case *ast.CommClause:
   253  			blockBranches(nil, s.Body)
   254  
   255  		case *ast.SelectStmt:
   256  			stmtBranches(s.Body)
   257  
   258  		case *ast.ForStmt:
   259  			stmtBranches(s.Body)
   260  
   261  		case *ast.RangeStmt:
   262  			stmtBranches(s.Body)
   263  		}
   264  	}
   265  
   266  	for _, s := range list {
   267  		stmtBranches(s)
   268  	}
   269  
   270  	return fwdJumps
   271  }
   272  

View as plain text