diff --git a/src/runtime/stack_test.go b/src/runtime/stack_test.go index 81a637ccb3..5d674470c1 100644 --- a/src/runtime/stack_test.go +++ b/src/runtime/stack_test.go @@ -310,6 +310,39 @@ func testDeferPtrsPanic(c chan int, i int) { useStackAndCall(i, func() { panic(1) }) } +//go:noinline +func testDeferLeafSigpanic1() { + // Cause a sigpanic to be injected in this frame. + // + // This function has to be declared before + // TestDeferLeafSigpanic so the runtime will crash if we think + // this function's continuation PC is in + // TestDeferLeafSigpanic. + *(*int)(nil) = 0 +} + +// TestDeferLeafSigpanic tests defer matching around leaf functions +// that sigpanic. This is tricky because on LR machines the outer +// function and the inner function have the same SP, but it's critical +// that we match up the defer correctly to get the right liveness map. +// See issue #25499. +func TestDeferLeafSigpanic(t *testing.T) { + // Push a defer that will walk the stack. + defer func() { + if err := recover(); err == nil { + t.Fatal("expected panic from nil pointer") + } + GC() + }() + // Call a leaf function. We must set up the exact call stack: + // + // defering function -> leaf function -> sigpanic + // + // On LR machines, the leaf function will have the same SP as + // the SP pushed for the defer frame. + testDeferLeafSigpanic1() +} + // TestPanicUseStack checks that a chain of Panic structs on the stack are // updated correctly if the stack grows during the deferred execution that // happens as a result of the panic. diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 0fd7ef1987..cc5e01eb8b 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -302,7 +302,14 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in // returns; everything live at earlier deferprocs is still live at that one. frame.continpc = frame.pc if waspanic { - if _defer != nil && _defer.sp == frame.sp { + // We match up defers with frames using the SP. + // However, if the function has an empty stack + // frame, then it's possible (on LR machines) + // for multiple call frames to have the same + // SP. But, since a function with no frame + // can't push a defer, the defer can't belong + // to that frame. + if _defer != nil && _defer.sp == frame.sp && frame.sp != frame.fp { frame.continpc = _defer.pc } else { frame.continpc = 0 @@ -310,7 +317,7 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in } // Unwind our local defer stack past this frame. - for _defer != nil && (_defer.sp == frame.sp || _defer.sp == _NoArgs) { + for _defer != nil && ((_defer.sp == frame.sp && frame.sp != frame.fp) || _defer.sp == _NoArgs) { _defer = _defer.link }