Source file test/recover6.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  // Test that inlining a function that calls recover does not change
     8  // recover semantics: gorecover counts logical (inline-expanded)
     9  // frames between gopanic and gorecover, so an inlined recover must
    10  // behave exactly like a non-inlined one.
    11  
    12  package main
    13  
    14  var got any
    15  
    16  // doRecover and setGot are small enough to be inlined everywhere
    17  // they are called.
    18  func doRecover() any { return recover() }
    19  
    20  func setGot() { got = recover() }
    21  
    22  // calls recover two logical frames below its caller.
    23  func setGotIndirect() { got = doRecover() }
    24  
    25  // mustPanic runs f, which must panic with "boom".
    26  func mustPanic(name string, f func()) {
    27  	defer func() {
    28  		if r := recover(); r != "boom" {
    29  			panic(name + ": panic did not propagate")
    30  		}
    31  	}()
    32  	f()
    33  	panic(name + " did not panic")
    34  }
    35  
    36  // mustRecover runs f, which must recover from its own panic.
    37  func mustRecover(name string, f func()) {
    38  	defer func() {
    39  		if r := recover(); r != nil {
    40  			panic(name + ": panic escaped")
    41  		}
    42  	}()
    43  	f()
    44  }
    45  
    46  func main() {
    47  	// Directly deferred function calling recover: recovers.
    48  	mustRecover("direct", func() {
    49  		defer setGot()
    50  		panic("boom")
    51  	})
    52  	if got != "boom" {
    53  		panic("direct: recover did not see the panic value")
    54  	}
    55  
    56  	// doRecover inlined into the deferred closure: recover is
    57  	// still two logical frames below gopanic and must return nil.
    58  	mustPanic("closure", func() {
    59  		defer func() { got = doRecover() }()
    60  		panic("boom")
    61  	})
    62  	if got != nil {
    63  		panic("closure: recover returned non-nil")
    64  	}
    65  
    66  	// Directly deferred function whose recover comes from an
    67  	// inlined callee: recover is two logical frames below gopanic
    68  	// and must return nil, exactly as if doRecover were not inlined.
    69  	mustPanic("nested", func() {
    70  		defer setGotIndirect()
    71  		panic("boom")
    72  	})
    73  	if got != nil {
    74  		panic("nested: recover returned non-nil")
    75  	}
    76  
    77  	// recover with no panic in flight, via an inlined helper.
    78  	if r := doRecover(); r != nil {
    79  		panic("idle: recover returned non-nil")
    80  	}
    81  }
    82  

View as plain text