Bug 1134198 - Call Debugger::onPop at the point that caused the frame to pop before any unwinding in the JIT. (r=jandem)

This commit is contained in:
Shu-yu Guo 2015-04-02 17:28:02 -07:00
parent e2f60d26f0
commit 80f8445c7c
2 changed files with 197 additions and 174 deletions

View File

@ -370,7 +370,7 @@ JitFrameIterator::machineState() const
}
static void
CloseLiveIterator(JSContext* cx, const InlineFrameIterator& frame, uint32_t localSlot)
CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, uint32_t localSlot)
{
SnapshotIterator si = frame.snapshotIterator();
@ -390,6 +390,20 @@ CloseLiveIterator(JSContext* cx, const InlineFrameIterator& frame, uint32_t loca
UnwindIteratorForUncatchableException(cx, obj);
}
class IgnoreStackDepthOp
{
public:
uint32_t operator()() { return UINT32_MAX; }
};
class TryNoteIterIon : public TryNoteIter<IgnoreStackDepthOp>
{
public:
TryNoteIterIon(JSContext* cx, JSScript* script, jsbytecode* pc)
: TryNoteIter(cx, script, pc, IgnoreStackDepthOp())
{ }
};
static void
HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromException* rfe,
bool* overrecursed)
@ -442,15 +456,8 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
if (!script->hasTrynotes())
return;
JSTryNote* tn = script->trynotes()->vector;
JSTryNote* tnEnd = tn + script->trynotes()->length;
uint32_t pcOffset = uint32_t(pc - script->main());
for (; tn != tnEnd; ++tn) {
if (pcOffset < tn->start)
continue;
if (pcOffset >= tn->start + tn->length)
continue;
for (TryNoteIterIon tni(cx, script, pc); !tni.done(); ++tni) {
JSTryNote* tn = *tni;
switch (tn->kind) {
case JSTRY_FOR_IN: {
@ -458,7 +465,7 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
MOZ_ASSERT(tn->stackDepth > 0);
uint32_t localSlot = tn->stackDepth;
CloseLiveIterator(cx, frame, localSlot);
CloseLiveIteratorIon(cx, frame, localSlot);
break;
}
@ -492,50 +499,49 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
}
static void
ForcedReturn(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
ResumeFromException* rfe, bool* calledDebugEpilogue)
OnLeaveBaselineFrame(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
ResumeFromException* rfe, bool frameOk)
{
BaselineFrame* baselineFrame = frame.baselineFrame();
MOZ_ASSERT(baselineFrame->hasReturnValue());
if (jit::DebugEpilogue(cx, baselineFrame, pc, true)) {
if (jit::DebugEpilogue(cx, baselineFrame, pc, frameOk)) {
rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
rfe->stackPointer = reinterpret_cast<uint8_t*>(baselineFrame);
return;
}
}
// DebugEpilogue threw an exception. Propagate to the caller frame.
*calledDebugEpilogue = true;
static inline void
ForcedReturn(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
ResumeFromException* rfe)
{
OnLeaveBaselineFrame(cx, frame, pc, rfe, true);
}
static inline void
BaselineFrameAndStackPointersFromTryNote(JSTryNote* tn, const JitFrameIterator& frame,
uint8_t** framePointer, uint8_t** stackPointer)
{
JSScript* script = frame.baselineFrame()->script();
*framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
*stackPointer = *framePointer - BaselineFrame::Size() -
(script->nfixed() + tn->stackDepth) * sizeof(Value);
}
static void
HandleClosingGeneratorReturn(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
jsbytecode* unwoundScopeToPc, ResumeFromException* rfe,
bool* calledDebugEpilogue)
SettleOnTryNote(JSContext* cx, JSTryNote *tn, const JitFrameIterator& frame,
ScopeIter& si, ResumeFromException* rfe, jsbytecode** pc)
{
// If we're closing a legacy generator, we need to return to the caller
// after executing the |finally| blocks. This is very similar to a forced
// return from the debugger.
RootedScript script(cx, frame.baselineFrame()->script());
if (!cx->isExceptionPending())
return;
RootedValue exception(cx);
if (!cx->getPendingException(&exception))
return;
if (!exception.isMagic(JS_GENERATOR_CLOSING))
return;
// Unwind scope chain (pop block objects).
if (cx->isExceptionPending())
UnwindScope(cx, si, UnwindScopeToTryPc(script, tn));
cx->clearPendingException();
SetReturnValueForClosingGenerator(cx, frame.baselineFrame());
// Compute base pointer and stack pointer.
BaselineFrameAndStackPointersFromTryNote(tn, frame, &rfe->framePointer, &rfe->stackPointer);
if (unwoundScopeToPc) {
if (frame.baselineFrame()->isDebuggee())
frame.baselineFrame()->setOverridePc(unwoundScopeToPc);
pc = unwoundScopeToPc;
}
ForcedReturn(cx, frame, pc, rfe, calledDebugEpilogue);
// Compute the pc.
*pc = script->main() + tn->start + tn->length;
}
struct AutoBaselineHandlingException
@ -553,122 +559,101 @@ struct AutoBaselineHandlingException
}
};
static void
HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFromException* rfe,
jsbytecode* pc, jsbytecode** unwoundScopeToPc, bool* calledDebugEpilogue)
class BaselineFrameStackDepthOp
{
MOZ_ASSERT(frame.isBaselineJS());
MOZ_ASSERT(!*calledDebugEpilogue);
// We may be propagating a forced return from the interrupt
// callback, which cannot easily force a return.
if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn();
ForcedReturn(cx, frame, pc, rfe, calledDebugEpilogue);
return;
BaselineFrame* frame_;
public:
explicit BaselineFrameStackDepthOp(BaselineFrame* frame)
: frame_(frame)
{ }
uint32_t operator()() {
MOZ_ASSERT(frame_->numValueSlots() >= frame_->script()->nfixed());
return frame_->numValueSlots() - frame_->script()->nfixed();
}
};
RootedValue exception(cx);
if (cx->isExceptionPending() && cx->compartment()->isDebuggee() &&
!cx->isClosingGenerator())
{
switch (Debugger::onExceptionUnwind(cx, frame.baselineFrame())) {
case JSTRAP_ERROR:
// Uncatchable exception.
MOZ_ASSERT(!cx->isExceptionPending());
break;
class TryNoteIterBaseline : public TryNoteIter<BaselineFrameStackDepthOp>
{
public:
TryNoteIterBaseline(JSContext* cx, BaselineFrame* frame, jsbytecode* pc)
: TryNoteIter(cx, frame->script(), pc, BaselineFrameStackDepthOp(frame))
{ }
};
case JSTRAP_CONTINUE:
case JSTRAP_THROW:
MOZ_ASSERT(cx->isExceptionPending());
break;
// Close all live iterators on a BaselineFrame due to exception unwinding. The
// pc parameter is updated to where the scopes have been unwound to.
static void
CloseLiveIteratorsBaselineForUncatchableException(JSContext* cx, const JitFrameIterator& frame,
jsbytecode* pc)
{
for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), pc); !tni.done(); ++tni) {
JSTryNote* tn = *tni;
case JSTRAP_RETURN:
ForcedReturn(cx, frame, pc, rfe, calledDebugEpilogue);
return;
default:
MOZ_CRASH("Invalid trap status");
if (tn->kind == JSTRY_FOR_IN) {
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
Value iterValue(*(Value*) stackPointer);
RootedObject iterObject(cx, &iterValue.toObject());
UnwindIteratorForUncatchableException(cx, iterObject);
}
}
}
static bool
ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, ScopeIter& si,
ResumeFromException* rfe, jsbytecode** pc)
{
RootedScript script(cx, frame.baselineFrame()->script());
if (!script->hasTrynotes()) {
HandleClosingGeneratorReturn(cx, frame, pc, *unwoundScopeToPc, rfe, calledDebugEpilogue);
return;
}
JSTryNote* tn = script->trynotes()->vector;
JSTryNote* tnEnd = tn + script->trynotes()->length;
uint32_t pcOffset = uint32_t(pc - script->main());
ScopeIter si(cx, frame.baselineFrame(), pc);
for (; tn != tnEnd; ++tn) {
if (pcOffset < tn->start)
continue;
if (pcOffset >= tn->start + tn->length)
continue;
// Skip if the try note's stack depth exceeds the frame's stack depth.
// See the big comment in TryNoteIter::settle for more info.
MOZ_ASSERT(frame.baselineFrame()->numValueSlots() >= script->nfixed());
size_t stackDepth = frame.baselineFrame()->numValueSlots() - script->nfixed();
if (tn->stackDepth > stackDepth)
continue;
// Unwind scope chain (pop block objects).
if (cx->isExceptionPending()) {
*unwoundScopeToPc = UnwindScopeToTryPc(script, tn);
UnwindScope(cx, si, *unwoundScopeToPc);
}
// Compute base pointer and stack pointer.
rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
rfe->stackPointer = rfe->framePointer - BaselineFrame::Size() -
(script->nfixed() + tn->stackDepth) * sizeof(Value);
for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) {
JSTryNote* tn = *tni;
MOZ_ASSERT(cx->isExceptionPending());
switch (tn->kind) {
case JSTRY_CATCH:
if (cx->isExceptionPending()) {
// If we're closing a legacy generator, we have to skip catch
// blocks.
if (cx->isClosingGenerator())
continue;
case JSTRY_CATCH: {
// If we're closing a legacy generator, we have to skip catch
// blocks.
if (cx->isClosingGenerator())
continue;
// Ion can compile try-catch, but bailing out to catch
// exceptions is slow. Reset the warm-up counter so that if we
// catch many exceptions we won't Ion-compile the script.
script->resetWarmUpCounter();
SettleOnTryNote(cx, tn, frame, si, rfe, pc);
// Resume at the start of the catch block.
rfe->kind = ResumeFromException::RESUME_CATCH;
jsbytecode* catchPC = script->main() + tn->start + tn->length;
rfe->target = script->baselineScript()->nativeCodeForPC(script, catchPC);
return;
}
break;
// Ion can compile try-catch, but bailing out to catch
// exceptions is slow. Reset the warm-up counter so that if we
// catch many exceptions we won't Ion-compile the script.
script->resetWarmUpCounter();
case JSTRY_FINALLY:
if (cx->isExceptionPending()) {
rfe->kind = ResumeFromException::RESUME_FINALLY;
jsbytecode* finallyPC = script->main() + tn->start + tn->length;
rfe->target = script->baselineScript()->nativeCodeForPC(script, finallyPC);
// Drop the exception instead of leaking cross compartment data.
if (!cx->getPendingException(MutableHandleValue::fromMarkedLocation(&rfe->exception)))
rfe->exception = UndefinedValue();
cx->clearPendingException();
return;
}
break;
// Resume at the start of the catch block.
rfe->kind = ResumeFromException::RESUME_CATCH;
rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc);
return true;
}
case JSTRY_FINALLY: {
SettleOnTryNote(cx, tn, frame, si, rfe, pc);
rfe->kind = ResumeFromException::RESUME_FINALLY;
rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc);
// Drop the exception instead of leaking cross compartment data.
if (!cx->getPendingException(MutableHandleValue::fromMarkedLocation(&rfe->exception)))
rfe->exception = UndefinedValue();
cx->clearPendingException();
return true;
}
case JSTRY_FOR_IN: {
Value iterValue(* (Value*) rfe->stackPointer);
uint8_t* framePointer;
uint8_t* stackPointer;
BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
Value iterValue(*(Value*) stackPointer);
RootedObject iterObject(cx, &iterValue.toObject());
if (cx->isExceptionPending())
UnwindIteratorForException(cx, iterObject);
else
UnwindIteratorForUncatchableException(cx, iterObject);
if (!UnwindIteratorForException(cx, iterObject)) {
// See comment in the JSTRY_FOR_IN case in Interpreter.cpp's
// ProcessTryNotes.
SettleOnTryNote(cx, tn, frame, si, rfe, pc);
MOZ_ASSERT(**pc == JSOP_ENDITER);
return false;
}
break;
}
@ -680,8 +665,66 @@ HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFrom
MOZ_CRASH("Invalid try note");
}
}
return true;
}
HandleClosingGeneratorReturn(cx, frame, pc, *unwoundScopeToPc, rfe, calledDebugEpilogue);
static void
HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFromException* rfe,
jsbytecode* pc)
{
MOZ_ASSERT(frame.isBaselineJS());
// We may be propagating a forced return from the interrupt
// callback, which cannot easily force a return.
if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn();
ForcedReturn(cx, frame, pc, rfe);
return;
}
bool frameOk = false;
RootedScript script(cx, frame.baselineFrame()->script());
again:
if (cx->isExceptionPending()) {
if (!cx->isClosingGenerator()) {
switch (Debugger::onExceptionUnwind(cx, frame.baselineFrame())) {
case JSTRAP_ERROR:
// Uncatchable exception.
MOZ_ASSERT(!cx->isExceptionPending());
goto again;
case JSTRAP_CONTINUE:
case JSTRAP_THROW:
MOZ_ASSERT(cx->isExceptionPending());
break;
case JSTRAP_RETURN:
if (script->hasTrynotes())
CloseLiveIteratorsBaselineForUncatchableException(cx, frame, pc);
ForcedReturn(cx, frame, pc, rfe);
return;
default:
MOZ_CRASH("Invalid trap status");
}
}
if (script->hasTrynotes()) {
ScopeIter si(cx, frame.baselineFrame(), pc);
if (!ProcessTryNotesBaseline(cx, frame, si, rfe, &pc))
goto again;
if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME)
return;
}
frameOk = HandleClosingGeneratorReturn(cx, frame.baselineFrame(), frameOk);
frameOk = Debugger::onLeaveFrame(cx, frame.baselineFrame(), frameOk);
} else if (script->hasTrynotes()) {
CloseLiveIteratorsBaselineForUncatchableException(cx, frame, pc);
}
OnLeaveBaselineFrame(cx, frame, pc, rfe, frameOk);
}
struct AutoDeleteDebugModeOSRInfo
@ -806,12 +849,6 @@ HandleException(ResumeFromException* rfe)
ionScript->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
} else if (iter.isBaselineJS()) {
// It's invalid to call DebugEpilogue twice for the same frame.
bool calledDebugEpilogue = false;
// Remember the pc we unwound the scope to.
jsbytecode* unwoundScopeToPc = nullptr;
// Set a flag on the frame to signal to DebugModeOSR that we're
// handling an exception. Also ensure the frame has an override
// pc. We clear the frame's override pc when we leave this block,
@ -828,7 +865,7 @@ HandleException(ResumeFromException* rfe)
iter.baselineScriptAndPc(nullptr, &pc);
AutoBaselineHandlingException handlingException(iter.baselineFrame(), pc);
HandleExceptionBaseline(cx, iter, rfe, pc, &unwoundScopeToPc, &calledDebugEpilogue);
HandleExceptionBaseline(cx, iter, rfe, pc);
// If we are propagating an exception through a frame with
// on-stack recompile info, we should free the allocated
@ -836,8 +873,11 @@ HandleException(ResumeFromException* rfe)
// be returning to the recompile handler.
AutoDeleteDebugModeOSRInfo deleteDebugModeOSRInfo(iter.baselineFrame());
if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME)
if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME &&
rfe->kind != ResumeFromException::RESUME_FORCED_RETURN)
{
return;
}
TraceLogStopEvent(logger, TraceLogger_Baseline);
TraceLogStopEvent(logger, TraceLogger_Scripts);
@ -847,26 +887,8 @@ HandleException(ResumeFromException* rfe)
probes::ExitScript(cx, script, script->functionNonDelazifying(),
/* popSPSFrame = */ false);
if (iter.baselineFrame()->isDebuggee() && !calledDebugEpilogue) {
// If we still need to call the DebugEpilogue, we must
// remember the pc we unwound the scope chain to, as it will
// be out of sync with the frame's actual pc.
if (unwoundScopeToPc)
iter.baselineFrame()->setOverridePc(unwoundScopeToPc);
// If DebugEpilogue returns |true|, we have to perform a forced
// return, e.g. return frame->returnValue() to the caller.
BaselineFrame* frame = iter.baselineFrame();
jsbytecode* pc;
iter.baselineScriptAndPc(nullptr, &pc);
if (jit::DebugEpilogue(cx, frame, pc, false)) {
MOZ_ASSERT(frame->hasReturnValue());
rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
rfe->framePointer = iter.fp() - BaselineFrame::FramePointerOffset;
rfe->stackPointer = reinterpret_cast<uint8_t*>(frame);
return;
}
}
if (rfe->kind == ResumeFromException::RESUME_FORCED_RETURN)
return;
}
JitFrameLayout* current = iter.isScripted() ? iter.jsFrame() : nullptr;

View File

@ -709,17 +709,18 @@ DebugEpilogueOnBaselineReturn(JSContext* cx, BaselineFrame* frame, jsbytecode* p
bool
DebugEpilogue(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool ok)
{
// Unwind scope chain to stack depth 0.
ScopeIter si(cx, frame, pc);
UnwindAllScopesInFrame(cx, si);
jsbytecode* unwindPc = frame->script()->main();
frame->setOverridePc(unwindPc);
// If Debugger::onLeaveFrame returns |true| we have to return the frame's
// return value. If it returns |false|, the debugger threw an exception.
// In both cases we have to pop debug scopes.
ok = Debugger::onLeaveFrame(cx, frame, ok);
// Unwind to the outermost scope and set pc to the end of the script,
// regardless of error.
ScopeIter si(cx, frame, pc);
UnwindAllScopesInFrame(cx, si);
JSScript* script = frame->script();
frame->setOverridePc(script->lastPC());
if (frame->isNonEvalFunctionFrame()) {
MOZ_ASSERT_IF(ok, frame->hasReturnValue());
DebugScopes::onPopCall(frame, cx);