runtime: iterate through inlinings when processing recover()

We care about the wrapper-ness of logical frames, not physical frames.

Fixes #73916
Fixes #73917
Fixex #73920

Change-Id: Ia17c8390e71e6c0e13e23dcbb7bc7273ef25da90
Reviewed-on: https://go-review.googlesource.com/c/go/+/685375
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Keith Randall 2025-07-01 15:29:12 -07:00
parent c76c3abc54
commit 08376e1a9c
5 changed files with 160 additions and 11 deletions

View file

@ -1142,18 +1142,21 @@ func gorecover(_ uintptr) any {
nonWrapperFrames := 0
loop:
for ; u.valid(); u.next() {
switch u.frame.fn.funcID {
case abi.FuncIDWrapper:
continue
case abi.FuncID_gopanic:
if u.frame.fp == uintptr(p.gopanicFP) && nonWrapperFrames > 0 {
canRecover = true
}
break loop
default:
nonWrapperFrames++
if nonWrapperFrames > 1 {
for iu, f := newInlineUnwinder(u.frame.fn, u.symPC()); f.valid(); f = iu.next(f) {
sf := iu.srcFunc(f)
switch sf.funcID {
case abi.FuncIDWrapper:
continue
case abi.FuncID_gopanic:
if u.frame.fp == uintptr(p.gopanicFP) && nonWrapperFrames > 0 {
canRecover = true
}
break loop
default:
nonWrapperFrames++
if nonWrapperFrames > 1 {
break loop
}
}
}
}

View file

@ -0,0 +1,32 @@
// run
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func callRecover() {
if recover() != nil {
println("recovered")
}
}
func F(int) { callRecover() }
func main() {
mustPanic(func() {
defer F(1)
panic("XXX")
})
}
func mustPanic(f func()) {
defer func() {
r := recover()
if r == nil {
panic("didn't panic")
}
}()
f()
}

View file

@ -0,0 +1,34 @@
// run
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func callRecover() {
func() {
if recover() != nil {
println("recovered")
}
}()
}
func F() int { callRecover(); return 0 }
func main() {
mustPanic(func() {
defer F()
panic("XXX")
})
}
func mustPanic(f func()) {
defer func() {
r := recover()
if r == nil {
panic("didn't panic")
}
}()
f()
}

View file

@ -0,0 +1,40 @@
// run
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func callRecover() {
if recover() != nil {
println("recovered")
}
}
type T int
func (*T) M() { callRecover() }
type S struct{ *T } // has a wrapper S.M wrapping (*T.M)
var p = S{new(T)}
var fn = S.M // using a function pointer to force using the wrapper
func main() {
mustPanic(func() {
defer fn(p)
panic("XXX")
})
}
func mustPanic(f func()) {
defer func() {
r := recover()
if r == nil {
panic("didn't panic")
}
}()
f()
}

View file

@ -0,0 +1,40 @@
// run
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func callRecover() {
if recover() != nil {
println("recovered")
}
}
type T int
func (*T) M() { callRecover() }
type S struct{ *T } // has a wrapper (*S).M wrapping (*T.M)
var p = &S{new(T)}
var fn = (*S).M // using a function pointer to force using the wrapper
func main() {
mustPanic(func() {
defer fn(p)
panic("XXX")
})
}
func mustPanic(f func()) {
defer func() {
r := recover()
if r == nil {
panic("didn't panic")
}
}()
f()
}