Skip to content

Commit 8dfb447

Browse files
committed
runtime: do not add open defer entry above a started open defer entry
Fix two defer bugs related to adding/removing open defer entries. The bugs relate to the way that we add and remove open defer entries from the defer chain. At the point of a panic, when we want to start processing defer entries in order during the panic process, we need to add entries to the defer chain for stack frames with open defers, since the normal fast-defer code does not add these entries. We do this by calling addOneOpenDeferFrame() at the beginning of each time around the defer loop in gopanic(). Those defer entries get sorted with other open and non-open-coded defer frames. However, the tricky part is that we also need to remove defer entries if they end not being needed because of a recover (which means we are back to executing the defer code inline at function exits). But we need to deal with multiple panics and in-process defers on the stack, so we can't just remove all open-coded defers from the the defer chain during a recover. The fix (and new invariant) is that we should not add any open-coded defers to the defer chain that are higher up the stack than an open-coded defer that is in progress. We know that open-coded defer will still be run until completed, and when it is completed, then a more outer frame will be added (if there is one). This fits with existing code in gopanic that only removes open-coded defer entries up to any defer in progress. These bugs were because of the previous inconsistency between adding and removing open defer entries, which meant that stale defer entries could be left on the list, in these unusual cases with both recursive panics plus multiple independent (non-nested) cases of panic & recover. The test for #48898 was difficult to add to defer_test.go (while keeping the failure mode), so I added as a go/test/fixedbug test instead. Fixes #43920 Updates #43941 Fixes #48898 Change-Id: I593b77033e08c33094315abf8089fbc4cab07376 Reviewed-on: https://go-review.googlesource.com/c/go/+/356011 Trust: Dan Scales <[email protected]> Trust: Cuong Manh Le <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent c812b97 commit 8dfb447

File tree

4 files changed

+161
-14
lines changed

4 files changed

+161
-14
lines changed

src/runtime/defer_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,82 @@ func expect(t *testing.T, n int, err interface{}) {
438438
t.Fatalf("have %v, want %v", err, n)
439439
}
440440
}
441+
442+
func TestIssue43920(t *testing.T) {
443+
var steps int
444+
445+
defer func() {
446+
expect(t, 1, recover())
447+
}()
448+
defer func() {
449+
defer func() {
450+
defer func() {
451+
expect(t, 5, recover())
452+
}()
453+
defer panic(5)
454+
func() {
455+
panic(4)
456+
}()
457+
}()
458+
defer func() {
459+
expect(t, 3, recover())
460+
}()
461+
defer panic(3)
462+
}()
463+
func() {
464+
defer step(t, &steps, 1)
465+
panic(1)
466+
}()
467+
}
468+
469+
func step(t *testing.T, steps *int, want int) {
470+
println("step", want)
471+
*steps++
472+
if *steps != want {
473+
t.Fatalf("have %v, want %v", *steps, want)
474+
}
475+
}
476+
477+
func TestIssue43941(t *testing.T) {
478+
var steps int = 7
479+
defer func() {
480+
step(t, &steps, 14)
481+
expect(t, 4, recover())
482+
}()
483+
func() {
484+
func() {
485+
defer func() {
486+
defer func() {
487+
expect(t, 3, recover())
488+
}()
489+
defer panic(3)
490+
panic(2)
491+
}()
492+
defer func() {
493+
expect(t, 1, recover())
494+
}()
495+
defer panic(1)
496+
}()
497+
defer func() {}()
498+
defer func() {}()
499+
defer step(t, &steps, 10)
500+
defer step(t, &steps, 9)
501+
step(t, &steps, 8)
502+
}()
503+
func() {
504+
defer step(t, &steps, 13)
505+
defer step(t, &steps, 12)
506+
func() {
507+
defer step(t, &steps, 11)
508+
panic(4)
509+
}()
510+
511+
// Code below isn't executed,
512+
// but removing it breaks the test case.
513+
defer func() {}()
514+
defer panic(-1)
515+
defer step(t, &steps, -1)
516+
defer step(t, &steps, -1)
517+
defer func() {}()
518+
}()
519+
}

src/runtime/panic.go

+41-14
Original file line numberDiff line numberDiff line change
@@ -560,14 +560,28 @@ func printpanics(p *_panic) {
560560
print("\n")
561561
}
562562

563-
// addOneOpenDeferFrame scans the stack for the first frame (if any) with
564-
// open-coded defers and if it finds one, adds a single record to the defer chain
565-
// for that frame. If sp is non-nil, it starts the stack scan from the frame
566-
// specified by sp. If sp is nil, it uses the sp from the current defer record
567-
// (which has just been finished). Hence, it continues the stack scan from the
568-
// frame of the defer that just finished. It skips any frame that already has an
569-
// open-coded _defer record, which would have been created from a previous
570-
// (unrecovered) panic.
563+
// addOneOpenDeferFrame scans the stack (in gentraceback order, from inner frames to
564+
// outer frames) for the first frame (if any) with open-coded defers. If it finds
565+
// one, it adds a single entry to the defer chain for that frame. The entry added
566+
// represents all the defers in the associated open defer frame, and is sorted in
567+
// order with respect to any non-open-coded defers.
568+
//
569+
// addOneOpenDeferFrame stops (possibly without adding a new entry) if it encounters
570+
// an in-progress open defer entry. An in-progress open defer entry means there has
571+
// been a new panic because of a defer in the associated frame. addOneOpenDeferFrame
572+
// does not add an open defer entry past a started entry, because that started entry
573+
// still needs to finished, and addOneOpenDeferFrame will be called when that started
574+
// entry is completed. The defer removal loop in gopanic() similarly stops at an
575+
// in-progress defer entry. Together, addOneOpenDeferFrame and the defer removal loop
576+
// ensure the invariant that there is no open defer entry further up the stack than
577+
// an in-progress defer, and also that the defer removal loop is guaranteed to remove
578+
// all not-in-progress open defer entries from the defer chain.
579+
//
580+
// If sp is non-nil, addOneOpenDeferFrame starts the stack scan from the frame
581+
// specified by sp. If sp is nil, it uses the sp from the current defer record (which
582+
// has just been finished). Hence, it continues the stack scan from the frame of the
583+
// defer that just finished. It skips any frame that already has a (not-in-progress)
584+
// open-coded _defer record in the defer chain.
571585
//
572586
// Note: All entries of the defer chain (including this new open-coded entry) have
573587
// their pointers (including sp) adjusted properly if the stack moves while
@@ -608,6 +622,16 @@ func addOneOpenDeferFrame(gp *g, pc uintptr, sp unsafe.Pointer) {
608622
if !d.openDefer {
609623
throw("duplicated defer entry")
610624
}
625+
// Don't add any record past an
626+
// in-progress defer entry. We don't
627+
// need it, and more importantly, we
628+
// want to keep the invariant that
629+
// there is no open defer entry
630+
// passed an in-progress entry (see
631+
// header comment).
632+
if d.started {
633+
return false
634+
}
611635
return true
612636
}
613637
prev = d
@@ -849,12 +873,15 @@ func gopanic(e interface{}) {
849873
}
850874
atomic.Xadd(&runningPanicDefers, -1)
851875

852-
// Remove any remaining non-started, open-coded
853-
// defer entries after a recover, since the
854-
// corresponding defers will be executed normally
855-
// (inline). Any such entry will become stale once
856-
// we run the corresponding defers inline and exit
857-
// the associated stack frame.
876+
// After a recover, remove any remaining non-started,
877+
// open-coded defer entries, since the corresponding defers
878+
// will be executed normally (inline). Any such entry will
879+
// become stale once we run the corresponding defers inline
880+
// and exit the associated stack frame. We only remove up to
881+
// the first started (in-progress) open defer entry, not
882+
// including the current frame, since any higher entries will
883+
// be from a higher panic in progress, and will still be
884+
// needed.
858885
d := gp._defer
859886
var prev *_defer
860887
if !done {

test/fixedbugs/issue48898.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// run
2+
3+
// Copyright 2021 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
func main() {
10+
defer func() {
11+
println(recover().(int))
12+
}()
13+
func() {
14+
func() (_ [2]int) { type _ int; return }()
15+
func() {
16+
defer func() {
17+
defer func() {
18+
recover()
19+
}()
20+
defer panic(3)
21+
panic(2)
22+
}()
23+
defer func() {
24+
recover()
25+
}()
26+
panic(1)
27+
}()
28+
defer func() {}()
29+
}()
30+
31+
var x = 123
32+
func() {
33+
// in the original issue, this defer was not executed (which is incorrect)
34+
defer print(x)
35+
func() {
36+
defer func() {}()
37+
panic(4)
38+
}()
39+
}()
40+
}

test/fixedbugs/issue48898.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1234

0 commit comments

Comments
 (0)