runtime: don't duplicate reraised panic values in printpanics

Change the output printed when crashing with a reraised panic value
to not duplicate that value.

Changes output of panicking with "PANIC", recovering, and reraising
from:

  panic: PANIC [recovered]
    panic: PANIC

to:

  panic: PANIC [recovered, reraised]

Fixes #71517

Change-Id: Id59938c4ea0df555b851ffc650fe6f94c0845499
Reviewed-on: https://go-review.googlesource.com/c/go/+/645916
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2025-01-31 15:03:15 -08:00
parent 9b4a462a7d
commit 478ad013f9
5 changed files with 114 additions and 1 deletions

View File

@ -1 +1,18 @@
## 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]

View File

@ -356,6 +356,37 @@ panic: third panic
}
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)

View File

@ -649,6 +649,13 @@ func preprintpanics(p *_panic) {
}
}()
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()
@ -664,6 +671,9 @@ func preprintpanics(p *_panic) {
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
if p.link.reraised {
return
}
if !p.link.goexit {
print("\t")
}
@ -673,7 +683,9 @@ func printpanics(p *_panic) {
}
print("panic: ")
printpanicval(p.arg)
if p.recovered {
if p.reraised {
print(" [recovered, reraised]")
} else if p.recovered {
print(" [recovered]")
}
print("\n")

View File

@ -1016,6 +1016,7 @@ type _panic struct {
slotsPtr unsafe.Pointer
recovered bool // whether this panic has been recovered
reraised bool // whether this panic was reraised
goexit bool
deferreturn bool
}

View File

@ -19,6 +19,9 @@ func init() {
register("StringPanic", StringPanic)
register("NilPanic", NilPanic)
register("CircularPanic", CircularPanic)
register("ReraisedPanic", ReraisedPanic)
register("ReraisedMiddlePanic", ReraisedMiddlePanic)
register("ReraisedPanicSandwich", ReraisedPanicSandwich)
}
func test(name string) {
@ -137,3 +140,52 @@ func (e exampleCircleEndError) Error() 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")
}()
}