]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: force segv for nil defer function to be in deferreturn()
authorDan Scales <danscales@google.com>
Wed, 11 Dec 2019 01:27:26 +0000 (17:27 -0800)
committerDan Scales <danscales@google.com>
Thu, 12 Dec 2019 19:23:45 +0000 (19:23 +0000)
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/+/210978
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/runtime/callers_test.go
src/runtime/panic.go

index 3cd1b40ec985608fdea552295a45a7a5ebf95e25..302e33deeb81f2678f166233852bbbac516be66b 100644 (file)
@@ -254,9 +254,8 @@ func TestCallersDivZeroPanic(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
        // 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
-       // where the nil pointer deref happens). We could consider hiding jmpdefer in
-       // tracebacks.
+       // non-opt or race checker mode) include an extra 'deferreturn' frame (which is
+       // where the nil pointer deref happens).
        state := 1
        want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1",
                "runtime.gopanic", "runtime.panicmem", "runtime.sigpanic"}
@@ -279,3 +278,32 @@ func TestCallersDeferNilFuncPanic(t *testing.T) {
        // function exit, rather than at the defer statement.
        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
+}
index 0823f11e98e58ae1a586c3a1bdc673cc5494ba24..4cb6c8a3604ae2bdd769c0de74b1d3da4df9e0cf 100644 (file)
@@ -561,6 +561,12 @@ func deferreturn(arg0 uintptr) {
        d.fn = nil
        gp._defer = d.link
        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)))
 }