diff options
author | Russ Cox <rsc@golang.org> | 2013-09-12 14:00:16 -0400 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2013-09-12 14:00:16 -0400 |
commit | 7276c02b4193edb19bc0d2d36a786238564db03f (patch) | |
tree | e5d13c00ad0b813e8a1edcf9381a8b242780ef2f /test/recover.go | |
parent | 1ea0c480dc16a986c2c335ff2965e70d99bfa654 (diff) | |
download | go-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.go | 216 |
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) +} |