aboutsummaryrefslogtreecommitdiff
path: root/test/recover.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2013-09-12 14:00:16 -0400
committerRuss Cox <rsc@golang.org>2013-09-12 14:00:16 -0400
commit7276c02b4193edb19bc0d2d36a786238564db03f (patch)
treee5d13c00ad0b813e8a1edcf9381a8b242780ef2f /test/recover.go
parent1ea0c480dc16a986c2c335ff2965e70d99bfa654 (diff)
downloadgo-7276c02b4193edb19bc0d2d36a786238564db03f.tar.gz
go-7276c02b4193edb19bc0d2d36a786238564db03f.zip
runtime, cmd/gc, cmd/ld: ignore method wrappers in recover
Bug #1: Issue 5406 identified an interesting case: defer iface.M() may end up calling a wrapper that copies an indirect receiver from the iface value and then calls the real M method. That's two calls down, not just one, and so recover() == nil always in the real M method, even during a panic. [For the purposes of this entire discussion, a wrapper's implementation is a function containing an ordinary call, not the optimized tail call form that is somtimes possible. The tail call does not create a second frame, so it is already handled correctly.] Fix this bug by introducing g->panicwrap, which counts the number of bytes on current stack segment that are due to wrapper calls that should not count against the recover check. All wrapper functions must now adjust g->panicwrap up on entry and back down on exit. This adds slightly to their expense; on the x86 it is a single instruction at entry and exit; on the ARM it is three. However, the alternative is to make a call to recover depend on being able to walk the stack, which I very much want to avoid. We have enough problems walking the stack for garbage collection and profiling. Also, if performance is critical in a specific case, it is already faster to use a pointer receiver and avoid this kind of wrapper entirely. Bug #2: The old code, which did not consider the possibility of two calls, already contained a check to see if the call had split its stack and so the panic-created segment was one behind the current segment. In the wrapper case, both of the two calls might split their stacks, so the panic-created segment can be two behind the current segment. Fix this by propagating the Stktop.panic flag forward during stack splits instead of looking backward during recover. Fixes #5406. R=golang-dev, iant CC=golang-dev https://golang.org/cl/13367052
Diffstat (limited to 'test/recover.go')
-rw-r--r--test/recover.go216
1 files changed, 212 insertions, 4 deletions
diff --git a/test/recover.go b/test/recover.go
index 7c27d7c4d6..dc8bcfe801 100644
--- a/test/recover.go
+++ b/test/recover.go
@@ -10,6 +10,7 @@ package main
import (
"os"
+ "reflect"
"runtime"
)
@@ -26,15 +27,39 @@ func main() {
test6()
test6WithClosures()
test7()
+ test8()
+ test9()
+ test9reflect1()
+ test9reflect2()
+ test10()
+ test10reflect1()
+ test10reflect2()
+ test11()
+ test11reflect1()
+ test11reflect2()
+ test12()
+ test12reflect1()
+ test12reflect2()
+ test13()
+ test13reflect1()
+ test13reflect2()
+ test14()
+ test14reflect1()
+ test14reflect2()
+ test15()
}
func die() {
runtime.Breakpoint() // can't depend on panic
}
-func mustRecover(x interface{}) {
- mustNotRecover() // because it's not a defer call
- v := recover()
+func mustRecoverBody(v1, v2, v3, x interface{}) {
+ v := v1
+ if v != nil {
+ println("spurious recover", v)
+ die()
+ }
+ v = v2
if v == nil {
println("missing recover")
die() // panic is useless here
@@ -45,13 +70,21 @@ func mustRecover(x interface{}) {
}
// the value should be gone now regardless
- v = recover()
+ v = v3
if v != nil {
println("recover didn't recover")
die()
}
}
+func doubleRecover() interface{} {
+ return recover()
+}
+
+func mustRecover(x interface{}) {
+ mustRecoverBody(doubleRecover(), recover(), recover(), x)
+}
+
func mustNotRecover() {
v := recover()
if v != nil {
@@ -277,3 +310,178 @@ func test8() {
die()
}
}
+
+type I interface{ M() }
+
+// pointer receiver, so no wrapper in i.M()
+type T1 struct {}
+
+func (*T1) M() {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 9)
+}
+
+func test9() {
+ var i I = &T1{}
+ defer i.M()
+ panic(9)
+}
+
+func test9reflect1() {
+ f := reflect.ValueOf(&T1{}).Method(0).Interface().(func())
+ defer f()
+ panic(9)
+}
+
+func test9reflect2() {
+ f := reflect.TypeOf(&T1{}).Method(0).Func.Interface().(func(*T1))
+ defer f(&T1{})
+ panic(9)
+}
+
+// word-sized value receiver, so no wrapper in i.M()
+type T2 uintptr
+
+func (T2) M() {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 10)
+}
+
+func test10() {
+ var i I = T2(0)
+ defer i.M()
+ panic(10)
+}
+
+func test10reflect1() {
+ f := reflect.ValueOf(T2(0)).Method(0).Interface().(func())
+ defer f()
+ panic(10)
+}
+
+func test10reflect2() {
+ f := reflect.TypeOf(T2(0)).Method(0).Func.Interface().(func(T2))
+ defer f(T2(0))
+ panic(10)
+}
+
+// tiny receiver, so basic wrapper in i.M()
+type T3 struct {}
+
+func (T3) M() {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 11)
+}
+
+func test11() {
+ var i I = T3{}
+ defer i.M()
+ panic(11)
+}
+
+func test11reflect1() {
+ f := reflect.ValueOf(T3{}).Method(0).Interface().(func())
+ defer f()
+ panic(11)
+}
+
+func test11reflect2() {
+ f := reflect.TypeOf(T3{}).Method(0).Func.Interface().(func(T3))
+ defer f(T3{})
+ panic(11)
+}
+
+// large receiver, so basic wrapper in i.M()
+type T4 [2]string
+
+func (T4) M() {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 12)
+}
+
+func test12() {
+ var i I = T4{}
+ defer i.M()
+ panic(12)
+}
+
+func test12reflect1() {
+ f := reflect.ValueOf(T4{}).Method(0).Interface().(func())
+ defer f()
+ panic(12)
+}
+
+func test12reflect2() {
+ f := reflect.TypeOf(T4{}).Method(0).Func.Interface().(func(T4))
+ defer f(T4{})
+ panic(12)
+}
+
+// enormous receiver, so wrapper splits stack to call M
+type T5 [8192]byte
+
+func (T5) M() {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 13)
+}
+
+func test13() {
+ var i I = T5{}
+ defer i.M()
+ panic(13)
+}
+
+func test13reflect1() {
+ f := reflect.ValueOf(T5{}).Method(0).Interface().(func())
+ defer f()
+ panic(13)
+}
+
+func test13reflect2() {
+ f := reflect.TypeOf(T5{}).Method(0).Func.Interface().(func(T5))
+ defer f(T5{})
+ panic(13)
+}
+
+// enormous receiver + enormous method frame, so wrapper splits stack to call M,
+// and then M splits stack to allocate its frame.
+// recover must look back two frames to find the panic.
+type T6 [8192]byte
+
+var global byte
+
+func (T6) M() {
+ var x [8192]byte
+ x[0] = 1
+ x[1] = 2
+ for i := range x {
+ global += x[i]
+ }
+ mustRecoverBody(doubleRecover(), recover(), recover(), 14)
+}
+
+func test14() {
+ var i I = T6{}
+ defer i.M()
+ panic(14)
+}
+
+func test14reflect1() {
+ f := reflect.ValueOf(T6{}).Method(0).Interface().(func())
+ defer f()
+ panic(14)
+}
+
+func test14reflect2() {
+ f := reflect.TypeOf(T6{}).Method(0).Func.Interface().(func(T6))
+ defer f(T6{})
+ panic(14)
+}
+
+// function created by reflect.MakeFunc
+
+func reflectFunc(args []reflect.Value) (results []reflect.Value) {
+ mustRecoverBody(doubleRecover(), recover(), recover(), 15)
+ return nil
+}
+
+func test15() {
+ f := reflect.MakeFunc(reflect.TypeOf((func())(nil)), reflectFunc).Interface().(func())
+ defer f()
+ panic(15)
+}