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
5package types
6
7import (
8	"go/ast"
9	"go/token"
10)
11
12// labels checks correct label use in body.
13func (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.
44type 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.
52func (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.
67func (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.
78func (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).
90func (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