Skip to content

Commit fae87a2

Browse files
committed
runtime: fix problem with repeated panic/recover/re-panics and open-coded defers
In the open-code defer implementation, we add defer struct entries to the defer chain on-the-fly at panic time to represent stack frames that contain open-coded defers. This allows us to process non-open-coded and open-coded defers in the correct order. Also, we need somewhere to be able to store the 'started' state of open-coded defers. However, if a recover succeeds, defers will now be processed inline again (unless another panic happens). Any defer entry representing a frame with open-coded defers will become stale once we run the corresponding defers inline and exit the associated stack frame. So, we need to remove all entries for open-coded defers at recover time. The current code was only removing the top-most open-coded defer from the defer chain during recovery. However, with recursive functions that do repeated panic-recover-repanic, multiple stale entries can accumulate on the chain. So, we just adjust the loop to process the entire chain. Since this is at panic/recover case, it is fine to scan through the entire chain (which should usually have few elements in it, since most defers are open-coded). The added test fails with a SEGV without the fix, because it tries to run a stale open-code defer entry (and the stack has changed). Fixes #37664. Change-Id: I8e3da5d610b5e607411451b66881dea887f7484d Reviewed-on: https://go-review.googlesource.com/c/go/+/222420 Run-TryBot: Dan Scales <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 5db3c8f commit fae87a2

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

src/runtime/defer_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package runtime_test
66

77
import (
88
"fmt"
9+
"os"
910
"reflect"
1011
"runtime"
1112
"testing"
@@ -281,3 +282,56 @@ func TestDeferForFuncWithNoExit(t *testing.T) {
281282
for {
282283
}
283284
}
285+
286+
// Test case approximating issue #37664, where a recursive function (interpreter)
287+
// may do repeated recovers/re-panics until it reaches the frame where the panic
288+
// can actually be handled. The recurseFnPanicRec() function is testing that there
289+
// are no stale defer structs on the defer chain after the interpreter() sequence,
290+
// by writing a bunch of 0xffffffffs into several recursive stack frames, and then
291+
// doing a single panic-recover which would invoke any such stale defer structs.
292+
func TestDeferWithRepeatedRepanics(t *testing.T) {
293+
interpreter(0, 6, 2)
294+
recurseFnPanicRec(0, 10)
295+
interpreter(0, 5, 1)
296+
recurseFnPanicRec(0, 10)
297+
interpreter(0, 6, 3)
298+
recurseFnPanicRec(0, 10)
299+
}
300+
301+
func interpreter(level int, maxlevel int, rec int) {
302+
defer func() {
303+
e := recover()
304+
if e == nil {
305+
return
306+
}
307+
if level != e.(int) {
308+
//fmt.Fprintln(os.Stderr, "re-panicing, level", level)
309+
panic(e)
310+
}
311+
//fmt.Fprintln(os.Stderr, "Recovered, level", level)
312+
}()
313+
if level+1 < maxlevel {
314+
interpreter(level+1, maxlevel, rec)
315+
} else {
316+
//fmt.Fprintln(os.Stderr, "Initiating panic")
317+
panic(rec)
318+
}
319+
}
320+
321+
func recurseFnPanicRec(level int, maxlevel int) {
322+
defer func() {
323+
recover()
324+
}()
325+
recurseFn(level, maxlevel)
326+
}
327+
328+
func recurseFn(level int, maxlevel int) {
329+
a := [40]uint32{0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}
330+
if level+1 < maxlevel {
331+
// Need this print statement to keep a around. '_ = a[4]' doesn't do it.
332+
fmt.Fprintln(os.Stderr, "recurseFn", level, a[4])
333+
recurseFn(level+1, maxlevel)
334+
} else {
335+
panic("recurseFn panic")
336+
}
337+
}

src/runtime/panic.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,11 +1003,12 @@ func gopanic(e interface{}) {
10031003
atomic.Xadd(&runningPanicDefers, -1)
10041004

10051005
if done {
1006-
// Remove any remaining non-started, open-coded defer
1007-
// entry after a recover (there's at most one, if we just
1008-
// ran a non-open-coded defer), since the entry will
1009-
// become out-dated and the defer will be executed
1010-
// normally.
1006+
// Remove any remaining non-started, open-coded
1007+
// defer entries after a recover, since the
1008+
// corresponding defers will be executed normally
1009+
// (inline). Any such entry will become stale once
1010+
// we run the corresponding defers inline and exit
1011+
// the associated stack frame.
10111012
d := gp._defer
10121013
var prev *_defer
10131014
for d != nil {
@@ -1025,8 +1026,9 @@ func gopanic(e interface{}) {
10251026
} else {
10261027
prev.link = d.link
10271028
}
1029+
newd := d.link
10281030
freedefer(d)
1029-
break
1031+
d = newd
10301032
} else {
10311033
prev = d
10321034
d = d.link

0 commit comments

Comments
 (0)