Source file test/fixedbugs/issue71685.go

     1  // run
     2  
     3  // Copyright 2026 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  // A recover called in the body of a range-over-func loop takes effect,
     8  // just as it would anywhere else in the function containing the loop.
     9  // The body runs in a frame of its own, called from the iterator, but in
    10  // the source it is part of that function, and the frames the compiler
    11  // uses to run the loop must not change what recover means.
    12  
    13  package main
    14  
    15  import "fmt"
    16  
    17  func iter(yield func(int) bool) { yield(0) }
    18  
    19  var fails int
    20  
    21  func check(name string, got, want any) {
    22  	if got != want {
    23  		fmt.Printf("%s = %v, want %v\n", name, got, want)
    24  		fails++
    25  	}
    26  }
    27  
    28  // recovered reports what a call of f, deferred by a panicking function,
    29  // recovered, or nil if the panic went unrecovered.
    30  func recovered(f func(*any)) (got any) {
    31  	defer func() {
    32  		if recover() != nil {
    33  			got = nil // the panic escaped f
    34  		}
    35  	}()
    36  	var r any
    37  	func() {
    38  		defer f(&r)
    39  		panic("boom")
    40  	}()
    41  	return r
    42  }
    43  
    44  // A recover in a loop body recovers, and returns the panic value.
    45  func body(r *any) {
    46  	for range iter {
    47  		*r = recover()
    48  	}
    49  }
    50  
    51  // So does one in the body of a loop nested in another loop's body.
    52  func nested(r *any) {
    53  	for range iter {
    54  		for range iter {
    55  			*r = recover()
    56  		}
    57  	}
    58  }
    59  
    60  // A recover in a func literal in the body does not: it is two calls away
    61  // from the deferred function, not one, exactly as outside a loop.
    62  func inClosure(r *any) {
    63  	for range iter {
    64  		func() { *r = recover() }()
    65  	}
    66  }
    67  
    68  // A recover in a loop in a function that was not the one deferred does
    69  // not recover either.
    70  func inCallee(r *any) {
    71  	helper(r)
    72  }
    73  
    74  func helper(r *any) {
    75  	for range iter {
    76  		*r = recover()
    77  	}
    78  }
    79  
    80  // Ranging over something other than a function must behave the same way.
    81  func overInt(r *any) {
    82  	for range 1 {
    83  		*r = recover()
    84  	}
    85  }
    86  
    87  func overSlice(r *any) {
    88  	for range []int{0} {
    89  		*r = recover()
    90  	}
    91  }
    92  
    93  func overChan(r *any) {
    94  	ch := make(chan int, 1)
    95  	ch <- 0
    96  	close(ch)
    97  	for range ch {
    98  		*r = recover()
    99  	}
   100  }
   101  
   102  // A defer in the body defers to the containing function, and a recover in
   103  // the body still works alongside it.
   104  func withDefer(r *any) {
   105  	for range iter {
   106  		defer func() { *r = "deferred ran" }()
   107  		if recover() == nil {
   108  			*r = "did not recover"
   109  			return
   110  		}
   111  	}
   112  }
   113  
   114  // Recovering twice: the second call reports no panic left to recover.
   115  func twice(r *any) {
   116  	for range iter {
   117  		first := recover()
   118  		second := recover()
   119  		*r = fmt.Sprint(first, " ", second)
   120  	}
   121  }
   122  
   123  // A whole panic and recover inside the body, before recovering the panic
   124  // the body was deferred for.
   125  func inner(r *any) {
   126  	for range iter {
   127  		func() {
   128  			defer func() { recover() }()
   129  			panic("inner")
   130  		}()
   131  		*r = recover()
   132  	}
   133  }
   134  
   135  func main() {
   136  	check("body", recovered(body), "boom")
   137  	check("nested", recovered(nested), "boom")
   138  	check("inClosure", recovered(inClosure), nil)
   139  	check("inCallee", recovered(inCallee), nil)
   140  	check("overInt", recovered(overInt), "boom")
   141  	check("overSlice", recovered(overSlice), "boom")
   142  	check("overChan", recovered(overChan), "boom")
   143  	check("withDefer", recovered(withDefer), "deferred ran")
   144  	check("twice", recovered(twice), "boom <nil>")
   145  	check("inner", recovered(inner), "boom")
   146  
   147  	// With nothing panicking, a recover in a loop body returns nil and the
   148  	// loop runs as usual.
   149  	n := 0
   150  	var got any = "not nil"
   151  	for range iter {
   152  		got = recover()
   153  		n++
   154  	}
   155  	check("no panic: recover()", got, nil)
   156  	check("no panic: iterations", n, 1)
   157  
   158  	if fails > 0 {
   159  		panic("failed")
   160  	}
   161  }
   162  

View as plain text