github.com/ltltlt/go-source-code@v0.0.0-20190830023027-95be009773aa/cmd/compile/internal/syntax/branches.go (about)

     1  // Copyright 2017 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 syntax
     6  
     7  import (
     8  	"cmd/internal/src"
     9  	"fmt"
    10  )
    11  
    12  // TODO(gri) consider making this part of the parser code
    13  
    14  // checkBranches checks correct use of labels and branch
    15  // statements (break, continue, goto) in a function body.
    16  // It catches:
    17  //    - misplaced breaks and continues
    18  //    - bad labeled breaks and continues
    19  //    - invalid, unused, duplicate, and missing labels
    20  //    - gotos jumping over variable declarations and into blocks
    21  func checkBranches(body *BlockStmt, errh ErrorHandler) {
    22  	if body == nil {
    23  		return
    24  	}
    25  
    26  	// scope of all labels in this body
    27  	ls := &labelScope{errh: errh}
    28  	fwdGotos := ls.blockBranches(nil, targets{}, nil, body.Pos(), body.List)
    29  
    30  	// If there are any forward gotos left, no matching label was
    31  	// found for them. Either those labels were never defined, or
    32  	// they are inside blocks and not reachable from the gotos.
    33  	for _, fwd := range fwdGotos {
    34  		name := fwd.Label.Value
    35  		if l := ls.labels[name]; l != nil {
    36  			l.used = true // avoid "defined and not used" error
    37  			ls.err(fwd.Label.Pos(), "goto %s jumps into block starting at %s", name, l.parent.start)
    38  		} else {
    39  			ls.err(fwd.Label.Pos(), "label %s not defined", name)
    40  		}
    41  	}
    42  
    43  	// spec: "It is illegal to define a label that is never used."
    44  	for _, l := range ls.labels {
    45  		if !l.used {
    46  			l := l.lstmt.Label
    47  			ls.err(l.Pos(), "label %s defined and not used", l.Value)
    48  		}
    49  	}
    50  }
    51  
    52  type labelScope struct {
    53  	errh   ErrorHandler
    54  	labels map[string]*label // all label declarations inside the function; allocated lazily
    55  }
    56  
    57  type label struct {
    58  	parent *block       // block containing this label declaration
    59  	lstmt  *LabeledStmt // statement declaring the label
    60  	used   bool         // whether the label is used or not
    61  }
    62  
    63  type block struct {
    64  	parent *block       // immediately enclosing block, or nil
    65  	start  src.Pos      // start of block
    66  	lstmt  *LabeledStmt // labeled statement associated with this block, or nil
    67  }
    68  
    69  func (ls *labelScope) err(pos src.Pos, format string, args ...interface{}) {
    70  	ls.errh(Error{pos, fmt.Sprintf(format, args...)})
    71  }
    72  
    73  // declare declares the label introduced by s in block b and returns
    74  // the new label. If the label was already declared, declare reports
    75  // and error and the existing label is returned instead.
    76  func (ls *labelScope) declare(b *block, s *LabeledStmt) *label {
    77  	name := s.Label.Value
    78  	labels := ls.labels
    79  	if labels == nil {
    80  		labels = make(map[string]*label)
    81  		ls.labels = labels
    82  	} else if alt := labels[name]; alt != nil {
    83  		ls.err(s.Pos(), "label %s already defined at %s", name, alt.lstmt.Label.Pos().String())
    84  		return alt
    85  	}
    86  	l := &label{b, s, false}
    87  	labels[name] = l
    88  	return l
    89  }
    90  
    91  // gotoTarget returns the labeled statement matching the given name and
    92  // declared in block b or any of its enclosing blocks. The result is nil
    93  // if the label is not defined, or doesn't match a valid labeled statement.
    94  func (ls *labelScope) gotoTarget(b *block, name string) *LabeledStmt {
    95  	if l := ls.labels[name]; l != nil {
    96  		l.used = true // even if it's not a valid target
    97  		for ; b != nil; b = b.parent {
    98  			if l.parent == b {
    99  				return l.lstmt
   100  			}
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  var invalid = new(LabeledStmt) // singleton to signal invalid enclosing target
   107  
   108  // enclosingTarget returns the innermost enclosing labeled statement matching
   109  // the given name. The result is nil if the label is not defined, and invalid
   110  // if the label is defined but doesn't label a valid labeled statement.
   111  func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt {
   112  	if l := ls.labels[name]; l != nil {
   113  		l.used = true // even if it's not a valid target (see e.g., test/fixedbugs/bug136.go)
   114  		for ; b != nil; b = b.parent {
   115  			if l.lstmt == b.lstmt {
   116  				return l.lstmt
   117  			}
   118  		}
   119  		return invalid
   120  	}
   121  	return nil
   122  }
   123  
   124  // targets describes the target statements within which break
   125  // or continue statements are valid.
   126  type targets struct {
   127  	breaks    Stmt     // *ForStmt, *SwitchStmt, *SelectStmt, or nil
   128  	continues *ForStmt // or nil
   129  }
   130  
   131  // blockBranches processes a block's body starting at start and returns the
   132  // list of unresolved (forward) gotos. parent is the immediately enclosing
   133  // block (or nil), ctxt provides information about the enclosing statements,
   134  // and lstmt is the labeled statement associated with this block, or nil.
   135  func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start src.Pos, body []Stmt) []*BranchStmt {
   136  	b := &block{parent: parent, start: start, lstmt: lstmt}
   137  
   138  	var varPos src.Pos
   139  	var varName Expr
   140  	var fwdGotos, badGotos []*BranchStmt
   141  
   142  	recordVarDecl := func(pos src.Pos, name Expr) {
   143  		varPos = pos
   144  		varName = name
   145  		// Any existing forward goto jumping over the variable
   146  		// declaration is invalid. The goto may still jump out
   147  		// of the block and be ok, but we don't know that yet.
   148  		// Remember all forward gotos as potential bad gotos.
   149  		badGotos = append(badGotos[:0], fwdGotos...)
   150  	}
   151  
   152  	jumpsOverVarDecl := func(fwd *BranchStmt) bool {
   153  		if varPos.IsKnown() {
   154  			for _, bad := range badGotos {
   155  				if fwd == bad {
   156  					return true
   157  				}
   158  			}
   159  		}
   160  		return false
   161  	}
   162  
   163  	innerBlock := func(ctxt targets, start src.Pos, body []Stmt) {
   164  		// Unresolved forward gotos from the inner block
   165  		// become forward gotos for the current block.
   166  		fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...)
   167  	}
   168  
   169  	for _, stmt := range body {
   170  		lstmt = nil
   171  	L:
   172  		switch s := stmt.(type) {
   173  		case *DeclStmt:
   174  			for _, d := range s.DeclList {
   175  				if v, ok := d.(*VarDecl); ok {
   176  					recordVarDecl(v.Pos(), v.NameList[0])
   177  					break // the first VarDecl will do
   178  				}
   179  			}
   180  
   181  		case *LabeledStmt:
   182  			// declare non-blank label
   183  			if name := s.Label.Value; name != "_" {
   184  				l := ls.declare(b, s)
   185  				// resolve matching forward gotos
   186  				i := 0
   187  				for _, fwd := range fwdGotos {
   188  					if fwd.Label.Value == name {
   189  						fwd.Target = s
   190  						l.used = true
   191  						if jumpsOverVarDecl(fwd) {
   192  							ls.err(
   193  								fwd.Label.Pos(),
   194  								"goto %s jumps over declaration of %s at %s",
   195  								name, String(varName), varPos,
   196  							)
   197  						}
   198  					} else {
   199  						// no match - keep forward goto
   200  						fwdGotos[i] = fwd
   201  						i++
   202  					}
   203  				}
   204  				fwdGotos = fwdGotos[:i]
   205  				lstmt = s
   206  			}
   207  			// process labeled statement
   208  			stmt = s.Stmt
   209  			goto L
   210  
   211  		case *BranchStmt:
   212  			// unlabeled branch statement
   213  			if s.Label == nil {
   214  				switch s.Tok {
   215  				case _Break:
   216  					if t := ctxt.breaks; t != nil {
   217  						s.Target = t
   218  					} else {
   219  						ls.err(s.Pos(), "break is not in a loop, switch, or select")
   220  					}
   221  				case _Continue:
   222  					if t := ctxt.continues; t != nil {
   223  						s.Target = t
   224  					} else {
   225  						ls.err(s.Pos(), "continue is not in a loop")
   226  					}
   227  				case _Fallthrough:
   228  					// nothing to do
   229  				case _Goto:
   230  					fallthrough // should always have a label
   231  				default:
   232  					panic("invalid BranchStmt")
   233  				}
   234  				break
   235  			}
   236  
   237  			// labeled branch statement
   238  			name := s.Label.Value
   239  			switch s.Tok {
   240  			case _Break:
   241  				// spec: "If there is a label, it must be that of an enclosing
   242  				// "for", "switch", or "select" statement, and that is the one
   243  				// whose execution terminates."
   244  				if t := ls.enclosingTarget(b, name); t != nil {
   245  					switch t := t.Stmt.(type) {
   246  					case *SwitchStmt, *SelectStmt, *ForStmt:
   247  						s.Target = t
   248  					default:
   249  						ls.err(s.Label.Pos(), "invalid break label %s", name)
   250  					}
   251  				} else {
   252  					ls.err(s.Label.Pos(), "break label not defined: %s", name)
   253  				}
   254  
   255  			case _Continue:
   256  				// spec: "If there is a label, it must be that of an enclosing
   257  				// "for" statement, and that is the one whose execution advances."
   258  				if t := ls.enclosingTarget(b, name); t != nil {
   259  					if t, ok := t.Stmt.(*ForStmt); ok {
   260  						s.Target = t
   261  					} else {
   262  						ls.err(s.Label.Pos(), "invalid continue label %s", name)
   263  					}
   264  				} else {
   265  					ls.err(s.Label.Pos(), "continue label not defined: %s", name)
   266  				}
   267  
   268  			case _Goto:
   269  				if t := ls.gotoTarget(b, name); t != nil {
   270  					s.Target = t
   271  				} else {
   272  					// label may be declared later - add goto to forward gotos
   273  					fwdGotos = append(fwdGotos, s)
   274  				}
   275  
   276  			case _Fallthrough:
   277  				fallthrough // should never have a label
   278  			default:
   279  				panic("invalid BranchStmt")
   280  			}
   281  
   282  		case *AssignStmt:
   283  			if s.Op == Def {
   284  				recordVarDecl(s.Pos(), s.Lhs)
   285  			}
   286  
   287  		case *BlockStmt:
   288  			innerBlock(ctxt, s.Pos(), s.List)
   289  
   290  		case *IfStmt:
   291  			innerBlock(ctxt, s.Then.Pos(), s.Then.List)
   292  			if s.Else != nil {
   293  				innerBlock(ctxt, s.Else.Pos(), []Stmt{s.Else})
   294  			}
   295  
   296  		case *ForStmt:
   297  			innerBlock(targets{s, s}, s.Body.Pos(), s.Body.List)
   298  
   299  		case *SwitchStmt:
   300  			inner := targets{s, ctxt.continues}
   301  			for _, cc := range s.Body {
   302  				innerBlock(inner, cc.Pos(), cc.Body)
   303  			}
   304  
   305  		case *SelectStmt:
   306  			inner := targets{s, ctxt.continues}
   307  			for _, cc := range s.Body {
   308  				innerBlock(inner, cc.Pos(), cc.Body)
   309  			}
   310  		}
   311  	}
   312  
   313  	return fwdGotos
   314  }