case "runtime.runOpenDeferFrame":
// Don't show in the call stack (used when invoking defer functions)
return FuncID_wrapper
+ case "runtime.reflectcallSave":
+ // Don't show in the call stack (used when invoking defer functions)
+ return FuncID_wrapper
}
if file == "<autogenerated>" {
return FuncID_wrapper
panic(1)
}
+func TestCallersAbortedPanic(t *testing.T) {
+ want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic.func2", "runtime_test.TestCallersAbortedPanic"}
+
+ defer func() {
+ r := recover()
+ if r != nil {
+ t.Fatalf("should be no panic remaining to recover")
+ }
+ }()
+
+ defer func() {
+ // panic2 was aborted/replaced by panic1, so when panic2 was
+ // recovered, there is no remaining panic on the stack.
+ pcs := make([]uintptr, 20)
+ pcs = pcs[:runtime.Callers(0, pcs)]
+ testCallersEqual(t, pcs, want)
+ }()
+ defer func() {
+ r := recover()
+ if r != "panic2" {
+ t.Fatalf("got %v, wanted %v", r, "panic2")
+ }
+ }()
+ defer func() {
+ // panic2 aborts/replaces panic1, because it is a recursive panic
+ // that is not recovered within the defer function called by
+ // panic1 panicking sequence
+ panic("panic2")
+ }()
+ panic("panic1")
+}
+
+func TestCallersAbortedPanic2(t *testing.T) {
+ want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic2.func2", "runtime_test.TestCallersAbortedPanic2"}
+ defer func() {
+ r := recover()
+ if r != nil {
+ t.Fatalf("should be no panic remaining to recover")
+ }
+ }()
+ defer func() {
+ pcs := make([]uintptr, 20)
+ pcs = pcs[:runtime.Callers(0, pcs)]
+ testCallersEqual(t, pcs, want)
+ }()
+ func() {
+ defer func() {
+ r := recover()
+ if r != "panic2" {
+ t.Fatalf("got %v, wanted %v", r, "panic2")
+ }
+ }()
+ func() {
+ defer func() {
+ // Again, panic2 aborts/replaces panic1
+ panic("panic2")
+ }()
+ panic("panic1")
+ }()
+ }()
+}
+
func TestCallersNilPointerPanic(t *testing.T) {
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
}
+func TestRecursivePanic4(t *testing.T) {
+ output := runTestProg(t, "testprog", "RecursivePanic4")
+ want := `panic: first panic [recovered]
+ panic: second panic
+`
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+
+}
+
func TestGoexitCrash(t *testing.T) {
output := runTestProg(t, "testprog", "GoexitExit")
want := "no goroutines (main called runtime.Goexit) - deadlock!"
}
func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
- // 1. defer a function that recovers
- // 2. defer a function that panics
- // 3. call goexit
- // Goexit should run the #2 defer. Its panic
- // should be caught by the #1 defer, and execution
- // should resume in the caller. Like the Goexit
- // never happened!
- defer func() {
- r := recover()
- if r == nil {
- panic("bad recover")
- }
- }()
- defer func() {
- panic("hello")
- }()
- runtime.Goexit()
+ t.Parallel()
+ output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit")
+ want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
+func TestRecoverBeforePanicAfterGoexit2(t *testing.T) {
+ t.Parallel()
+ output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit2")
+ want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
}
func TestNetpollDeadlock(t *testing.T) {
}
// This tests an extra recursive panic behavior that is only specified in the
-// code. Suppose a first panic P1 happens and starts processing defer calls. If
-// a second panic P2 happens while processing defer call D in frame F, then defer
+// code. Suppose a first panic P1 happens and starts processing defer calls. If a
+// second panic P2 happens while processing defer call D in frame F, then defer
// call processing is restarted (with some potentially new defer calls created by
-// D or its callees). If the defer processing reaches the started defer call D
+// D or its callees). If the defer processing reaches the started defer call D
// again in the defer stack, then the original panic P1 is aborted and cannot
-// continue panic processing or be recovered. If the panic P2 does a recover at
-// some point, it will naturally the original panic P1 from the stack, since the
-// original panic had to be in frame F or a descendant of F.
+// continue panic processing or be recovered. If the panic P2 does a recover at
+// some point, it will naturally remove the original panic P1 from the stack
+// (since the original panic had to be in frame F or a descendant of F).
func TestAbortedPanic(t *testing.T) {
defer func() {
- // The first panic should have been "aborted", so there is
- // no other panic to recover
r := recover()
if r != nil {
t.Fatal(fmt.Sprintf("wanted nil recover, got %v", r))
// This code is similar to gopanic, see that implementation
// for detailed comments.
gp := getg()
- addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
+ // Create a panic object for Goexit, so we can recognize when it might be
+ // bypassed by a recover().
+ var p _panic
+ p.goexit = true
+ p.link = gp._panic
+ gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
+
+ addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
for {
d := gp._defer
if d == nil {
}
}
d.started = true
+ d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
if d.openDefer {
done := runOpenDeferFrame(gp, d)
if !done {
// defer that can be recovered.
throw("unfinished open-coded defers in Goexit")
}
- addOneOpenDeferFrame(gp, 0, nil)
+ if p.aborted {
+ // Since our current defer caused a panic and may
+ // have been already freed, just restart scanning
+ // for open-coded defers from this frame again.
+ addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
+ } else {
+ addOneOpenDeferFrame(gp, 0, nil)
+ }
} else {
- reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
+
+ // Save the pc/sp in reflectcallSave(), so we can "recover" back to this
+ // loop if necessary.
+ reflectcallSave(&p, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz))
+ }
+ if p.aborted {
+ // We had a recursive panic in the defer d we started, and
+ // then did a recover in a defer that was further down the
+ // defer chain than d. In the case of an outstanding Goexit,
+ // we force the recover to return back to this loop. d will
+ // have already been freed if completed, so just continue
+ // immediately to the next defer on the chain.
+ p.aborted = false
+ continue
}
if gp._defer != d {
throw("bad defer entry in Goexit")
func printpanics(p *_panic) {
if p.link != nil {
printpanics(p.link)
- print("\t")
+ if !p.link.goexit {
+ print("\t")
+ }
+ }
+ if p.goexit {
+ return
}
print("panic: ")
printany(p.arg)
}
deferBits = deferBits &^ (1 << i)
*(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits
- if d._panic != nil {
- d._panic.argp = unsafe.Pointer(getargp(0))
+ p := d._panic
+ reflectcallSave(p, unsafe.Pointer(closure), deferArgs, argWidth)
+ if p != nil && p.aborted {
+ break
}
- reflectcall(nil, unsafe.Pointer(closure), deferArgs, argWidth, argWidth)
d.fn = nil
// These args are just a copy, so can be cleared immediately
memclrNoHeapPointers(deferArgs, uintptr(argWidth))
return done
}
+// reflectcallSave calls reflectcall after saving the caller's pc and sp in the
+// panic record. This allows the runtime to return to the Goexit defer processing
+// loop, in the unusual case where the Goexit may be bypassed by a successful
+// recover.
+func reflectcallSave(p *_panic, fn, arg unsafe.Pointer, argsize uint32) {
+ if p != nil {
+ p.argp = unsafe.Pointer(getargp(0))
+ p.pc = getcallerpc()
+ p.sp = unsafe.Pointer(getcallersp())
+ }
+ reflectcall(nil, fn, arg, argsize, argsize)
+ if p != nil {
+ p.pc = 0
+ p.sp = unsafe.Pointer(nil)
+ }
+}
+
// The implementation of the predeclared function panic.
func gopanic(e interface{}) {
gp := getg()
}
// 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.
+ // take defer off list. An earlier panic will not continue running, but we will make sure below that an
+ // earlier Goexit does continue running.
if d.started {
if d._panic != nil {
d._panic.aborted = true
freedefer(d)
}
if p.recovered {
+ gp._panic = p.link
+ if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {
+ // A normal recover would bypass/abort the Goexit. Instead,
+ // we return to the processing loop of the Goexit.
+ gp.sigcode0 = uintptr(gp._panic.sp)
+ gp.sigcode1 = uintptr(gp._panic.pc)
+ mcall(recovery)
+ throw("bypassed recovery failed") // mcall should not return
+ }
atomic.Xadd(&runningPanicDefers, -1)
if done {
// If they match, the caller is the one who can recover.
gp := getg()
p := gp._panic
- if p != nil && !p.recovered && argp == uintptr(p.argp) {
+ if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
arg interface{} // argument to panic
link *_panic // link to earlier panic
+ pc uintptr // where to return to in runtime if this panic is bypassed
+ sp unsafe.Pointer // where to return to in runtime if this panic is bypassed
recovered bool // whether this panic is over
aborted bool // the panic was aborted
+ goexit bool
}
// stack traces
register("RecursivePanic", RecursivePanic)
register("RecursivePanic2", RecursivePanic2)
register("RecursivePanic3", RecursivePanic3)
+ register("RecursivePanic4", RecursivePanic4)
register("GoexitExit", GoexitExit)
register("GoNil", GoNil)
register("MainGoroutineID", MainGoroutineID)
register("GoexitInPanic", GoexitInPanic)
register("PanicAfterGoexit", PanicAfterGoexit)
register("RecoveredPanicAfterGoexit", RecoveredPanicAfterGoexit)
+ register("RecoverBeforePanicAfterGoexit", RecoverBeforePanicAfterGoexit)
+ register("RecoverBeforePanicAfterGoexit2", RecoverBeforePanicAfterGoexit2)
register("PanicTraceback", PanicTraceback)
register("GoschedInPanic", GoschedInPanic)
register("SyscallInPanic", SyscallInPanic)
panic("first panic")
}
+// Test case where a single defer recovers one panic but starts another panic. If
+// the second panic is never recovered, then the recovered first panic will still
+// appear on the panic stack (labeled '[recovered]') and the runtime stack.
+func RecursivePanic4() {
+ defer func() {
+ recover()
+ panic("second panic")
+ }()
+ panic("first panic")
+}
+
func GoexitExit() {
println("t1")
go func() {
runtime.Goexit()
}
+func RecoverBeforePanicAfterGoexit() {
+ // 1. defer a function that recovers
+ // 2. defer a function that panics
+ // 3. call goexit
+ // Goexit runs the #2 defer. Its panic
+ // is caught by the #1 defer. For Goexit, we explicitly
+ // resume execution in the Goexit loop, instead of resuming
+ // execution in the caller (which would make the Goexit disappear!)
+ defer func() {
+ r := recover()
+ if r == nil {
+ panic("bad recover")
+ }
+ }()
+ defer func() {
+ panic("hello")
+ }()
+ runtime.Goexit()
+}
+
+func RecoverBeforePanicAfterGoexit2() {
+ for i := 0; i < 2; i++ {
+ defer func() {
+ }()
+ }
+ // 1. defer a function that recovers
+ // 2. defer a function that panics
+ // 3. call goexit
+ // Goexit runs the #2 defer. Its panic
+ // is caught by the #1 defer. For Goexit, we explicitly
+ // resume execution in the Goexit loop, instead of resuming
+ // execution in the caller (which would make the Goexit disappear!)
+ defer func() {
+ r := recover()
+ if r == nil {
+ panic("bad recover")
+ }
+ }()
+ defer func() {
+ panic("hello")
+ }()
+ runtime.Goexit()
+}
+
func PanicTraceback() {
pt1()
}
func main() {
defer func() {
+ // This recover recovers the panic caused by the nil defer func
+ // g(). The original panic(1) was already aborted/replaced by this
+ // new panic, so when this recover is done, the program completes
+ // normally.
recover()
}()
f()