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