Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/types/labels.go

Documentation: go/types

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

View as plain text