Skip to content

Commit 7b50024

Browse files
committed
runtime: detect successful recovers differently
Use stack unwinding instead of keeping incremental track of the argp of defers that are allowed to recover. It's much simpler, and it lets us get rid of the incremental tracking by wrapper code. (Ripped out in a subsequent CL.) We only need to stack unwind a few frames to get the right answer, and only when recover()ing in a panic situation. It will be more expensive in that case, but cheaper in all others. Change-Id: Id095807db6864b7ac1e1baf09285b77a07c46d19 Reviewed-on: https://go-review.googlesource.com/c/go/+/685355 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]>
1 parent 7b9de66 commit 7b50024

File tree

2 files changed

+80
-18
lines changed

2 files changed

+80
-18
lines changed

src/runtime/panic.go

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ func gopanic(e any) {
864864

865865
var p _panic
866866
p.arg = e
867+
p.gopanicFP = unsafe.Pointer(sys.GetCallerSP())
867868

868869
runningPanicDefers.Add(1)
869870

@@ -1086,27 +1087,86 @@ func (p *_panic) initOpenCodedDefers(fn funcInfo, varp unsafe.Pointer) bool {
10861087
}
10871088

10881089
// The implementation of the predeclared function recover.
1089-
// Cannot split the stack because it needs to reliably
1090-
// find the stack segment of its caller.
1091-
//
1092-
// TODO(rsc): Once we commit to CopyStackAlways,
1093-
// this doesn't need to be nosplit.
1094-
//
1095-
//go:nosplit
1096-
func gorecover(argp uintptr) any {
1097-
// Must be in a function running as part of a deferred call during the panic.
1098-
// Must be called from the topmost function of the call
1099-
// (the function used in the defer statement).
1100-
// p.argp is the argument pointer of that topmost deferred function call.
1101-
// Compare against argp reported by caller.
1102-
// If they match, the caller is the one who can recover.
1090+
func gorecover(_ uintptr) any {
11031091
gp := getg()
11041092
p := gp._panic
1105-
if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
1106-
p.recovered = true
1107-
return p.arg
1093+
if p == nil || p.goexit || p.recovered {
1094+
return nil
1095+
}
1096+
1097+
// Check to see if the function that called recover() was
1098+
// deferred directly from the panicking function.
1099+
// For code like:
1100+
// func foo() {
1101+
// defer bar()
1102+
// panic("panic")
1103+
// }
1104+
// func bar() {
1105+
// recover()
1106+
// }
1107+
// Normally the stack would look like this:
1108+
// foo
1109+
// runtime.gopanic
1110+
// bar
1111+
// runtime.gorecover
1112+
//
1113+
// However, if the function we deferred requires a wrapper
1114+
// of some sort, we need to ignore the wrapper. In that case,
1115+
// the stack looks like:
1116+
// foo
1117+
// runtime.gopanic
1118+
// wrapper
1119+
// bar
1120+
// runtime.gorecover
1121+
// And we should also successfully recover.
1122+
//
1123+
// Finally, in the weird case "defer recover()", the stack looks like:
1124+
// foo
1125+
// runtime.gopanic
1126+
// wrapper
1127+
// runtime.gorecover
1128+
// And we should not recover in that case.
1129+
//
1130+
// So our criteria is, there must be exactly one non-wrapper
1131+
// frame between gopanic and gorecover.
1132+
//
1133+
// We don't recover this:
1134+
// defer func() { func() { recover() }() }
1135+
// because there are 2 non-wrapper frames.
1136+
//
1137+
// We don't recover this:
1138+
// defer recover()
1139+
// because there are 0 non-wrapper frames.
1140+
canRecover := false
1141+
systemstack(func() {
1142+
var u unwinder
1143+
u.init(gp, 0)
1144+
u.next() // skip systemstack_switch
1145+
u.next() // skip gorecover
1146+
nonWrapperFrames := 0
1147+
loop:
1148+
for ; u.valid(); u.next() {
1149+
switch u.frame.fn.funcID {
1150+
case abi.FuncIDWrapper:
1151+
continue
1152+
case abi.FuncID_gopanic:
1153+
if u.frame.fp == uintptr(p.gopanicFP) && nonWrapperFrames > 0 {
1154+
canRecover = true
1155+
}
1156+
break loop
1157+
default:
1158+
nonWrapperFrames++
1159+
if nonWrapperFrames > 1 {
1160+
break loop
1161+
}
1162+
}
1163+
}
1164+
})
1165+
if !canRecover {
1166+
return nil
11081167
}
1109-
return nil
1168+
p.recovered = true
1169+
return p.arg
11101170
}
11111171

11121172
//go:linkname sync_throw sync.throw

src/runtime/runtime2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,8 @@ type _panic struct {
10301030
repanicked bool // whether this panic repanicked
10311031
goexit bool
10321032
deferreturn bool
1033+
1034+
gopanicFP unsafe.Pointer // frame pointer of the gopanic frame
10331035
}
10341036

10351037
// savedOpenDeferState tracks the extra state from _panic that's

0 commit comments

Comments
 (0)