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