Source file src/cmd/compile/internal/types2/return.go

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

View as plain text