From 13b575679205863c3929efcfbb7504b0520dec07 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Fri, 14 Feb 2025 14:27:09 -0700 Subject: [PATCH] application.go: Ensure HandlePanicFromNativeCallback captures the panicking goroutine's stack When handling a recover, runtime/debug.Stack returns the stack from the panicking goroutine. We save that to an error along with the original value used for the panic, and then panic on that error within the new goroutine. Fixes #127 Signed-off-by: Aaron Klotz --- application.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/application.go b/application.go index 2c1f1f7c..2040fa0c 100644 --- a/application.go +++ b/application.go @@ -14,6 +14,8 @@ import ( "log" "os" "runtime" + "runtime/debug" + "strings" "sync" "sync/atomic" "time" @@ -249,6 +251,32 @@ func (app *Application) Exit(exitCode int) { postQuitMsg() } +type redirectedPanicError struct { + inner any + stack []byte +} + +func (e *redirectedPanicError) Error() string { + var msg string + switch v := e.inner.(type) { + case string: + msg = v + case error: + msg = v.Error() + case fmt.Stringer: + msg = v.String() + } + + return strings.Join([]string{msg, string(e.stack)}, "\n") +} + +func (e *redirectedPanicError) Unwrap() error { + if err, ok := e.inner.(error); ok { + return err + } + return nil +} + // HandlePanicFromNativeCallback should be deferred at boundaries where native // code is invoking a callback into Go code. It recovers any panic that occurred // farther down the call stack and re-triggers the panic on a new goroutine, @@ -256,7 +284,11 @@ func (app *Application) Exit(exitCode int) { // code invoking the callback. func (app *Application) HandlePanicFromNativeCallback() { if x := recover(); x != nil { - go panic(x) + e := &redirectedPanicError{ + inner: x, + stack: debug.Stack(), // Since we're in a recover, Stack will report the panicking stack! + } + go panic(e) // Don't let the main goroutine go anywhere past this point. select {} }