diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 5229948e01..580d6bfba3 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -224,16 +224,23 @@ ProcessNameForCollectorLog() namespace xpc { -// This handles JS Exceptions (via ExceptionStackOrNull), as well as DOM and XPC -// Exceptions. +// This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC +// Exceptions, and arbitrary values that were associated with a stack by the +// JS engine when they were thrown, as specified by exceptionStack. // // Note that the returned object is _not_ wrapped into the compartment of // exceptionValue. JSObject* FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, - JS::HandleValue exceptionValue) + JS::HandleValue exceptionValue, + JS::HandleObject exceptionStack) { if (!exceptionValue.isObject()) { + // Use the stack provided by the JS engine, if available. This will not be + // a wrapper. + if (exceptionStack) { + return exceptionStack; + } return nullptr; } @@ -257,6 +264,10 @@ FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, // Not a DOM Exception, try XPC Exception. UNWRAP_OBJECT(Exception, exceptionObject, exception); if (!exception) { + // As above, use the stack provided by the JS engine, if available. + if (exceptionStack) { + return exceptionStack; + } return nullptr; } } @@ -421,10 +432,12 @@ public: ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx, xpc::ErrorReport* aReport, - JS::Handle aError) + JS::Handle aError, + JS::Handle aErrorStack) : mWindow(aWindow) , mReport(aReport) , mError(aRootingCx, aError) + , mErrorStack(aRootingCx, aErrorStack) {} NS_IMETHOD Run() override @@ -471,7 +484,7 @@ public: if (status != nsEventStatus_eConsumeNoDefault) { JS::Rooted stack(rootingCx, - xpc::FindExceptionStackForConsoleReport(win, mError)); + xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack)); mReport->LogToConsoleWithStack(stack); } @@ -481,7 +494,8 @@ public: private: nsCOMPtr mWindow; RefPtr mReport; - JS::PersistentRootedValue mError; + JS::PersistentRootedValue mError; + JS::PersistentRootedObject mErrorStack; static bool sHandlingScriptError; }; @@ -494,9 +508,10 @@ namespace xpc { void DispatchScriptErrorEvent(nsPIDOMWindowInner *win, JS::RootingContext* rootingCx, - xpc::ErrorReport *xpcReport, JS::Handle exception) + xpc::ErrorReport *xpcReport, JS::Handle exception, + JS::Handle exceptionStack) { - nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception)); + nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception, exceptionStack)); } } /* namespace xpc */ diff --git a/dom/script/ScriptSettings.cpp b/dom/script/ScriptSettings.cpp index 514b5cf858..790394de65 100644 --- a/dom/script/ScriptSettings.cpp +++ b/dom/script/ScriptSettings.cpp @@ -577,8 +577,9 @@ AutoJSAPI::ReportException() } JSAutoCompartment ac(cx(), errorGlobal); JS::Rooted exn(cx()); + JS::Rooted exnStack(cx()); js::ErrorReport jsReport(cx()); - if (StealException(&exn) && + if (StealExceptionAndStack(&exn, &exnStack) && jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) { if (mIsMainThread) { RefPtr xpcReport = new xpc::ErrorReport(); @@ -595,10 +596,10 @@ AutoJSAPI::ReportException() inner ? inner->WindowID() : 0); if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) { JS::RootingContext* rcx = JS::RootingContext::get(cx()); - DispatchScriptErrorEvent(inner, rcx, xpcReport, exn); + DispatchScriptErrorEvent(inner, rcx, xpcReport, exn, exnStack); } else { JS::Rooted stack(cx(), - xpc::FindExceptionStackForConsoleReport(inner, exn)); + xpc::FindExceptionStackForConsoleReport(inner, exn, exnStack)); xpcReport->LogToConsoleWithStack(stack); } } else { @@ -638,9 +639,16 @@ AutoJSAPI::PeekException(JS::MutableHandle aVal) bool AutoJSAPI::StealException(JS::MutableHandle aVal) { + JS::Rooted stack(cx()); + return StealExceptionAndStack(aVal, &stack); +} + +bool AutoJSAPI::StealExceptionAndStack(JS::MutableHandle aVal, + JS::MutableHandle aStack) { if (!PeekException(aVal)) { return false; } + aStack.set(JS::GetPendingExceptionStack(cx())); JS_ClearPendingException(cx()); return true; } diff --git a/dom/script/ScriptSettings.h b/dom/script/ScriptSettings.h index f6cfb6c3e4..f2e12f0be9 100644 --- a/dom/script/ScriptSettings.h +++ b/dom/script/ScriptSettings.h @@ -274,6 +274,12 @@ public: // into the current compartment. MOZ_MUST_USE bool StealException(JS::MutableHandle aVal); + // As for StealException(), but put the saved frames for any stack trace + // associated with the point the exception was thrown into aStack. + // aVal will be in the current compartment, but aStack might not be. + MOZ_MUST_USE bool StealExceptionAndStack(JS::MutableHandle aVal, + JS::MutableHandle aStack); + // Peek the current exception from the JS engine, without stealing it. // Callers must ensure that HasException() is true, and that cx() is in a // non-null compartment. diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index fbe6977bf9..01a22482eb 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -941,7 +941,7 @@ HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, bool* mus return jit::DebugEpilogue(cx, frame, pc, true); case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); return false; default: diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 7d32948a27..0d23c96cb4 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6219,12 +6219,16 @@ JS_GetPendingException(JSContext* cx, MutableHandleValue vp) } JS_PUBLIC_API(void) -JS_SetPendingException(JSContext* cx, HandleValue value) +JS_SetPendingException(JSContext* cx, HandleValue value, JS::ExceptionStackBehavior behavior) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); releaseAssertSameCompartment(cx, value); - cx->setPendingException(value); + if (behavior == JS::ExceptionStackBehavior::Capture) { + cx->setPendingExceptionAndCaptureStack(value); + } else { + cx->setPendingException(value, nullptr); + } } JS_PUBLIC_API(void) @@ -6234,12 +6238,20 @@ JS_ClearPendingException(JSContext* cx) cx->clearPendingException(); } +JS_PUBLIC_API(JSObject*) +JS::GetPendingExceptionStack(JSContext* cx) +{ + AssertHeapIsIdle(cx); + return cx->getPendingExceptionStack(); +} + JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext* cx) : context(cx), wasPropagatingForcedReturn(cx->propagatingForcedReturn_), wasOverRecursed(cx->overRecursed_), wasThrowing(cx->throwing), - exceptionValue(cx) + exceptionValue(cx), + exceptionStack(cx) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); @@ -6249,10 +6261,21 @@ JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext* cx) cx->overRecursed_ = false; if (wasThrowing) { exceptionValue = cx->unwrappedException_; + exceptionStack = cx->unwrappedExceptionStack_; cx->clearPendingException(); } } +void +JS::AutoSaveExceptionState::drop() +{ + wasPropagatingForcedReturn = false; + wasOverRecursed = false; + wasThrowing = false; + exceptionValue.setUndefined(); + exceptionStack = nullptr; +} + void JS::AutoSaveExceptionState::restore() { @@ -6260,6 +6283,9 @@ JS::AutoSaveExceptionState::restore() context->overRecursed_ = wasOverRecursed; context->throwing = wasThrowing; context->unwrappedException_ = exceptionValue; + if (exceptionStack) { + context->unwrappedExceptionStack_ = &exceptionStack->as(); + } drop(); } @@ -6272,6 +6298,9 @@ JS::AutoSaveExceptionState::~AutoSaveExceptionState() context->overRecursed_ = wasOverRecursed; context->throwing = true; context->unwrappedException_ = exceptionValue; + if (exceptionStack) { + context->unwrappedExceptionStack_ = &exceptionStack->as(); + } } } } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 938fcb2a33..6002d86ad5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5762,8 +5762,22 @@ JS_IsExceptionPending(JSContext* cx); extern JS_PUBLIC_API(bool) JS_GetPendingException(JSContext* cx, JS::MutableHandleValue vp); +namespace JS { + +enum class ExceptionStackBehavior: bool { + // Do not capture any stack. + DoNotCapture, + + // Capture the current JS stack when setting the exception. It may be + // retrieved by JS::GetPendingExceptionStack. + Capture +}; + +} // namespace JS + extern JS_PUBLIC_API(void) -JS_SetPendingException(JSContext* cx, JS::HandleValue v); +JS_SetPendingException(JSContext* cx, JS::HandleValue v, + JS::ExceptionStackBehavior behavior = JS::ExceptionStackBehavior::Capture); extern JS_PUBLIC_API(void) JS_ClearPendingException(JSContext* cx); @@ -5790,6 +5804,7 @@ class JS_PUBLIC_API(AutoSaveExceptionState) bool wasOverRecursed; bool wasThrowing; RootedValue exceptionValue; + RootedObject exceptionStack; public: /* @@ -5808,12 +5823,7 @@ class JS_PUBLIC_API(AutoSaveExceptionState) * Discard any stored exception state. * If this is called, the destructor is a no-op. */ - void drop() { - wasPropagatingForcedReturn = false; - wasOverRecursed = false; - wasThrowing = false; - exceptionValue.setUndefined(); - } + void drop(); /* * Replace cx's exception state with the stored exception state. Then @@ -5823,6 +5833,18 @@ class JS_PUBLIC_API(AutoSaveExceptionState) void restore(); }; +/** + * Get the SavedFrame stack object captured when the pending exception was set + * on the JSContext. This fuzzily correlates with a `throw` statement in JS, + * although arbitrary JSAPI consumers or VM code may also set pending exceptions + * via `JS_SetPendingException`. + * + * This is not the same stack as `e.stack` when `e` is an `Error` object. (That + * would be JS::ExceptionStackOrNull). + */ +MOZ_MUST_USE JS_PUBLIC_API(JSObject*) +GetPendingExceptionStack(JSContext* cx); + } /* namespace JS */ /* Deprecated API. Use AutoSaveExceptionState instead. */ diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index baab36183a..c780b7feba 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -241,7 +241,8 @@ js::ReportOutOfMemory(ExclusiveContext* cxArg) if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback) oomCallback(cx, cx->runtime()->oomCallbackData); - cx->setPendingException(StringValue(cx->names().outOfMemory)); + RootedValue oomMessage(cx, StringValue(cx->names().outOfMemory)); + cx->setPendingException(oomMessage, nullptr); } void @@ -1013,6 +1014,7 @@ JSContext::JSContext(JSRuntime* parentRuntime) JSRuntime(parentRuntime), throwing(false), unwrappedException_(this), + unwrappedExceptionStack_(this), overRecursed_(false), propagatingForcedReturn_(false), liveVolatileJitFrameIterators_(nullptr), @@ -1038,6 +1040,24 @@ JSContext::~JSContext() MOZ_ASSERT(!resolvingList); } + +void +JSContext::setPendingExceptionAndCaptureStack(HandleValue value) +{ + static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; + + RootedObject stack(this); + if (!CaptureCurrentStack(this, &stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH)))) { + clearPendingException(); + } + + RootedSavedFrame nstack(this); + if (stack) { + nstack = &stack->as(); + } + setPendingException(value, nstack); +} + bool JSContext::getPendingException(MutableHandleValue rval) { @@ -1045,16 +1065,23 @@ JSContext::getPendingException(MutableHandleValue rval) rval.set(unwrappedException_); if (IsAtomsCompartment(compartment())) return true; + RootedSavedFrame stack(this, unwrappedExceptionStack_); bool wasOverRecursed = overRecursed_; clearPendingException(); if (!compartment()->wrap(this, rval)) return false; assertSameCompartment(this, rval); - setPendingException(rval); + setPendingException(rval, stack); overRecursed_ = wasOverRecursed; return true; } +SavedFrame* +JSContext::getPendingExceptionStack() +{ + return unwrappedExceptionStack_; +} + bool JSContext::isThrowingOutOfMemory() { diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 935e6c59e5..93106d681e 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -369,6 +369,7 @@ struct JSContext : public js::ExclusiveContext, /* Exception state -- the exception member is a GC root by definition. */ bool throwing; /* is there a pending exception? */ JS::PersistentRooted unwrappedException_; /* most-recently-thrown exception */ + JS::PersistentRooted unwrappedExceptionStack_; /* stack when the exception was thrown */ // True if the exception currently being thrown is by result of // ReportOverRecursed. See Debugger::slowPathOnExceptionUnwind. @@ -495,17 +496,21 @@ struct JSContext : public js::ExclusiveContext, MOZ_MUST_USE bool getPendingException(JS::MutableHandleValue rval); + + js::SavedFrame* getPendingExceptionStack(); bool isThrowingOutOfMemory(); bool isThrowingDebuggeeWouldRun(); bool isClosingGenerator(); - void setPendingException(const js::Value& v); + void setPendingException(JS::HandleValue v, js::HandleSavedFrame stack); + void setPendingExceptionAndCaptureStack(JS::HandleValue v); void clearPendingException() { throwing = false; overRecursed_ = false; unwrappedException_.setUndefined(); + unwrappedExceptionStack_ = nullptr; } bool isThrowingOverRecursed() const { return throwing && overRecursed_; } diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index e08541e8da..c89ef86ec2 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -368,12 +368,13 @@ ExclusiveContext::typeLifoAlloc() } /* namespace js */ inline void -JSContext::setPendingException(const js::Value& v) +JSContext::setPendingException(JS::HandleValue v, js::HandleSavedFrame stack) { // overRecursed_ is set after the fact by ReportOverRecursed. this->overRecursed_ = false; this->throwing = true; this->unwrappedException_ = v; + this->unwrappedExceptionStack_ = stack; // We don't use assertSameCompartment here to allow // js::SetPendingExceptionCrossContext to work. MOZ_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment()); diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 7f87c312d3..2eb8e7d105 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -361,13 +361,13 @@ struct SuppressErrorsGuard } }; -// Cut off the stack if it gets too deep (most commonly for infinite recursion -// errors). -static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; - static bool CaptureStack(JSContext* cx, MutableHandleObject stack) { + // Cut off the stack if it gets too deep (most commonly for infinite recursion + // errors). + static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7; + return CaptureCurrentStack(cx, stack, JS::StackCapture(JS::MaxFrames(MAX_REPORTED_STACK_DEPTH))); } @@ -699,7 +699,12 @@ js::ErrorToException(JSContext* cx, JSErrorReport* reportp, return; // Throw it. - cx->setPendingException(ObjectValue(*errObject)); + RootedValue errValue(cx, ObjectValue(*errObject)); + RootedSavedFrame nstack(cx); + if (stack) { + nstack = &stack->as(); + } + cx->setPendingException(errValue, nstack); // Flag the error report passed in to indicate an exception was raised. reportp->flags |= JSREPORT_EXCEPTION; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 96285c1665..858e10a326 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -971,8 +971,10 @@ js::ThrowStopIteration(JSContext* cx) // StopIteration isn't a constructor, but it's stored in GlobalObject // as one, out of laziness. Hence the GetBuiltinConstructor call here. RootedObject ctor(cx); - if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) - cx->setPendingException(ObjectValue(*ctor)); + if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) { + RootedValue ctorval(cx, ObjectValue(*ctor)); + cx->setPendingExceptionAndCaptureStack(ctorval); + } return false; } @@ -1261,12 +1263,13 @@ js::UnwindIteratorForException(JSContext* cx, HandleObject obj) { RootedValue v(cx); bool getOk = cx->getPendingException(&v); + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); cx->clearPendingException(); if (!CloseIterator(cx, obj)) return false; if (!getOk) return false; - cx->setPendingException(v); + cx->setPendingException(v, stack); return true; } diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp index 36cb1317f7..314409bba1 100644 --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -408,12 +408,15 @@ ErrorCopier::~ErrorCopier() { RootedValue exc(cx); if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is()) { + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); cx->clearPendingException(); ac.reset(); Rooted errObj(cx, &exc.toObject().as()); JSObject* copyobj = CopyErrorObject(cx, errObj); - if (copyobj) - cx->setPendingException(ObjectValue(*copyobj)); + if (copyobj) { + RootedValue rootedCopy(cx, ObjectValue(*copyobj)); + cx->setPendingException(rootedCopy, stack); + } } } } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index f665dcef9b..f844d1d482 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -848,7 +848,7 @@ Debugger::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; case JSTRAP_ERROR: @@ -960,7 +960,7 @@ Debugger::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode return true; case JSTRAP_THROW: - cx->setPendingException(value); + cx->setPendingExceptionAndCaptureStack(value); return false; case JSTRAP_ERROR: @@ -993,7 +993,7 @@ Debugger::slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; default: @@ -1028,7 +1028,7 @@ Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame) break; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); break; case JSTRAP_ERROR: @@ -1321,7 +1321,7 @@ public: bool operator()(JSContext* cx) override { - cx->setPendingException(exn_); + cx->setPendingExceptionAndCaptureStack(exn_); return false; } @@ -1753,6 +1753,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp) MOZ_ASSERT(hook->isCallable()); RootedValue exc(cx); + RootedSavedFrame stack(cx, cx->getPendingExceptionStack()); if (!cx->getPendingException(&exc)) return JSTRAP_ERROR; cx->clearPendingException(); @@ -1772,7 +1773,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp) bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv); JSTrapStatus st = processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp); if (st == JSTRAP_CONTINUE) - cx->setPendingException(exc); + cx->setPendingException(exc, stack); return st; } @@ -2005,13 +2006,7 @@ Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp) * onStep handlers mess with that (other than by returning a resumption * value). */ - RootedValue exception(cx, UndefinedValue()); - bool exceptionPending = cx->isExceptionPending(); - if (exceptionPending) { - if (!cx->getPendingException(&exception)) - return JSTRAP_ERROR; - cx->clearPendingException(); - } + JS::AutoSaveExceptionState savedExc(cx); /* * Build list of Debugger.Frame instances referring to this frame with @@ -2070,13 +2065,13 @@ Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp) bool ok = js::Call(cx, fval, frame, &rval); JSTrapStatus st = dbg->processHandlerResult(ac, ok, rval, iter.abstractFramePtr(), iter.pc(), vp); - if (st != JSTRAP_CONTINUE) + if (st != JSTRAP_CONTINUE) { + savedExc.drop(); return st; + } } vp.setUndefined(); - if (exceptionPending) - cx->setPendingException(exception); return JSTRAP_CONTINUE; } diff --git a/js/src/vm/ForOfIterator.cpp b/js/src/vm/ForOfIterator.cpp index d616792697..abf27d0aee 100644 --- a/js/src/vm/ForOfIterator.cpp +++ b/js/src/vm/ForOfIterator.cpp @@ -158,9 +158,12 @@ ForOfIterator::closeThrow() MOZ_ASSERT(iterator); RootedValue completionException(cx_); + RootedSavedFrame completionExceptionStack(cx_); if (cx_->isExceptionPending()) { - if (!GetAndClearException(cx_, &completionException)) + if (!GetAndClearExceptionAndStack(cx_, &completionException, &completionExceptionStack)) { completionException.setUndefined(); + completionExceptionStack = nullptr; + } } // Steps 1-2 (implicit) @@ -172,7 +175,7 @@ ForOfIterator::closeThrow() // Step 4. if (returnVal.isUndefined()) { - cx_->setPendingException(completionException); + cx_->setPendingException(completionException, completionExceptionStack); return; } @@ -195,7 +198,7 @@ ForOfIterator::closeThrow() } // Step 6. - cx_->setPendingException(completionException); + cx_->setPendingException(completionException, completionExceptionStack); // Steps 7-9 (skipped). return; diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index 9265a1b628..df2869c89c 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -131,7 +131,7 @@ js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, HandlesetPendingException(arg); + cx->setPendingExceptionAndCaptureStack(arg); genObj->setRunning(); } else { MOZ_ASSERT(resumeKind == GeneratorObject::CLOSE); @@ -143,7 +143,8 @@ js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, HandlesetPendingException(MagicValue(JS_GENERATOR_CLOSING)); + RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING)); + cx->setPendingException(closing, nullptr); genObj->setClosing(); } return false; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index cf58e2d608..b95e533b6e 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1883,7 +1883,7 @@ CASE(EnableInterruptsPseudoOpcode) goto error; goto successful_return_continuation; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); goto error; default:; } @@ -1905,7 +1905,7 @@ CASE(EnableInterruptsPseudoOpcode) goto error; goto successful_return_continuation; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); goto error; default: break; @@ -3825,7 +3825,8 @@ CASE(JSOP_RETSUB) * be necessary, but it seems clearer. And it points out a FIXME: * 350509, due to Igor Bukanov. */ - cx->setPendingException(rval); + ReservedRooted v(&rootValue0, rval); + cx->setPendingExceptionAndCaptureStack(v); goto error; } MOZ_ASSERT(rval.isInt32()); @@ -4331,7 +4332,7 @@ bool js::Throw(JSContext* cx, HandleValue v) { MOZ_ASSERT(!cx->isExceptionPending()); - cx->setPendingException(v); + cx->setPendingExceptionAndCaptureStack(v); return false; } @@ -4342,7 +4343,7 @@ js::ThrowingOperation(JSContext* cx, HandleValue v) // execution instead of calling the (JIT) exception handler. MOZ_ASSERT(!cx->isExceptionPending()); - cx->setPendingException(v); + cx->setPendingExceptionAndCaptureStack(v); return true; } @@ -4548,16 +4549,25 @@ js::ThrowMsgOperation(JSContext* cx, const unsigned errorNum) } bool -js::GetAndClearException(JSContext* cx, MutableHandleValue res) +js::GetAndClearExceptionAndStack(JSContext* cx, MutableHandleValue res, + MutableHandleSavedFrame stack) { if (!cx->getPendingException(res)) return false; + stack.set(cx->getPendingExceptionStack()); cx->clearPendingException(); // Allow interrupting deeply nested exception handling. return CheckForInterrupt(cx); } +bool +js::GetAndClearException(JSContext* cx, MutableHandleValue res) +{ + RootedSavedFrame stack(cx); + return GetAndClearExceptionAndStack(cx, res, &stack); +} + template bool js::DeletePropertyJit(JSContext* cx, HandleValue v, HandlePropertyName name, bool* bp) diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 6a908e115b..df9368e71c 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -489,6 +489,9 @@ ThrowMsgOperation(JSContext* cx, const unsigned errorNum); bool GetAndClearException(JSContext* cx, MutableHandleValue res); +bool +GetAndClearExceptionAndStack(JSContext* cx, MutableHandleValue res, MutableHandleSavedFrame stack); + bool DeleteNameOperation(JSContext* cx, HandlePropertyName name, HandleObject scopeObj, MutableHandleValue res); diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 251c8258cf..a12255c636 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -548,7 +548,7 @@ InvokeInterruptCallback(JSContext* cx) Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval); return false; case JSTRAP_THROW: - cx->setPendingException(rval); + cx->setPendingExceptionAndCaptureStack(rval); return false; default:; } diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index fb292ac941..0479bda59f 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -1729,7 +1729,8 @@ RejectWithPendingException(JSContext* cx, Handle promise) return false; RootedValue rejectionValue(cx); - if (!GetAndClearException(cx, &rejectionValue)) + RootedSavedFrame stack(cx); + if (!GetAndClearExceptionAndStack(cx, &rejectionValue, &stack)) return false; return PromiseObject::reject(cx, promise, rejectionValue); diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 594753d64f..70103dfd17 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2310,7 +2310,7 @@ nsXPCComponents_Utils::ReportError(HandleValue error, JSContext* cx) if (errorObj) { JS::RootedObject stackVal(cx, - FindExceptionStackForConsoleReport(win, error)); + FindExceptionStackForConsoleReport(win, error, nullptr)); if (stackVal) { scripterr = new nsScriptErrorWithStack(stackVal); } diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h index 56468f0edd..5351be979b 100644 --- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -590,11 +590,13 @@ class ErrorReport : public ErrorBase { void DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, - xpc::ErrorReport* xpcReport, JS::Handle exception); + xpc::ErrorReport* xpcReport, JS::Handle exception, + JS::Handle exceptionStack); // Get a stack of the sort that can be passed to // xpc::ErrorReport::LogToConsoleWithStack from the given exception value. Can -// return null if the exception value doesn't have an associated stack. The +// be nullptr if the exception value doesn't have an associated stack, and if +// there is no stack supplied by the JS engine in exceptionStack. The // returned stack, if any, may also not be in the same compartment as // exceptionValue. // @@ -605,7 +607,8 @@ DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, // the stack in the console message keeping the window alive. JSObject* FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, - JS::HandleValue exceptionValue); + JS::HandleValue exceptionValue, + JS::HandleObject exceptionStack); // Return a name for the compartment. // This function makes reasonable efforts to make this name both mostly human-readable