Commit 22d28a24 authored by Dan Scales's avatar Dan Scales

runtime: force segv for nil defer function to be in deferreturn()

If the defer function pointer is nil, force the seg fault to happen in deferreturn
rather than in jmpdefer. jmpdefer is used fairly infrequently now because most
functions have open-coded defers.

The open-coded defer implementation calls gentraceback() with a callback when
looking for the first open-coded defer frame. gentraceback() throws an error if it
is called with a callback on an LR architecture and jmpdefer is on the stack,
because the stack trace can be incorrect in that case - see issue #8153. So, we
want to make sure that we don't have a seg fault in jmpdefer.

Fixes #36050

Change-Id: Ie25e6f015d8eb170b40248dedeb26a37b7f9b38d
Reviewed-on: https://go-review.googlesource.com/c/go/+/210978Reviewed-by: default avatarKeith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 100bf440
...@@ -254,9 +254,8 @@ func TestCallersDivZeroPanic(t *testing.T) { ...@@ -254,9 +254,8 @@ func TestCallersDivZeroPanic(t *testing.T) {
func TestCallersDeferNilFuncPanic(t *testing.T) { func TestCallersDeferNilFuncPanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack. We cut off the check // Make sure we don't have any extra frames on the stack. We cut off the check
// at runtime.sigpanic, because non-open-coded defers (which may be used in // at runtime.sigpanic, because non-open-coded defers (which may be used in
// non-opt or race checker mode) include an extra 'jmpdefer' frame (which is // non-opt or race checker mode) include an extra 'deferreturn' frame (which is
// where the nil pointer deref happens). We could consider hiding jmpdefer in // where the nil pointer deref happens).
// tracebacks.
state := 1 state := 1
want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1", want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1",
"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic"} "runtime.gopanic", "runtime.panicmem", "runtime.sigpanic"}
...@@ -279,3 +278,32 @@ func TestCallersDeferNilFuncPanic(t *testing.T) { ...@@ -279,3 +278,32 @@ func TestCallersDeferNilFuncPanic(t *testing.T) {
// function exit, rather than at the defer statement. // function exit, rather than at the defer statement.
state = 2 state = 2
} }
// Same test, but forcing non-open-coded defer by putting the defer in a loop. See
// issue #36050
func TestCallersDeferNilFuncPanicWithLoop(t *testing.T) {
state := 1
want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanicWithLoop.func1",
"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic", "runtime.deferreturn", "runtime_test.TestCallersDeferNilFuncPanicWithLoop"}
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
}
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallersEqual(t, pcs, want)
if state == 1 {
t.Fatal("nil defer func panicked at defer time rather than function exit time")
}
}()
for i := 0; i < 1; i++ {
var f func()
defer f()
}
// Use the value of 'state' to make sure nil defer func f causes panic at
// function exit, rather than at the defer statement.
state = 2
}
...@@ -561,6 +561,12 @@ func deferreturn(arg0 uintptr) { ...@@ -561,6 +561,12 @@ func deferreturn(arg0 uintptr) {
d.fn = nil d.fn = nil
gp._defer = d.link gp._defer = d.link
freedefer(d) freedefer(d)
// If the defer function pointer is nil, force the seg fault to happen
// here rather than in jmpdefer. gentraceback() throws an error if it is
// called with a callback on an LR architecture and jmpdefer is on the
// stack, because the stack trace can be incorrect in that case - see
// issue #8153).
_ = fn.fn
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment