## Runtime {#runtime}
+
+<!-- go.dev/issue/71517 -->
+
+The message printed when a program exits due to an unhandled panic
+that was recovered and re-raised no longer repeats the text of
+the panic value.
+
+Previously, a program which panicked with `panic("PANIC")`,
+recovered the panic, and then re-panicked with the original
+value would print:
+
+ panic: PANIC [recovered]
+ panic: PANIC
+
+This program will now print:
+
+ panic: PANIC [recovered, reraised]
}
+func TestReraisedPanic(t *testing.T) {
+ output := runTestProg(t, "testprog", "ReraisedPanic")
+ want := `panic: message [recovered, reraised]
+`
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
+func TestReraisedMiddlePanic(t *testing.T) {
+ output := runTestProg(t, "testprog", "ReraisedMiddlePanic")
+ want := `panic: inner [recovered]
+ panic: middle [recovered, reraised]
+ panic: outer
+`
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
+func TestReraisedPanicSandwich(t *testing.T) {
+ output := runTestProg(t, "testprog", "ReraisedPanicSandwich")
+ want := `panic: outer [recovered]
+ panic: inner [recovered]
+ panic: outer
+`
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
func TestGoexitCrash(t *testing.T) {
// External linking brings in cgo, causing deadlock detection not working.
testenv.MustInternalLink(t, false)
}
}()
for p != nil {
+ if p.link != nil && *efaceOf(&p.link.arg) == *efaceOf(&p.arg) {
+ // This panic contains the same value as the next one in the chain.
+ // Mark it as reraised. We will skip printing it twice in a row.
+ p.link.reraised = true
+ p = p.link
+ continue
+ }
switch v := p.arg.(type) {
case error:
p.arg = v.Error()
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
+ if p.link.reraised {
+ return
+ }
if !p.link.goexit {
print("\t")
}
}
print("panic: ")
printpanicval(p.arg)
- if p.recovered {
+ if p.reraised {
+ print(" [recovered, reraised]")
+ } else if p.recovered {
print(" [recovered]")
}
print("\n")
slotsPtr unsafe.Pointer
recovered bool // whether this panic has been recovered
+ reraised bool // whether this panic was reraised
goexit bool
deferreturn bool
}
register("StringPanic", StringPanic)
register("NilPanic", NilPanic)
register("CircularPanic", CircularPanic)
+ register("ReraisedPanic", ReraisedPanic)
+ register("ReraisedMiddlePanic", ReraisedMiddlePanic)
+ register("ReraisedPanicSandwich", ReraisedPanicSandwich)
}
func test(name string) {
func CircularPanic() {
panic(exampleCircleStartError{})
}
+
+func ReraisedPanic() {
+ defer func() {
+ panic(recover())
+ }()
+ panic("message")
+}
+
+func ReraisedMiddlePanic() {
+ defer func() {
+ recover()
+ panic("outer")
+ }()
+ func() {
+ defer func() {
+ panic(recover())
+ }()
+ func() {
+ defer func() {
+ recover()
+ panic("middle")
+ }()
+ panic("inner")
+ }()
+ }()
+}
+
+// Panic sandwich:
+//
+// panic("outer") =>
+// recovered, panic("inner") =>
+// panic(recovered outer panic value)
+//
+// Exercises the edge case where we reraise a panic value,
+// but with another panic in the middle.
+func ReraisedPanicSandwich() {
+ var outer any
+ defer func() {
+ recover()
+ panic(outer)
+ }()
+ func() {
+ defer func() {
+ outer = recover()
+ panic("inner")
+ }()
+ panic("outer")
+ }()
+}