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