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		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.
47type 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.
55func (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.
70func (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.
81func (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).
93func (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