Home | History | Annotate | Download | only in syntax
      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 }
    315