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 (
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
21func 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
52type labelScope struct {
53	errh   ErrorHandler
54	labels map[string]*label // all label declarations inside the function; allocated lazily
55}
56
57type 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
63type 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
69func (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.
76func (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.
94func (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
106var 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.
111func (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.
126type 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 asociated with this block, or nil.
135func (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