Home | History | Annotate | Download | only in types
      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 
      7 package types
      8 
      9 import (
     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 "".
     17 func (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, token.NoPos); 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 
     85 func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool {
     86 	// trailing empty statements are permitted - skip them
     87 	for i := len(list) - 1; i >= 0; i-- {
     88 		if _, ok := list[i].(*ast.EmptyStmt); !ok {
     89 			return check.isTerminating(list[i], label)
     90 		}
     91 	}
     92 	return false // all statements are empty
     93 }
     94 
     95 func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool {
     96 	hasDefault := false
     97 	for _, s := range body.List {
     98 		cc := s.(*ast.CaseClause)
     99 		if cc.List == nil {
    100 			hasDefault = true
    101 		}
    102 		if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) {
    103 			return false
    104 		}
    105 	}
    106 	return hasDefault
    107 }
    108 
    109 // TODO(gri) For nested breakable statements, the current implementation of hasBreak
    110 //	     will traverse the same subtree repeatedly, once for each label. Replace
    111 //           with a single-pass label/break matching phase.
    112 
    113 // hasBreak reports if s is or contains a break statement
    114 // referring to the label-ed statement or implicit-ly the
    115 // closest outer breakable statement.
    116 func hasBreak(s ast.Stmt, label string, implicit bool) bool {
    117 	switch s := s.(type) {
    118 	default:
    119 		unreachable()
    120 
    121 	case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt,
    122 		*ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt,
    123 		*ast.DeferStmt, *ast.ReturnStmt:
    124 		// no chance
    125 
    126 	case *ast.LabeledStmt:
    127 		return hasBreak(s.Stmt, label, implicit)
    128 
    129 	case *ast.BranchStmt:
    130 		if s.Tok == token.BREAK {
    131 			if s.Label == nil {
    132 				return implicit
    133 			}
    134 			if s.Label.Name == label {
    135 				return true
    136 			}
    137 		}
    138 
    139 	case *ast.BlockStmt:
    140 		return hasBreakList(s.List, label, implicit)
    141 
    142 	case *ast.IfStmt:
    143 		if hasBreak(s.Body, label, implicit) ||
    144 			s.Else != nil && hasBreak(s.Else, label, implicit) {
    145 			return true
    146 		}
    147 
    148 	case *ast.CaseClause:
    149 		return hasBreakList(s.Body, label, implicit)
    150 
    151 	case *ast.SwitchStmt:
    152 		if label != "" && hasBreak(s.Body, label, false) {
    153 			return true
    154 		}
    155 
    156 	case *ast.TypeSwitchStmt:
    157 		if label != "" && hasBreak(s.Body, label, false) {
    158 			return true
    159 		}
    160 
    161 	case *ast.CommClause:
    162 		return hasBreakList(s.Body, label, implicit)
    163 
    164 	case *ast.SelectStmt:
    165 		if label != "" && hasBreak(s.Body, label, false) {
    166 			return true
    167 		}
    168 
    169 	case *ast.ForStmt:
    170 		if label != "" && hasBreak(s.Body, label, false) {
    171 			return true
    172 		}
    173 
    174 	case *ast.RangeStmt:
    175 		if label != "" && hasBreak(s.Body, label, false) {
    176 			return true
    177 		}
    178 	}
    179 
    180 	return false
    181 }
    182 
    183 func hasBreakList(list []ast.Stmt, label string, implicit bool) bool {
    184 	for _, s := range list {
    185 		if hasBreak(s, label, implicit) {
    186 			return true
    187 		}
    188 	}
    189 	return false
    190 }
    191