Bug 1730426 - Start explicitly throwing uncatchable exceptions. r=jandem

Add Interrupt status to JS::ExceptionStatus and set it when intentionally
throwing an uncatchable exception. This is only a guideline for now, so avoid
excessive asserts for now.

Differential Revision: https://phabricator.services.mozilla.com/D125363
This commit is contained in:
Ted Campbell 2021-09-13 19:21:26 +00:00
Родитель 4d190c6349
Коммит 811579616a
11 изменённых файлов: 44 добавлений и 6 удалений

Просмотреть файл

@ -39,6 +39,12 @@ extern JS_PUBLIC_API void JS_SetPendingException(
JSContext* cx, JS::HandleValue v, JSContext* cx, JS::HandleValue v,
JS::ExceptionStackBehavior behavior = JS::ExceptionStackBehavior::Capture); JS::ExceptionStackBehavior behavior = JS::ExceptionStackBehavior::Capture);
// Indicate that we are intentionally raising an interrupt/uncatchable
// exception. Returning false/nullptr without calling this will still be treated
// as an interrupt, but in the future the implicit behaviour may no longer be
// allowed.
extern JS_PUBLIC_API void JS_SetPendingInterrupt(JSContext* cx);
extern JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx); extern JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx);
/** /**
@ -56,7 +62,9 @@ namespace JS {
// When propagating an exception up the call stack, we store the underlying // When propagating an exception up the call stack, we store the underlying
// reason on the JSContext as one of the following enum values. // reason on the JSContext as one of the following enum values.
// //
// TODO: Track uncatchable exceptions explicitly. // NOTE: It is not yet a hard requirement for uncatchable exceptions to set
// status to Interrupt so rely on the last return value. If Interrupt is
// not set then the status will remain as None.
enum class ExceptionStatus { enum class ExceptionStatus {
// No expection status. // No expection status.
None, None,
@ -66,6 +74,12 @@ enum class ExceptionStatus {
// non-error completion. // non-error completion.
ForcedReturn, ForcedReturn,
// An uncatchable exception that functions as an interrupt. This is used for
// watchdog mechanisms (like the slow-script notification) or the debugger
// API. It cannot be caught be JS catch blocks, but the debugger or event
// loops may still capture it.
Interrupt,
// Throwing a (catchable) exception. Certain well-known exceptions are // Throwing a (catchable) exception. Certain well-known exceptions are
// explicitly tracked for convenience. // explicitly tracked for convenience.
Throwing, Throwing,

Просмотреть файл

@ -3680,7 +3680,7 @@ static bool Terminate(JSContext* cx, unsigned arg, Value* vp) {
fprintf(stderr, "terminate called\n"); fprintf(stderr, "terminate called\n");
} }
JS_ClearPendingException(cx); cx->setInterrupting();
return false; return false;
} }

Просмотреть файл

@ -300,7 +300,7 @@ static void PropagateForcedReturn(JSContext* cx, AbstractFramePtr frame,
return false; return false;
case ResumeMode::Terminate: case ResumeMode::Terminate:
cx->clearPendingException(); cx->setInterrupting();
return false; return false;
case ResumeMode::Return: case ResumeMode::Return:
@ -1018,7 +1018,7 @@ NativeResumeMode DebugAPI::slowPathOnNativeCall(JSContext* cx,
return NativeResumeMode::Abort; return NativeResumeMode::Abort;
case ResumeMode::Terminate: case ResumeMode::Terminate:
cx->clearPendingException(); cx->setInterrupting();
return NativeResumeMode::Abort; return NativeResumeMode::Abort;
case ResumeMode::Return: case ResumeMode::Return:
@ -1951,6 +1951,7 @@ Completion Completion::fromJSResult(JSContext* cx, bool ok, const Value& rv) {
} }
if (!cx->isExceptionPending()) { if (!cx->isExceptionPending()) {
cx->clearInterrupt();
return Completion(Terminate()); return Completion(Terminate());
} }

Просмотреть файл

@ -524,7 +524,7 @@ again:
} }
// We may be propagating a forced return from a debugger hook function. // We may be propagating a forced return from a debugger hook function.
if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); cx->clearPropagatingForcedReturn();
frameOk = true; frameOk = true;
} }

Просмотреть файл

@ -3685,6 +3685,10 @@ JS_PUBLIC_API void JS_SetPendingException(JSContext* cx, HandleValue value,
} }
} }
JS_PUBLIC_API void JS_SetPendingInterrupt(JSContext* cx) {
cx->setInterrupting();
}
JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx) { JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx) {
AssertHeapIsIdle(); AssertHeapIsIdle();
cx->clearPendingException(); cx->clearPendingException();

Просмотреть файл

@ -2617,6 +2617,7 @@ static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
? JS_ExecuteScript(cx, script, args.rval()) ? JS_ExecuteScript(cx, script, args.rval())
: JS_ExecuteScript(cx, envChain, script, args.rval()))) { : JS_ExecuteScript(cx, envChain, script, args.rval()))) {
if (catchTermination && !JS_IsExceptionPending(cx)) { if (catchTermination && !JS_IsExceptionPending(cx)) {
cx->clearInterrupt();
JSAutoRealm ar1(cx, callerGlobal); JSAutoRealm ar1(cx, callerGlobal);
JSString* str = JS_NewStringCopyZ(cx, "terminated"); JSString* str = JS_NewStringCopyZ(cx, "terminated");
if (!str) { if (!str) {
@ -3147,6 +3148,8 @@ static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
js::StopDrainingJobQueue(cx); js::StopDrainingJobQueue(cx);
sc->exitCode = code; sc->exitCode = code;
sc->quitting = true; sc->quitting = true;
cx->setInterrupting();
return false; return false;
} }

Просмотреть файл

@ -1220,7 +1220,7 @@ again:
UnwindIteratorsForUncatchableException(cx, regs); UnwindIteratorsForUncatchableException(cx, regs);
// We may be propagating a forced return from a debugger hook function. // We may be propagating a forced return from a debugger hook function.
if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) { if (cx->isPropagatingForcedReturn()) {
cx->clearPropagatingForcedReturn(); cx->clearPropagatingForcedReturn();
ok = true; ok = true;
} }

Просмотреть файл

@ -837,6 +837,7 @@ void InternalJobQueue::runJobs(JSContext* cx) {
if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) { if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) {
// Nothing we can do about uncatchable exceptions. // Nothing we can do about uncatchable exceptions.
if (!cx->isExceptionPending()) { if (!cx->isExceptionPending()) {
cx->clearInterrupt();
continue; continue;
} }
RootedValue exn(cx); RootedValue exn(cx);

Просмотреть файл

@ -799,6 +799,18 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext,
return JS::IsCatchableExceptionStatus(status); return JS::IsCatchableExceptionStatus(status);
} }
void setInterrupting() {
MOZ_ASSERT(status == JS::ExceptionStatus::None);
status = JS::ExceptionStatus::Interrupt;
}
void clearInterrupt() {
// NOTE: Calling `setInterrupting` is still optional, but certainly there
// should not be any catchable exception pending.
MOZ_ASSERT(status == JS::ExceptionStatus::None ||
status == JS::ExceptionStatus::Interrupt);
status = JS::ExceptionStatus::None;
}
[[nodiscard]] bool getPendingException(JS::MutableHandleValue rval); [[nodiscard]] bool getPendingException(JS::MutableHandleValue rval);
js::SavedFrame* getPendingExceptionStack(); js::SavedFrame* getPendingExceptionStack();

Просмотреть файл

@ -482,6 +482,8 @@ static bool HandleInterrupt(JSContext* cx, bool invokeCallback) {
chars = u"(stack not available)"; chars = u"(stack not available)";
} }
WarnNumberUC(cx, JSMSG_TERMINATED, chars); WarnNumberUC(cx, JSMSG_TERMINATED, chars);
cx->setInterrupting();
return false; return false;
} }

Просмотреть файл

@ -423,6 +423,7 @@ static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
gQuitting = true; gQuitting = true;
// exit(0); // exit(0);
JS_SetPendingInterrupt(cx);
return false; return false;
} }