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// This file implements isTerminating.
6
7package types
8
9import (
10	"go/ast"
11	"go/token"
12)
13
14// isTerminating reports if s is a terminating statement.
15// If s is labeled, label is the label name; otherwise s
16// is "".
17func (check *Checker) isTerminating(s ast.Stmt, label string) bool {
18	switch s := s.(type) {
19	default:
20		unreachable()
21
22	case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt,
23		*ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt,
24		*ast.RangeStmt:
25		// no chance
26
27	case *ast.LabeledStmt:
28		return check.isTerminating(s.Stmt, s.Label.Name)
29
30	case *ast.ExprStmt:
31		// calling the predeclared (possibly parenthesized) panic() function is terminating
32		if call, ok := unparen(s.X).(*ast.CallExpr); ok && check.isPanic[call] {
33			return true
34		}
35
36	case *ast.ReturnStmt:
37		return true
38
39	case *ast.BranchStmt:
40		if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH {
41			return true
42		}
43
44	case *ast.BlockStmt:
45		return check.isTerminatingList(s.List, "")
46
47	case *ast.IfStmt:
48		if s.Else != nil &&
49			check.isTerminating(s.Body, "") &&
50			check.isTerminating(s.Else, "") {
51			return true
52		}
53
54	case *ast.SwitchStmt:
55		return check.isTerminatingSwitch(s.Body, label)
56
57	case *ast.TypeSwitchStmt:
58		return check.isTerminatingSwitch(s.Body, label)
59
60	case *ast.SelectStmt:
61		for _, s := range s.Body.List {
62			cc := s.(*ast.CommClause)
63			if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) {
64				return false
65			}
66
67		}
68		return true
69
70	case *ast.ForStmt:
71		if s.Cond == nil && !hasBreak(s.Body, label, true) {
72			return true
73		}
74	}
75
76	return false
77}
78
79func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool {
80	// trailing empty statements are permitted - skip them
81	for i := len(list) - 1; i >= 0; i-- {
82		if _, ok := list[i].(*ast.EmptyStmt); !ok {
83			return check.isTerminating(list[i], label)
84		}
85	}
86	return false // all statements are empty
87}
88
89func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool {
90	hasDefault := false
91	for _, s := range body.List {
92		cc := s.(*ast.CaseClause)
93		if cc.List == nil {
94			hasDefault = true
95		}
96		if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) {
97			return false
98		}
99	}
100	return hasDefault
101}
102
103// TODO(gri) For nested breakable statements, the current implementation of hasBreak
104//	     will traverse the same subtree repeatedly, once for each label. Replace
105//           with a single-pass label/break matching phase.
106
107// hasBreak reports if s is or contains a break statement
108// referring to the label-ed statement or implicit-ly the
109// closest outer breakable statement.
110func hasBreak(s ast.Stmt, label string, implicit bool) bool {
111	switch s := s.(type) {
112	default:
113		unreachable()
114
115	case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt,
116		*ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt,
117		*ast.DeferStmt, *ast.ReturnStmt:
118		// no chance
119
120	case *ast.LabeledStmt:
121		return hasBreak(s.Stmt, label, implicit)
122
123	case *ast.BranchStmt:
124		if s.Tok == token.BREAK {
125			if s.Label == nil {
126				return implicit
127			}
128			if s.Label.Name == label {
129				return true
130			}
131		}
132
133	case *ast.BlockStmt:
134		return hasBreakList(s.List, label, implicit)
135
136	case *ast.IfStmt:
137		if hasBreak(s.Body, label, implicit) ||
138			s.Else != nil && hasBreak(s.Else, label, implicit) {
139			return true
140		}
141
142	case *ast.CaseClause:
143		return hasBreakList(s.Body, label, implicit)
144
145	case *ast.SwitchStmt:
146		if label != "" && hasBreak(s.Body, label, false) {
147			return true
148		}
149
150	case *ast.TypeSwitchStmt:
151		if label != "" && hasBreak(s.Body, label, false) {
152			return true
153		}
154
155	case *ast.CommClause:
156		return hasBreakList(s.Body, label, implicit)
157
158	case *ast.SelectStmt:
159		if label != "" && hasBreak(s.Body, label, false) {
160			return true
161		}
162
163	case *ast.ForStmt:
164		if label != "" && hasBreak(s.Body, label, false) {
165			return true
166		}
167
168	case *ast.RangeStmt:
169		if label != "" && hasBreak(s.Body, label, false) {
170			return true
171		}
172	}
173
174	return false
175}
176
177func hasBreakList(list []ast.Stmt, label string, implicit bool) bool {
178	for _, s := range list {
179		if hasBreak(s, label, implicit) {
180			return true
181		}
182	}
183	return false
184}
185