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

View as plain text