mirror of
https://github.com/golang/go
synced 2024-11-23 00:30:07 -07:00
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
This commit is contained in:
parent
1ea0c480dc
commit
7276c02b41
@ -271,6 +271,35 @@ noops(void)
|
||||
p->to.offset = -autosize;
|
||||
p->to.reg = REGSP;
|
||||
p->spadj = autosize;
|
||||
|
||||
if(cursym->text->reg & WRAPPER) {
|
||||
// g->panicwrap += autosize;
|
||||
// MOVW panicwrap_offset(g), R3
|
||||
// ADD $autosize, R3
|
||||
// MOVW R3 panicwrap_offset(g)
|
||||
p = appendp(p);
|
||||
p->as = AMOVW;
|
||||
p->from.type = D_OREG;
|
||||
p->from.reg = REGG;
|
||||
p->from.offset = 2*PtrSize;
|
||||
p->to.type = D_REG;
|
||||
p->to.reg = 3;
|
||||
|
||||
p = appendp(p);
|
||||
p->as = AADD;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autosize;
|
||||
p->to.type = D_REG;
|
||||
p->to.reg = 3;
|
||||
|
||||
p = appendp(p);
|
||||
p->as = AMOVW;
|
||||
p->from.type = D_REG;
|
||||
p->from.reg = 3;
|
||||
p->to.type = D_OREG;
|
||||
p->to.reg = REGG;
|
||||
p->to.offset = 2*PtrSize;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARET:
|
||||
@ -290,6 +319,36 @@ noops(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cursym->text->reg & WRAPPER) {
|
||||
// g->panicwrap -= autosize;
|
||||
// MOVW panicwrap_offset(g), R3
|
||||
// SUB $autosize, R3
|
||||
// MOVW R3 panicwrap_offset(g)
|
||||
p->as = AMOVW;
|
||||
p->from.type = D_OREG;
|
||||
p->from.reg = REGG;
|
||||
p->from.offset = 2*PtrSize;
|
||||
p->to.type = D_REG;
|
||||
p->to.reg = 3;
|
||||
p = appendp(p);
|
||||
|
||||
p->as = ASUB;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autosize;
|
||||
p->to.type = D_REG;
|
||||
p->to.reg = 3;
|
||||
p = appendp(p);
|
||||
|
||||
p->as = AMOVW;
|
||||
p->from.type = D_REG;
|
||||
p->from.reg = 3;
|
||||
p->to.type = D_OREG;
|
||||
p->to.reg = REGG;
|
||||
p->to.offset = 2*PtrSize;
|
||||
p = appendp(p);
|
||||
}
|
||||
|
||||
p->as = AMOVW;
|
||||
p->scond |= C_PBIT;
|
||||
p->from.type = D_OREG;
|
||||
|
@ -511,11 +511,12 @@ dostkoff(void)
|
||||
diag("nosplit func likely to overflow stack");
|
||||
|
||||
q = P;
|
||||
if(!(p->from.scale & NOSPLIT)) {
|
||||
if(!(p->from.scale & NOSPLIT) || (p->from.scale & WRAPPER)) {
|
||||
p = appendp(p);
|
||||
p = load_g_cx(p); // load g into CX
|
||||
p = stacksplit(p, autoffset, &q); // emit split check
|
||||
}
|
||||
if(!(cursym->text->from.scale & NOSPLIT))
|
||||
p = stacksplit(p, autoffset, &q); // emit split check
|
||||
|
||||
if(autoffset) {
|
||||
p = appendp(p);
|
||||
@ -523,8 +524,6 @@ dostkoff(void)
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset;
|
||||
p->spadj = autoffset;
|
||||
if(q != P)
|
||||
q->pcond = p;
|
||||
} else {
|
||||
// zero-byte stack adjustment.
|
||||
// Insert a fake non-zero adjustment so that stkcheck can
|
||||
@ -536,7 +535,19 @@ dostkoff(void)
|
||||
p->as = ANOP;
|
||||
p->spadj = PtrSize;
|
||||
}
|
||||
if(q != P)
|
||||
q->pcond = p;
|
||||
deltasp = autoffset;
|
||||
|
||||
if(cursym->text->from.scale & WRAPPER) {
|
||||
// g->panicwrap += autoffset + PtrSize;
|
||||
p = appendp(p);
|
||||
p->as = AADDL;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset + PtrSize;
|
||||
p->to.type = D_INDIR+D_CX;
|
||||
p->to.offset = 2*PtrSize;
|
||||
}
|
||||
|
||||
if(debug['K'] > 1 && autoffset) {
|
||||
// 6l -KK means double-check for stack overflow
|
||||
@ -654,6 +665,19 @@ dostkoff(void)
|
||||
|
||||
if(autoffset != deltasp)
|
||||
diag("unbalanced PUSH/POP");
|
||||
|
||||
if(cursym->text->from.scale & WRAPPER) {
|
||||
p = load_g_cx(p);
|
||||
p = appendp(p);
|
||||
// g->panicwrap -= autoffset + PtrSize;
|
||||
p->as = ASUBL;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset + PtrSize;
|
||||
p->to.type = D_INDIR+D_CX;
|
||||
p->to.offset = 2*PtrSize;
|
||||
p = appendp(p);
|
||||
p->as = ARET;
|
||||
}
|
||||
|
||||
if(autoffset) {
|
||||
p->as = AADJSP;
|
||||
|
@ -444,11 +444,12 @@ dostkoff(void)
|
||||
|
||||
q = P;
|
||||
|
||||
if(!(p->from.scale & NOSPLIT)) {
|
||||
if(!(p->from.scale & NOSPLIT) || (p->from.scale & WRAPPER)) {
|
||||
p = appendp(p);
|
||||
p = load_g_cx(p); // load g into CX
|
||||
p = stacksplit(p, autoffset, &q); // emit split check
|
||||
}
|
||||
if(!(cursym->text->from.scale & NOSPLIT))
|
||||
p = stacksplit(p, autoffset, &q); // emit split check
|
||||
|
||||
if(autoffset) {
|
||||
p = appendp(p);
|
||||
@ -456,8 +457,6 @@ dostkoff(void)
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset;
|
||||
p->spadj = autoffset;
|
||||
if(q != P)
|
||||
q->pcond = p;
|
||||
} else {
|
||||
// zero-byte stack adjustment.
|
||||
// Insert a fake non-zero adjustment so that stkcheck can
|
||||
@ -469,8 +468,20 @@ dostkoff(void)
|
||||
p->as = ANOP;
|
||||
p->spadj = PtrSize;
|
||||
}
|
||||
if(q != P)
|
||||
q->pcond = p;
|
||||
deltasp = autoffset;
|
||||
|
||||
if(cursym->text->from.scale & WRAPPER) {
|
||||
// g->panicwrap += autoffset + PtrSize;
|
||||
p = appendp(p);
|
||||
p->as = AADDL;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset + PtrSize;
|
||||
p->to.type = D_INDIR+D_CX;
|
||||
p->to.offset = 2*PtrSize;
|
||||
}
|
||||
|
||||
if(debug['Z'] && autoffset && !(cursym->text->from.scale&NOSPLIT)) {
|
||||
// 8l -Z means zero the stack frame on entry.
|
||||
// This slows down function calls but can help avoid
|
||||
@ -540,6 +551,19 @@ dostkoff(void)
|
||||
|
||||
if(autoffset != deltasp)
|
||||
diag("unbalanced PUSH/POP");
|
||||
|
||||
if(cursym->text->from.scale & WRAPPER) {
|
||||
p = load_g_cx(p);
|
||||
p = appendp(p);
|
||||
// g->panicwrap -= autoffset + PtrSize;
|
||||
p->as = ASUBL;
|
||||
p->from.type = D_CONST;
|
||||
p->from.offset = autoffset + PtrSize;
|
||||
p->to.type = D_INDIR+D_CX;
|
||||
p->to.offset = 2*PtrSize;
|
||||
p = appendp(p);
|
||||
p->as = ARET;
|
||||
}
|
||||
|
||||
if(autoffset) {
|
||||
p->as = AADJSP;
|
||||
|
@ -272,6 +272,7 @@ struct Node
|
||||
uchar implicit;
|
||||
uchar addrtaken; // address taken, even if not moved to heap
|
||||
uchar dupok; // duplicate definitions ok (for func)
|
||||
uchar wrapper; // is method wrapper (for func)
|
||||
schar likely; // likeliness of if statement
|
||||
uchar hasbreak; // has break statement
|
||||
uchar needzero; // if it contains pointers, needs to be zeroed on function entry
|
||||
|
@ -95,7 +95,18 @@ compile(Node *fn)
|
||||
nodconst(&nod1, types[TINT32], 0);
|
||||
ptxt = gins(ATEXT, isblank(curfn->nname) ? N : curfn->nname, &nod1);
|
||||
if(fn->dupok)
|
||||
ptxt->TEXTFLAG = DUPOK;
|
||||
ptxt->TEXTFLAG |= DUPOK;
|
||||
if(fn->wrapper)
|
||||
ptxt->TEXTFLAG |= WRAPPER;
|
||||
|
||||
// Clumsy but important.
|
||||
// See test/recover.go for test cases and src/pkg/reflect/value.go
|
||||
// for the actual functions being considered.
|
||||
if(myimportpath != nil && strcmp(myimportpath, "reflect") == 0) {
|
||||
if(strcmp(curfn->nname->sym->name, "callReflect") == 0 || strcmp(curfn->nname->sym->name, "callMethod") == 0)
|
||||
ptxt->TEXTFLAG |= WRAPPER;
|
||||
}
|
||||
|
||||
afunclit(&ptxt->from, curfn->nname);
|
||||
|
||||
ginit();
|
||||
|
@ -2588,6 +2588,7 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface)
|
||||
n->left = newname(methodsym(method->sym, methodrcvr, 0));
|
||||
fn->nbody = list(fn->nbody, n);
|
||||
} else {
|
||||
fn->wrapper = 1; // ignore frame for panic+recover matching
|
||||
call = nod(OCALL, dot, N);
|
||||
call->list = args;
|
||||
call->isddd = isddd;
|
||||
|
@ -17,3 +17,5 @@
|
||||
#define RODATA 8
|
||||
// This data contains no pointers.
|
||||
#define NOPTR 16
|
||||
// This is a wrapper function and should not count as disabling 'recover'.
|
||||
#define WRAPPER 32
|
||||
|
@ -8,7 +8,7 @@
|
||||
// See the comment on the declaration of makeFuncStub in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·makeFuncStub(SB),NOSPLIT,$8
|
||||
TEXT ·makeFuncStub(SB),(NOSPLIT|WRAPPER),$8
|
||||
MOVL DX, 0(SP)
|
||||
LEAL argframe+0(FP), CX
|
||||
MOVL CX, 4(SP)
|
||||
@ -19,7 +19,7 @@ TEXT ·makeFuncStub(SB),NOSPLIT,$8
|
||||
// See the comment on the declaration of methodValueCall in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·methodValueCall(SB),NOSPLIT,$8
|
||||
TEXT ·methodValueCall(SB),(NOSPLIT|WRAPPER),$8
|
||||
MOVL DX, 0(SP)
|
||||
LEAL argframe+0(FP), CX
|
||||
MOVL CX, 4(SP)
|
||||
|
@ -8,7 +8,7 @@
|
||||
// See the comment on the declaration of makeFuncStub in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·makeFuncStub(SB),NOSPLIT,$16
|
||||
TEXT ·makeFuncStub(SB),(NOSPLIT|WRAPPER),$16
|
||||
MOVQ DX, 0(SP)
|
||||
LEAQ argframe+0(FP), CX
|
||||
MOVQ CX, 8(SP)
|
||||
@ -19,7 +19,7 @@ TEXT ·makeFuncStub(SB),NOSPLIT,$16
|
||||
// See the comment on the declaration of methodValueCall in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·methodValueCall(SB),NOSPLIT,$16
|
||||
TEXT ·methodValueCall(SB),(NOSPLIT|WRAPPER),$16
|
||||
MOVQ DX, 0(SP)
|
||||
LEAQ argframe+0(FP), CX
|
||||
MOVQ CX, 8(SP)
|
||||
|
@ -8,7 +8,7 @@
|
||||
// See the comment on the declaration of makeFuncStub in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·makeFuncStub(SB),NOSPLIT,$8
|
||||
TEXT ·makeFuncStub(SB),(NOSPLIT|WRAPPER),$8
|
||||
MOVW R7, 4(R13)
|
||||
MOVW $argframe+0(FP), R1
|
||||
MOVW R1, 8(R13)
|
||||
@ -19,7 +19,7 @@ TEXT ·makeFuncStub(SB),NOSPLIT,$8
|
||||
// See the comment on the declaration of methodValueCall in makefunc.go
|
||||
// for more details.
|
||||
// No argsize here, gc generates argsize info at call site.
|
||||
TEXT ·methodValueCall(SB),NOSPLIT,$8
|
||||
TEXT ·methodValueCall(SB),(NOSPLIT|WRAPPER),$8
|
||||
MOVW R7, 4(R13)
|
||||
MOVW $argframe+0(FP), R1
|
||||
MOVW R1, 8(R13)
|
||||
|
@ -497,6 +497,10 @@ func (v Value) call(op string, in []Value) []Value {
|
||||
// frame into a call using Values.
|
||||
// It is in this file so that it can be next to the call method above.
|
||||
// The remainder of the MakeFunc implementation is in makefunc.go.
|
||||
//
|
||||
// NOTE: This function must be marked as a "wrapper" in the generated code,
|
||||
// so that the linker can make it work correctly for panic and recover.
|
||||
// The gc compilers know to do that for the name "reflect.callReflect".
|
||||
func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) {
|
||||
ftyp := ctxt.typ
|
||||
f := ctxt.fn
|
||||
@ -650,6 +654,10 @@ func frameSize(t *rtype, rcvr bool) (total, in, outOffset, out uintptr) {
|
||||
// to deal with individual Values for each argument.
|
||||
// It is in this file so that it can be next to the two similar functions above.
|
||||
// The remainder of the makeMethodValue implementation is in makefunc.go.
|
||||
//
|
||||
// NOTE: This function must be marked as a "wrapper" in the generated code,
|
||||
// so that the linker can make it work correctly for panic and recover.
|
||||
// The gc compilers know to do that for the name "reflect.callMethod".
|
||||
func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
|
||||
t, fn, rcvr := methodReceiver("call", ctxt.rcvr, ctxt.method)
|
||||
total, in, outOffset, out := frameSize(t, true)
|
||||
|
@ -340,7 +340,7 @@ TEXT reflect·call(SB), NOSPLIT, $0-12
|
||||
JMP AX
|
||||
|
||||
#define CALLFN(NAME,MAXSIZE) \
|
||||
TEXT runtime·NAME(SB), 0, $MAXSIZE-12; \
|
||||
TEXT runtime·NAME(SB), WRAPPER, $MAXSIZE-12; \
|
||||
/* copy arguments to stack */ \
|
||||
MOVL argptr+4(FP), SI; \
|
||||
MOVL argsize+8(FP), CX; \
|
||||
|
@ -319,7 +319,7 @@ TEXT reflect·call(SB), NOSPLIT, $0-20
|
||||
JMP AX
|
||||
|
||||
#define CALLFN(NAME,MAXSIZE) \
|
||||
TEXT runtime·NAME(SB), 0, $MAXSIZE-20; \
|
||||
TEXT runtime·NAME(SB), WRAPPER, $MAXSIZE-20; \
|
||||
/* copy arguments to stack */ \
|
||||
MOVQ argptr+8(FP), SI; \
|
||||
MOVLQZX argsize+16(FP), CX; \
|
||||
|
@ -300,7 +300,7 @@ TEXT reflect·call(SB), NOSPLIT, $-4-12
|
||||
B (R1)
|
||||
|
||||
#define CALLFN(NAME,MAXSIZE) \
|
||||
TEXT runtime·NAME(SB), 0, $MAXSIZE-12; \
|
||||
TEXT runtime·NAME(SB), WRAPPER, $MAXSIZE-12; \
|
||||
/* copy arguments to stack */ \
|
||||
MOVW argptr+4(FP), R0; \
|
||||
MOVW argsize+8(FP), R2; \
|
||||
|
@ -339,69 +339,27 @@ runtime·unwindstack(G *gp, byte *sp)
|
||||
void
|
||||
runtime·recover(byte *argp, Eface ret)
|
||||
{
|
||||
Stktop *top, *oldtop;
|
||||
Panic *p;
|
||||
Stktop *top;
|
||||
|
||||
// Must be a panic going on.
|
||||
if((p = g->panic) == nil || p->recovered)
|
||||
goto nomatch;
|
||||
|
||||
// Frame must be at the top of the stack segment,
|
||||
// because each deferred call starts a new stack
|
||||
// segment as a side effect of using reflect.call.
|
||||
// (There has to be some way to remember the
|
||||
// variable argument frame size, and the segment
|
||||
// code already takes care of that for us, so we
|
||||
// reuse it.)
|
||||
//
|
||||
// As usual closures complicate things: the fp that
|
||||
// the closure implementation function claims to have
|
||||
// is where the explicit arguments start, after the
|
||||
// implicit pointer arguments and PC slot.
|
||||
// If we're on the first new segment for a closure,
|
||||
// then fp == top - top->args is correct, but if
|
||||
// the closure has its own big argument frame and
|
||||
// allocated a second segment (see below),
|
||||
// the fp is slightly above top - top->args.
|
||||
// That condition can't happen normally though
|
||||
// (stack pointers go down, not up), so we can accept
|
||||
// any fp between top and top - top->args as
|
||||
// indicating the top of the segment.
|
||||
// Must be an unrecovered panic in progress.
|
||||
// Must be on a stack segment created for a deferred call during a panic.
|
||||
// Must be at the top of that segment, meaning the deferred call itself
|
||||
// and not something it called. The top frame in the segment will have
|
||||
// argument pointer argp == top - top->argsize.
|
||||
// The subtraction of g->panicwrap allows wrapper functions that
|
||||
// do not count as official calls to adjust what we consider the top frame
|
||||
// while they are active on the stack. The linker emits adjustments of
|
||||
// g->panicwrap in the prologue and epilogue of functions marked as wrappers.
|
||||
top = (Stktop*)g->stackbase;
|
||||
if(argp < (byte*)top - top->argsize || (byte*)top < argp)
|
||||
goto nomatch;
|
||||
|
||||
// The deferred call makes a new segment big enough
|
||||
// for the argument frame but not necessarily big
|
||||
// enough for the function's local frame (size unknown
|
||||
// at the time of the call), so the function might have
|
||||
// made its own segment immediately. If that's the
|
||||
// case, back top up to the older one, the one that
|
||||
// reflect.call would have made for the panic.
|
||||
//
|
||||
// The fp comparison here checks that the argument
|
||||
// frame that was copied during the split (the top->args
|
||||
// bytes above top->fp) abuts the old top of stack.
|
||||
// This is a correct test for both closure and non-closure code.
|
||||
oldtop = (Stktop*)top->stackbase;
|
||||
if(oldtop != nil && top->argp == (byte*)oldtop - top->argsize)
|
||||
top = oldtop;
|
||||
|
||||
// Now we have the segment that was created to
|
||||
// run this call. It must have been marked as a panic segment.
|
||||
if(!top->panic)
|
||||
goto nomatch;
|
||||
|
||||
// Okay, this is the top frame of a deferred call
|
||||
// in response to a panic. It can see the panic argument.
|
||||
p->recovered = 1;
|
||||
ret = p->arg;
|
||||
FLUSH(&ret);
|
||||
return;
|
||||
|
||||
nomatch:
|
||||
ret.type = nil;
|
||||
ret.data = nil;
|
||||
p = g->panic;
|
||||
if(p != nil && !p->recovered && top->panic && argp == (byte*)top - top->argsize - g->panicwrap) {
|
||||
p->recovered = 1;
|
||||
ret = p->arg;
|
||||
} else {
|
||||
ret.type = nil;
|
||||
ret.data = nil;
|
||||
}
|
||||
FLUSH(&ret);
|
||||
}
|
||||
|
||||
|
@ -1773,6 +1773,7 @@ runtime·newproc1(FuncVal *fn, byte *argp, int32 narg, int32 nret, void *callerp
|
||||
newg->gopc = (uintptr)callerpc;
|
||||
newg->status = Grunnable;
|
||||
newg->goid = runtime·xadd64(&runtime·sched.goidgen, 1);
|
||||
newg->panicwrap = 0;
|
||||
if(raceenabled)
|
||||
newg->racectx = runtime·racegostart((void*)callerpc);
|
||||
runqput(m->p, newg);
|
||||
|
@ -250,6 +250,8 @@ struct G
|
||||
// stackguard0 can be set to StackPreempt as opposed to stackguard
|
||||
uintptr stackguard0; // cannot move - also known to linker, libmach, runtime/cgo
|
||||
uintptr stackbase; // cannot move - also known to libmach, runtime/cgo
|
||||
uint32 panicwrap; // cannot move - also known to linker
|
||||
uint32 selgen; // valid sudog pointer
|
||||
Defer* defer;
|
||||
Panic* panic;
|
||||
Gobuf sched;
|
||||
@ -264,7 +266,6 @@ struct G
|
||||
void* param; // passed parameter on wakeup
|
||||
int16 status;
|
||||
int64 goid;
|
||||
uint32 selgen; // valid sudog pointer
|
||||
int8* waitreason; // if status==Gwaiting
|
||||
G* schedlink;
|
||||
bool ispanic;
|
||||
@ -403,6 +404,7 @@ struct Stktop
|
||||
uintptr stackbase;
|
||||
Gobuf gobuf;
|
||||
uint32 argsize;
|
||||
uint32 panicwrap;
|
||||
|
||||
uint8* argp; // pointer to arguments in old frame
|
||||
uintptr free; // if free>0, call stackfree using free as size
|
||||
|
@ -174,6 +174,7 @@ runtime·oldstack(void)
|
||||
gp->stackbase = top->stackbase;
|
||||
gp->stackguard = top->stackguard;
|
||||
gp->stackguard0 = gp->stackguard;
|
||||
gp->panicwrap = top->panicwrap;
|
||||
|
||||
if(top->free != 0) {
|
||||
gp->stacksize -= top->free;
|
||||
@ -195,7 +196,7 @@ void
|
||||
runtime·newstack(void)
|
||||
{
|
||||
int32 framesize, argsize, oldstatus;
|
||||
Stktop *top;
|
||||
Stktop *top, *oldtop;
|
||||
byte *stk;
|
||||
uintptr sp;
|
||||
uintptr *src, *dst, *dstend;
|
||||
@ -316,6 +317,16 @@ runtime·newstack(void)
|
||||
// copy flag from panic
|
||||
top->panic = gp->ispanic;
|
||||
gp->ispanic = false;
|
||||
|
||||
// if this isn't a panic, maybe we're splitting the stack for a panic.
|
||||
// if we're splitting in the top frame, propagate the panic flag
|
||||
// forward so that recover will know we're in a panic.
|
||||
oldtop = (Stktop*)top->stackbase;
|
||||
if(oldtop != nil && oldtop->panic && top->argp == (byte*)oldtop - oldtop->argsize - gp->panicwrap)
|
||||
top->panic = true;
|
||||
|
||||
top->panicwrap = gp->panicwrap;
|
||||
gp->panicwrap = 0;
|
||||
|
||||
gp->stackbase = (uintptr)top;
|
||||
gp->stackguard = (uintptr)stk + StackGuard;
|
||||
|
216
test/recover.go
216
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user