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

View as plain text