Commit 54357b3b by Ian Lance Taylor

runtime: improve handling of panic during deferred function

    
    When a panic occurs while processing a deferred function that
    recovered an earlier panic, we shouldn't report the recovered panic
    in the panic stack trace. Stop doing so by keeping track of the panic
    that triggered a defer, marking it as aborted if we see the defer again,
    and discarding aborted panics when a panic is recovered. This is what
    the gc runtime does.
    
    The test for this is TestRecursivePanic in runtime/crash_test.go.
    We don't run that test yet, but we will soon.
    
    Reviewed-on: https://go-review.googlesource.com/46461

From-SVN: r249590
parent fb68f296
385efb8947af70b8425c833a1ab68ba5f357dfae c4adba240f9d5af8ab0534316d6b05bd988c432c
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -91,6 +91,9 @@ func throwinit() { ...@@ -91,6 +91,9 @@ func throwinit() {
// arg is a value to pass to pfn. // arg is a value to pass to pfn.
func deferproc(frame *bool, pfn uintptr, arg unsafe.Pointer) { func deferproc(frame *bool, pfn uintptr, arg unsafe.Pointer) {
d := newdefer() d := newdefer()
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
d.frame = frame d.frame = frame
d.panicStack = getg()._panic d.panicStack = getg()._panic
d.pfn = pfn d.pfn = pfn
...@@ -338,17 +341,28 @@ func Goexit() { ...@@ -338,17 +341,28 @@ func Goexit() {
if d == nil { if d == nil {
break break
} }
gp._defer = d.link
pfn := d.pfn pfn := d.pfn
if pfn == 0 {
if d._panic != nil {
d._panic.aborted = true
d._panic = nil
}
gp._defer = d.link
freedefer(d)
continue
}
d.pfn = 0 d.pfn = 0
if pfn != 0 { var fn func(unsafe.Pointer)
var fn func(unsafe.Pointer) *(*uintptr)(unsafe.Pointer(&fn)) = uintptr(unsafe.Pointer(&pfn))
*(*uintptr)(unsafe.Pointer(&fn)) = uintptr(unsafe.Pointer(&pfn)) fn(d.arg)
fn(d.arg)
}
if gp._defer != d {
throw("bad defer entry in Goexit")
}
d._panic = nil
gp._defer = d.link
freedefer(d) freedefer(d)
// Note: we ignore recovers here because Goexit isn't a panic // Note: we ignore recovers here because Goexit isn't a panic
} }
...@@ -442,39 +456,71 @@ func gopanic(e interface{}) { ...@@ -442,39 +456,71 @@ func gopanic(e interface{}) {
} }
pfn := d.pfn pfn := d.pfn
// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. The earlier panic or Goexit will not continue running.
if pfn == 0 {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
gp._defer = d.link
freedefer(d)
continue
}
d.pfn = 0 d.pfn = 0
if pfn != 0 { // Record the panic that is running the defer.
var fn func(unsafe.Pointer) // If there is a new panic during the deferred call, that panic
*(*uintptr)(unsafe.Pointer(&fn)) = uintptr(unsafe.Pointer(&pfn)) // will find d in the list and will mark d._panic (this panic) aborted.
fn(d.arg) d._panic = p
if p.recovered { var fn func(unsafe.Pointer)
// Some deferred function called recover. *(*uintptr)(unsafe.Pointer(&fn)) = uintptr(unsafe.Pointer(&pfn))
// Stop running this panic. fn(d.arg)
gp._panic = p.link
if gp._defer != d {
// Unwind the stack by throwing an exception. throw("bad defer entry in panic")
// The compiler has arranged to create }
// exception handlers in each function d._panic = nil
// that uses a defer statement. These
// exception handlers will check whether if p.recovered {
// the entry on the top of the defer stack // Some deferred function called recover.
// is from the current function. If it is, // Stop running this panic.
// we have unwound the stack far enough. gp._panic = p.link
unwindStack()
// Aborted panics are marked but remain on the g.panic list.
throw("unwindStack returned") // Remove them from the list.
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil { // must be done with signal
gp.sig = 0
} }
// Because we executed that defer function by a panic, // Unwind the stack by throwing an exception.
// and it did not call recover, we know that we are // The compiler has arranged to create
// not returning from the calling function--we are // exception handlers in each function
// panicking through it. // that uses a defer statement. These
*d.frame = false // exception handlers will check whether
// the entry on the top of the defer stack
// is from the current function. If it is,
// we have unwound the stack far enough.
unwindStack()
throw("unwindStack returned")
} }
// Because we executed that defer function by a panic,
// and it did not call recover, we know that we are
// not returning from the calling function--we are
// panicking through it.
*d.frame = false
// Deferred function did not panic. Remove d.
// In the p.recovered case, d will be removed by checkdefer.
gp._defer = d.link gp._defer = d.link
freedefer(d) freedefer(d)
} }
......
...@@ -700,6 +700,10 @@ type _defer struct { ...@@ -700,6 +700,10 @@ type _defer struct {
// has a defer statement itself. // has a defer statement itself.
panicStack *_panic panicStack *_panic
// The panic that caused the defer to run. This is used to
// discard panics that have already been handled.
_panic *_panic
// The function to call. // The function to call.
pfn uintptr pfn uintptr
...@@ -733,6 +737,10 @@ type _panic struct { ...@@ -733,6 +737,10 @@ type _panic struct {
// Whether this panic was pushed on the stack because of an // Whether this panic was pushed on the stack because of an
// exception thrown in some other language. // exception thrown in some other language.
isforeign bool isforeign bool
// Whether this panic was already seen by a deferred function
// which called panic again.
aborted bool
} }
const ( const (
......
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