/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * JS execution context. */ #include "vm/JSContext-inl.h" #include "mozilla/CheckedInt.h" #include "mozilla/DebugOnly.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Sprintf.h" #include "mozilla/Utf8.h" // mozilla::ConvertUtf16ToUtf8 #include #ifdef ANDROID # include # include # include #endif // ANDROID #ifdef XP_WIN # include #endif // XP_WIN #include "jsapi.h" // JS_SetNativeStackQuota #include "jsexn.h" #include "jstypes.h" #include "frontend/FrontendContext.h" #include "gc/GC.h" #include "irregexp/RegExpAPI.h" #include "jit/Simulator.h" #include "js/CallAndConstruct.h" // JS::Call #include "js/CharacterEncoding.h" #include "js/ContextOptions.h" // JS::ContextOptions #include "js/ErrorInterceptor.h" // JSErrorInterceptor #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/StackLimits.h" // js::ReportOverRecursed #include "js/MemoryCallbacks.h" #include "js/Printf.h" #include "js/PropertyAndElement.h" // JS_GetProperty #include "js/Stack.h" // JS::NativeStackSize, JS::NativeStackLimit, JS::NativeStackLimitMin #include "util/DiagnosticAssertions.h" #include "util/DifferentialTesting.h" #include "util/DoubleToString.h" #include "util/NativeStack.h" #include "util/Text.h" #include "util/WindowsWrapper.h" #include "vm/BytecodeUtil.h" // JSDVG_IGNORE_STACK #include "vm/ErrorObject.h" #include "vm/ErrorReporting.h" #include "vm/JSFunction.h" #include "vm/JSObject.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/Realm.h" #include "vm/StringType.h" // StringToNewUTF8CharsZ #include "vm/ToSource.h" // js::ValueToSource #include "vm/WellKnownAtom.h" // js_*_str #include "vm/Compartment-inl.h" #include "vm/Stack-inl.h" using namespace js; #ifdef DEBUG JSContext* js::MaybeGetJSContext() { if (!TlsContext.init()) { return nullptr; } return TlsContext.get(); } #endif bool js::AutoCycleDetector::init() { MOZ_ASSERT(cyclic); AutoCycleDetector::Vector& vector = cx->cycleDetectorVector(); for (JSObject* obj2 : vector) { if (MOZ_UNLIKELY(obj == obj2)) { return true; } } if (!vector.append(obj)) { return false; } cyclic = false; return true; } js::AutoCycleDetector::~AutoCycleDetector() { if (MOZ_LIKELY(!cyclic)) { AutoCycleDetector::Vector& vec = cx->cycleDetectorVector(); MOZ_ASSERT(vec.back() == obj); if (vec.length() > 1) { vec.popBack(); } else { // Avoid holding on to unused heap allocations. vec.clearAndFree(); } } } bool JSContext::init(ContextKind kind) { // Skip most of the initialization if this thread will not be running JS. if (kind == ContextKind::MainThread) { TlsContext.set(this); currentThread_ = ThreadId::ThisThreadId(); nativeStackBase_.emplace(GetNativeStackBase()); if (!fx.initInstance()) { return false; } #ifdef JS_SIMULATOR simulator_ = jit::Simulator::Create(); if (!simulator_) { return false; } #endif } isolate = irregexp::CreateIsolate(this); if (!isolate) { return false; } // Set the ContextKind last, so that ProtectedData checks will allow us to // initialize this context before it becomes the runtime's active context. kind_ = kind; return true; } static void InitDefaultStackQuota(JSContext* cx) { // Initialize stack quota to a reasonable default. Embedders can override this // by calling JS_SetNativeStackQuota. // // NOTE: Firefox overrides these values. For the main thread this happens in // XPCJSContext::Initialize. #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) static constexpr JS::NativeStackSize MaxStackSize = 2 * 128 * sizeof(size_t) * 1024; #else static constexpr JS::NativeStackSize MaxStackSize = 128 * sizeof(size_t) * 1024; #endif JS_SetNativeStackQuota(cx, MaxStackSize); } JSContext* js::NewContext(uint32_t maxBytes, JSRuntime* parentRuntime) { AutoNoteSingleThreadedRegion anstr; MOZ_RELEASE_ASSERT(!TlsContext.get()); #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) js::oom::SetThreadType(!parentRuntime ? js::THREAD_TYPE_MAIN : js::THREAD_TYPE_WORKER); #endif JSRuntime* runtime = js_new(parentRuntime); if (!runtime) { return nullptr; } JSContext* cx = js_new(runtime, JS::ContextOptions()); if (!cx) { js_delete(runtime); return nullptr; } if (!cx->init(ContextKind::MainThread)) { js_delete(cx); js_delete(runtime); return nullptr; } if (!runtime->init(cx, maxBytes)) { runtime->destroyRuntime(); js_delete(cx); js_delete(runtime); return nullptr; } // Initialize stack quota last because simulators rely on the JSRuntime having // been initialized. if (cx->isMainThreadContext()) { InitDefaultStackQuota(cx); } return cx; } void js::DestroyContext(JSContext* cx) { JS_AbortIfWrongThread(cx); MOZ_ASSERT(!cx->realm(), "Shouldn't destroy context with active realm"); MOZ_ASSERT(!cx->activation(), "Shouldn't destroy context with activations"); cx->checkNoGCRooters(); // Cancel all off thread Ion compiles. Completed Ion compiles may try to // interrupt this context. See HelperThread::handleIonWorkload. CancelOffThreadIonCompile(cx->runtime()); cx->jobQueue = nullptr; cx->internalJobQueue = nullptr; SetContextProfilingStack(cx, nullptr); JSRuntime* rt = cx->runtime(); // Flush promise tasks executing in helper threads early, before any parts // of the JSRuntime that might be visible to helper threads are torn down. rt->offThreadPromiseState.ref().shutdown(cx); // Destroy the runtime along with its last context. js::AutoNoteSingleThreadedRegion nochecks; rt->destroyRuntime(); js_delete_poison(cx); js_delete_poison(rt); } void JS::RootingContext::checkNoGCRooters() { #ifdef DEBUG for (auto const& stackRootPtr : stackRoots_) { MOZ_ASSERT(stackRootPtr == nullptr); } #endif } bool AutoResolving::alreadyStartedSlow() const { MOZ_ASSERT(link); AutoResolving* cursor = link; do { MOZ_ASSERT(this != cursor); if (object.get() == cursor->object && id.get() == cursor->id && kind == cursor->kind) { return true; } } while (!!(cursor = cursor->link)); return false; } static void MaybeReportOutOfMemoryForDifferentialTesting() { /* * OOMs are non-deterministic, especially across different execution modes * (e.g. interpreter vs JIT). When doing differential testing, print to stderr * so that the fuzzers can detect this. */ if (js::SupportDifferentialTesting()) { fprintf(stderr, "ReportOutOfMemory called\n"); } } /* * Since memory has been exhausted, avoid the normal error-handling path which * allocates an error object, report and callstack. Instead simply throw the * static atom "out of memory". * * Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does * not occur, so GC must be avoided or suppressed. */ void JSContext::onOutOfMemory() { runtime()->hadOutOfMemory = true; gc::AutoSuppressGC suppressGC(this); /* Report the oom. */ if (JS::OutOfMemoryCallback oomCallback = runtime()->oomCallback) { oomCallback(this, runtime()->oomCallbackData); } // If we OOM early in process startup, this may be unavailable so just return // instead of crashing unexpectedly. if (MOZ_UNLIKELY(!runtime()->hasInitializedSelfHosting())) { return; } RootedValue oomMessage(this, StringValue(names().outOfMemory)); setPendingException(oomMessage, nullptr); MOZ_ASSERT(status == JS::ExceptionStatus::Throwing); status = JS::ExceptionStatus::OutOfMemory; reportResourceExhaustion(); } JS_PUBLIC_API void js::ReportOutOfMemory(JSContext* cx) { MaybeReportOutOfMemoryForDifferentialTesting(); MOZ_ASSERT(cx->isMainThreadContext()); cx->onOutOfMemory(); } JS_PUBLIC_API void js::ReportOutOfMemory(FrontendContext* fc) { MaybeReportOutOfMemoryForDifferentialTesting(); fc->onOutOfMemory(); } static void MaybeReportOverRecursedForDifferentialTesting() { /* * We cannot make stack depth deterministic across different * implementations (e.g. JIT vs. interpreter will differ in * their maximum stack depth). * However, we can detect externally when we hit the maximum * stack depth which is useful for external testing programs * like fuzzers. */ if (js::SupportDifferentialTesting()) { fprintf(stderr, "ReportOverRecursed called\n"); } } void JSContext::onOverRecursed() { if (isHelperThreadContext()) { addPendingOverRecursed(); } else { // Try to construct an over-recursed error and then update the exception // status to `OverRecursed`. Creating the error can fail, so check there // is a reasonable looking exception pending before updating status. JS_ReportErrorNumberASCII(this, GetErrorMessage, nullptr, JSMSG_OVER_RECURSED); if (isExceptionPending() && !isThrowingOutOfMemory()) { MOZ_ASSERT(unwrappedException().isObject()); MOZ_ASSERT(status == JS::ExceptionStatus::Throwing); status = JS::ExceptionStatus::OverRecursed; } } reportResourceExhaustion(); } JS_PUBLIC_API void js::ReportOverRecursed(JSContext* maybecx) { MaybeReportOverRecursedForDifferentialTesting(); if (!maybecx) { return; } MOZ_ASSERT(maybecx->isMainThreadContext()); maybecx->onOverRecursed(); } JS_PUBLIC_API void js::ReportOverRecursed(FrontendContext* fc) { MaybeReportOverRecursedForDifferentialTesting(); fc->onOverRecursed(); } void js::ReportOversizedAllocation(JSContext* cx, const unsigned errorNumber) { // The JIT may optimize away allocations if it determines that they aren't // used. This can affect whether we throw an exception when the size of an // allocation exceeds implementation-defined limits (eg JSString::MAX_LENGTH). // These errors aren't interesting for the purposes of differential fuzzing. // We print a message so that fuzzers can detect this case. To simplify // tooling updates, we use the same message as ReportOutOfMemory. if (js::SupportDifferentialTesting()) { fprintf(stderr, "ReportOutOfMemory called\n"); } gc::AutoSuppressGC suppressGC(cx); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber); cx->reportResourceExhaustion(); } void js::ReportAllocationOverflow(JSContext* cx) { if (js::SupportDifferentialTesting()) { fprintf(stderr, "ReportAllocationOverflow called\n"); } if (!cx) { return; } MOZ_ASSERT(cx->isMainThreadContext()); cx->reportAllocationOverflow(); } void js::ReportAllocationOverflow(FrontendContext* fc) { fc->onAllocationOverflow(); } /* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */ void js::ReportUsageErrorASCII(JSContext* cx, HandleObject callee, const char* msg) { RootedValue usage(cx); if (!JS_GetProperty(cx, callee, "usage", &usage)) { return; } if (!usage.isString()) { JS_ReportErrorASCII(cx, "%s", msg); } else { RootedString usageStr(cx, usage.toString()); UniqueChars str = JS_EncodeStringToUTF8(cx, usageStr); if (!str) { return; } JS_ReportErrorUTF8(cx, "%s. Usage: %s", msg, str.get()); } } enum class PrintErrorKind { Error, Warning, Note }; static void PrintErrorLine(FILE* file, const char* prefix, JSErrorReport* report) { if (const char16_t* linebuf = report->linebuf()) { UniqueChars line; size_t n; { size_t linebufLen = report->linebufLength(); // This function is only used for shell command-line sorts of stuff where // performance doesn't really matter, so just encode into max-sized // memory. mozilla::CheckedInt utf8Len(linebufLen); utf8Len *= 3; if (utf8Len.isValid()) { line = UniqueChars(js_pod_malloc(utf8Len.value())); if (line) { n = mozilla::ConvertUtf16toUtf8({linebuf, linebufLen}, {line.get(), utf8Len.value()}); } } } const char* utf8buf; if (line) { utf8buf = line.get(); } else { static const char unavailableStr[] = ""; utf8buf = unavailableStr; n = js_strlen(unavailableStr); } fputs(":\n", file); if (prefix) { fputs(prefix, file); } for (size_t i = 0; i < n; i++) { fputc(utf8buf[i], file); } // linebuf/utf8buf usually ends with a newline. If not, add one here. if (n == 0 || utf8buf[n - 1] != '\n') { fputc('\n', file); } if (prefix) { fputs(prefix, file); } n = report->tokenOffset(); for (size_t i = 0, j = 0; i < n; i++) { if (utf8buf[i] == '\t') { for (size_t k = (j + 8) & ~7; j < k; j++) { fputc('.', file); } continue; } fputc('.', file); j++; } fputc('^', file); } } static void PrintErrorLine(FILE* file, const char* prefix, JSErrorNotes::Note* note) {} template static void PrintSingleError(FILE* file, JS::ConstUTF8CharsZ toStringResult, T* report, PrintErrorKind kind) { UniqueChars prefix; if (report->filename) { prefix = JS_smprintf("%s:", report->filename); } if (report->lineno) { prefix = JS_smprintf("%s%u:%u ", prefix ? prefix.get() : "", report->lineno, report->column); } if (kind != PrintErrorKind::Error) { const char* kindPrefix = nullptr; switch (kind) { case PrintErrorKind::Error: MOZ_CRASH("unreachable"); case PrintErrorKind::Warning: kindPrefix = "warning"; break; case PrintErrorKind::Note: kindPrefix = "note"; break; } prefix = JS_smprintf("%s%s: ", prefix ? prefix.get() : "", kindPrefix); } const char* message = toStringResult ? toStringResult.c_str() : report->message().c_str(); /* embedded newlines -- argh! */ const char* ctmp; while ((ctmp = strchr(message, '\n')) != 0) { ctmp++; if (prefix) { fputs(prefix.get(), file); } (void)fwrite(message, 1, ctmp - message, file); message = ctmp; } /* If there were no filename or lineno, the prefix might be empty */ if (prefix) { fputs(prefix.get(), file); } fputs(message, file); PrintErrorLine(file, prefix.get(), report); fputc('\n', file); fflush(file); } static void PrintErrorImpl(FILE* file, JS::ConstUTF8CharsZ toStringResult, JSErrorReport* report, bool reportWarnings) { MOZ_ASSERT(report); /* Conditionally ignore reported warnings. */ if (report->isWarning() && !reportWarnings) { return; } PrintErrorKind kind = PrintErrorKind::Error; if (report->isWarning()) { kind = PrintErrorKind::Warning; } PrintSingleError(file, toStringResult, report, kind); if (report->notes) { for (auto&& note : *report->notes) { PrintSingleError(file, JS::ConstUTF8CharsZ(), note.get(), PrintErrorKind::Note); } } } JS_PUBLIC_API void JS::PrintError(FILE* file, JSErrorReport* report, bool reportWarnings) { PrintErrorImpl(file, JS::ConstUTF8CharsZ(), report, reportWarnings); } JS_PUBLIC_API void JS::PrintError(FILE* file, const JS::ErrorReportBuilder& builder, bool reportWarnings) { PrintErrorImpl(file, builder.toStringResult(), builder.report(), reportWarnings); } void js::ReportIsNotDefined(JSContext* cx, HandleId id) { if (UniqueChars printable = IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_DEFINED, printable.get()); } } void js::ReportIsNotDefined(JSContext* cx, Handle name) { RootedId id(cx, NameToId(name)); ReportIsNotDefined(cx, id); } const char* NullOrUndefinedToCharZ(HandleValue v) { MOZ_ASSERT(v.isNullOrUndefined()); return v.isNull() ? js_null_str : js_undefined_str; } void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v, int vIndex) { MOZ_ASSERT(v.isNullOrUndefined()); if (vIndex == JSDVG_IGNORE_STACK) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, NullOrUndefinedToCharZ(v), "object"); return; } UniqueChars bytes = DecompileValueGenerator(cx, vIndex, v, nullptr); if (!bytes) { return; } if (strcmp(bytes.get(), js_undefined_str) == 0 || strcmp(bytes.get(), js_null_str) == 0) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NO_PROPERTIES, bytes.get()); } else { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, bytes.get(), NullOrUndefinedToCharZ(v)); } } void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v, int vIndex, HandleId key) { MOZ_ASSERT(v.isNullOrUndefined()); if (!cx->realm()->creationOptions().getPropertyErrorMessageFixEnabled()) { ReportIsNullOrUndefinedForPropertyAccess(cx, v, vIndex); return; } RootedValue idVal(cx, IdToValue(key)); RootedString idStr(cx, ValueToSource(cx, idVal)); if (!idStr) { return; } UniqueChars keyStr = StringToNewUTF8CharsZ(cx, *idStr); if (!keyStr) { return; } if (vIndex == JSDVG_IGNORE_STACK) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL, keyStr.get(), NullOrUndefinedToCharZ(v)); return; } UniqueChars bytes = DecompileValueGenerator(cx, vIndex, v, nullptr); if (!bytes) { return; } if (strcmp(bytes.get(), js_undefined_str) == 0 || strcmp(bytes.get(), js_null_str) == 0) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL, keyStr.get(), bytes.get()); return; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL_EXPR, keyStr.get(), bytes.get(), NullOrUndefinedToCharZ(v)); } bool js::ReportValueError(JSContext* cx, const unsigned errorNumber, int spindex, HandleValue v, HandleString fallback, const char* arg1, const char* arg2) { MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1); MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3); UniqueChars bytes = DecompileValueGenerator(cx, spindex, v, fallback); if (!bytes) { return false; } JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber, bytes.get(), arg1, arg2); return false; } JSObject* js::CreateErrorNotesArray(JSContext* cx, JSErrorReport* report) { Rooted notesArray(cx, NewDenseEmptyArray(cx)); if (!notesArray) { return nullptr; } if (!report->notes) { return notesArray; } for (auto&& note : *report->notes) { Rooted noteObj(cx, NewPlainObject(cx)); if (!noteObj) { return nullptr; } RootedString messageStr(cx, note->newMessageString(cx)); if (!messageStr) { return nullptr; } RootedValue messageVal(cx, StringValue(messageStr)); if (!DefineDataProperty(cx, noteObj, cx->names().message, messageVal)) { return nullptr; } RootedValue filenameVal(cx); if (const char* filename = note->filename) { JS::UTF8Chars utf8chars(filename, strlen(filename)); Rooted filenameStr(cx, NewStringCopyUTF8N(cx, utf8chars)); if (!filenameStr) { return nullptr; } filenameVal = StringValue(filenameStr); } if (!DefineDataProperty(cx, noteObj, cx->names().fileName, filenameVal)) { return nullptr; } RootedValue linenoVal(cx, Int32Value(note->lineno)); if (!DefineDataProperty(cx, noteObj, cx->names().lineNumber, linenoVal)) { return nullptr; } RootedValue columnVal(cx, Int32Value(note->column)); if (!DefineDataProperty(cx, noteObj, cx->names().columnNumber, columnVal)) { return nullptr; } if (!NewbornArrayPush(cx, notesArray, ObjectValue(*noteObj))) { return nullptr; } } return notesArray; } void JSContext::recoverFromOutOfMemory() { if (isHelperThreadContext()) { // Keep in sync with addPendingOutOfMemory. if (FrontendErrors* errors = frontendErrors()) { errors->outOfMemory = false; } } else { if (isExceptionPending()) { MOZ_ASSERT(isThrowingOutOfMemory()); clearPendingException(); } } } void JSContext::reportAllocationOverflow() { if (isHelperThreadContext()) { return; } gc::AutoSuppressGC suppressGC(this); JS_ReportErrorNumberASCII(this, GetErrorMessage, nullptr, JSMSG_ALLOC_OVERFLOW); } JS::StackKind JSContext::stackKindForCurrentPrincipal() { return runningWithTrustedPrincipals() ? JS::StackForTrustedScript : JS::StackForUntrustedScript; } JS::NativeStackLimit JSContext::stackLimitForCurrentPrincipal() { return stackLimit(stackKindForCurrentPrincipal()); } JS_PUBLIC_API bool js::UseInternalJobQueues(JSContext* cx) { // Internal job queue handling must be set up very early. Self-hosting // initialization is as good a marker for that as any. MOZ_RELEASE_ASSERT( !cx->runtime()->hasInitializedSelfHosting(), "js::UseInternalJobQueues must be called early during runtime startup."); MOZ_ASSERT(!cx->jobQueue); auto queue = MakeUnique(cx); if (!queue) { return false; } cx->internalJobQueue = std::move(queue); cx->jobQueue = cx->internalJobQueue.ref().get(); cx->runtime()->offThreadPromiseState.ref().initInternalDispatchQueue(); MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized()); return true; } #ifdef DEBUG JSObject* InternalJobQueue::copyJobs(JSContext* cx) { Rooted jobs(cx, NewDenseEmptyArray(cx)); if (!jobs) { return nullptr; } for (const JSObject* unwrappedJob : queue.get()) { RootedObject job(cx, const_cast(unwrappedJob)); if (!cx->compartment()->wrap(cx, &job)) { return nullptr; } if (!NewbornArrayPush(cx, jobs, ObjectValue(*job))) { return nullptr; } } return jobs; } JS_PUBLIC_API JSObject* js::GetJobsInInternalJobQueue(JSContext* cx) { MOZ_ASSERT(cx->internalJobQueue.ref()); return cx->internalJobQueue->copyJobs(cx); } #endif JS_PUBLIC_API bool js::EnqueueJob(JSContext* cx, JS::HandleObject job) { MOZ_ASSERT(cx->jobQueue); return cx->jobQueue->enqueuePromiseJob(cx, nullptr, job, nullptr, nullptr); } JS_PUBLIC_API void js::StopDrainingJobQueue(JSContext* cx) { MOZ_ASSERT(cx->internalJobQueue.ref()); cx->internalJobQueue->interrupt(); } JS_PUBLIC_API void js::RunJobs(JSContext* cx) { MOZ_ASSERT(cx->jobQueue); cx->jobQueue->runJobs(cx); JS::ClearKeptObjects(cx); } JSObject* InternalJobQueue::getIncumbentGlobal(JSContext* cx) { if (!cx->compartment()) { return nullptr; } return cx->global(); } bool InternalJobQueue::enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, JS::HandleObject job, JS::HandleObject allocationSite, JS::HandleObject incumbentGlobal) { MOZ_ASSERT(job); if (!queue.pushBack(job)) { ReportOutOfMemory(cx); return false; } JS::JobQueueMayNotBeEmpty(cx); return true; } void InternalJobQueue::runJobs(JSContext* cx) { if (draining_ || interrupted_) { return; } while (true) { cx->runtime()->offThreadPromiseState.ref().internalDrain(cx); // It doesn't make sense for job queue draining to be reentrant. At the // same time we don't want to assert against it, because that'd make // drainJobQueue unsafe for fuzzers. We do want fuzzers to test this, // so we simply ignore nested calls of drainJobQueue. draining_ = true; RootedObject job(cx); JS::HandleValueArray args(JS::HandleValueArray::empty()); RootedValue rval(cx); // Execute jobs in a loop until we've reached the end of the queue. while (!queue.empty()) { // A previous job might have set this flag. E.g., the js shell // sets it if the `quit` builtin function is called. if (interrupted_) { break; } job = queue.front(); queue.popFront(); // If the next job is the last job in the job queue, allow // skipping the standard job queuing behavior. if (queue.empty()) { JS::JobQueueIsEmpty(cx); } AutoRealm ar(cx, &job->as()); { if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) { // Nothing we can do about uncatchable exceptions. if (!cx->isExceptionPending()) { continue; } RootedValue exn(cx); if (cx->getPendingException(&exn)) { /* * Clear the exception, because * PrepareScriptEnvironmentAndInvoke will assert that we don't * have one. */ cx->clearPendingException(); js::ReportExceptionClosure reportExn(exn); PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn); } } } } draining_ = false; if (interrupted_) { interrupted_ = false; break; } queue.clear(); // It's possible a job added a new off-thread promise task. if (!cx->runtime()->offThreadPromiseState.ref().internalHasPending()) { break; } } } bool InternalJobQueue::empty() const { return queue.empty(); } JSObject* InternalJobQueue::maybeFront() const { if (queue.empty()) { return nullptr; } return queue.get().front(); } class js::InternalJobQueue::SavedQueue : public JobQueue::SavedJobQueue { public: SavedQueue(JSContext* cx, Queue&& saved, bool draining) : cx(cx), saved(cx, std::move(saved)), draining_(draining) { MOZ_ASSERT(cx->internalJobQueue.ref()); } ~SavedQueue() { MOZ_ASSERT(cx->internalJobQueue.ref()); cx->internalJobQueue->queue = std::move(saved.get()); cx->internalJobQueue->draining_ = draining_; } private: JSContext* cx; PersistentRooted saved; bool draining_; }; js::UniquePtr InternalJobQueue::saveJobQueue( JSContext* cx) { auto saved = js::MakeUnique(cx, std::move(queue.get()), draining_); if (!saved) { // When MakeUnique's allocation fails, the SavedQueue constructor is never // called, so this->queue is still initialized. (The move doesn't occur // until the constructor gets called.) ReportOutOfMemory(cx); return nullptr; } queue = Queue(SystemAllocPolicy()); draining_ = false; return saved; } mozilla::GenericErrorResult JSContext::alreadyReportedOOM() { #ifdef DEBUG if (isHelperThreadContext()) { // Keep in sync with addPendingOutOfMemory. if (FrontendErrors* errors = frontendErrors()) { MOZ_ASSERT(errors->outOfMemory); } } else { MOZ_ASSERT(isThrowingOutOfMemory()); } #endif return mozilla::Err(JS::OOM()); } mozilla::GenericErrorResult JSContext::alreadyReportedError() { return mozilla::Err(JS::Error()); } JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options) : runtime_(runtime), kind_(ContextKind::Uninitialized), options_(this, options), freeUnusedMemory(false), measuringExecutionTime_(this, false), jitActivation(this, nullptr), isolate(this, nullptr), activation_(this, nullptr), profilingActivation_(nullptr), entryMonitor(this, nullptr), noExecuteDebuggerTop(this, nullptr), #ifdef DEBUG inUnsafeCallWithABI(this, false), hasAutoUnsafeCallWithABI(this, false), #endif #ifdef JS_SIMULATOR simulator_(this, nullptr), #endif dtoaState(this, nullptr), suppressGC(this, 0), #ifdef FUZZING_JS_FUZZILLI executionHash(1), executionHashInputs(0), #endif #ifdef DEBUG noNurseryAllocationCheck(this, 0), disableStrictProxyCheckingCount(this, 0), #endif #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) runningOOMTest(this, false), #endif inUnsafeRegion(this, 0), generationalDisabled(this, 0), compactingDisabledCount(this, 0), frontendCollectionPool_(this), suppressProfilerSampling(false), tempLifoAlloc_(this, (size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), debuggerMutations(this, 0), ionPcScriptCache(this, nullptr), status(this, JS::ExceptionStatus::None), unwrappedException_(this), unwrappedExceptionStack_(this), #ifdef DEBUG hadResourceExhaustion_(this, false), #endif reportGranularity(this, JS_DEFAULT_JITREPORT_GRANULARITY), resolvingList(this, nullptr), #ifdef DEBUG enteredPolicy(this, nullptr), #endif generatingError(this, false), cycleDetectorVector_(this, this), data(nullptr), asyncStackForNewActivations_(this), asyncCauseForNewActivations(this, nullptr), asyncCallIsExplicit(this, false), interruptCallbacks_(this), interruptCallbackDisabled(this, false), interruptBits_(0), inlinedICScript_(this, nullptr), jitStackLimit(JS::NativeStackLimitMin), jitStackLimitNoInterrupt(this, JS::NativeStackLimitMin), jobQueue(this, nullptr), internalJobQueue(this), canSkipEnqueuingJobs(this, false), promiseRejectionTrackerCallback(this, nullptr), promiseRejectionTrackerCallbackData(this, nullptr), #ifdef JS_STRUCTURED_SPEW structuredSpewer_(), #endif insideDebuggerEvaluationWithOnNativeCallHook(this, nullptr) { MOZ_ASSERT(static_cast(this) == JS::RootingContext::get(this)); } JSContext::~JSContext() { // Clear the ContextKind first, so that ProtectedData checks will allow us to // destroy this context even if the runtime is already gone. kind_ = ContextKind::Uninitialized; /* Free the stuff hanging off of cx. */ MOZ_ASSERT(!resolvingList); if (dtoaState) { DestroyDtoaState(dtoaState); } fx.destroyInstance(); #ifdef JS_SIMULATOR js::jit::Simulator::Destroy(simulator_); #endif if (isolate) { irregexp::DestroyIsolate(isolate.ref()); } TlsContext.set(nullptr); } void JSContext::setHelperThread(const JS::ContextOptions& options, const AutoLockHelperThreadState& locked) { MOZ_ASSERT(isHelperThreadContext()); MOZ_ASSERT_IF(!JSRuntime::hasLiveRuntimes(), !TlsContext.get()); MOZ_ASSERT(currentThread_ == ThreadId()); TlsContext.set(this); currentThread_ = ThreadId::ThisThreadId(); options_ = options; } void JSContext::clearHelperThread(const AutoLockHelperThreadState& locked) { MOZ_ASSERT(isHelperThreadContext()); MOZ_ASSERT(TlsContext.get() == this); MOZ_ASSERT(currentThread_ == ThreadId::ThisThreadId()); currentThread_ = ThreadId(); options_ = JS::ContextOptions(); TlsContext.set(nullptr); } void JSContext::setRuntime(JSRuntime* rt) { MOZ_ASSERT(!resolvingList); MOZ_ASSERT(!compartment()); MOZ_ASSERT(!activation()); MOZ_ASSERT(!unwrappedException_.ref().initialized()); MOZ_ASSERT(!unwrappedExceptionStack_.ref().initialized()); MOZ_ASSERT(!asyncStackForNewActivations_.ref().initialized()); runtime_ = rt; } #if defined(NIGHTLY_BUILD) static bool IsOutOfMemoryException(JSContext* cx, const Value& v) { return v == StringValue(cx->names().outOfMemory); } #endif void JSContext::setPendingException(HandleValue v, Handle stack) { #if defined(NIGHTLY_BUILD) do { // Do not intercept exceptions if we are already // in the exception interceptor. That would lead // to infinite recursion. if (this->runtime()->errorInterception.isExecuting) { break; } // Check whether we have an interceptor at all. if (!this->runtime()->errorInterception.interceptor) { break; } // Don't report OOM exceptions. The interceptor isn't interested in those // and they can confuse the interceptor because OOM can be thrown when we // are not in a realm (atom allocation, for example). if (IsOutOfMemoryException(this, v)) { break; } // Make sure that we do not call the interceptor from within // the interceptor. this->runtime()->errorInterception.isExecuting = true; // The interceptor must be infallible. const mozilla::DebugOnly wasExceptionPending = this->isExceptionPending(); this->runtime()->errorInterception.interceptor->interceptError(this, v); MOZ_ASSERT(wasExceptionPending == this->isExceptionPending()); this->runtime()->errorInterception.isExecuting = false; } while (false); #endif // defined(NIGHTLY_BUILD) // overRecursed_ is set after the fact by ReportOverRecursed. this->status = JS::ExceptionStatus::Throwing; this->unwrappedException() = v; this->unwrappedExceptionStack() = stack; } void JSContext::setPendingException(HandleValue value, ShouldCaptureStack captureStack) { Rooted nstack(this); if (captureStack == ShouldCaptureStack::Always || realm()->shouldCaptureStackForThrow()) { RootedObject stack(this); if (!CaptureStack(this, &stack)) { clearPendingException(); } if (stack) { nstack = &stack->as(); } } setPendingException(value, nstack); } bool JSContext::getPendingException(MutableHandleValue rval) { MOZ_ASSERT(isExceptionPending()); RootedValue exception(this, unwrappedException()); if (zone()->isAtomsZone()) { rval.set(exception); return true; } Rooted stack(this, unwrappedExceptionStack()); JS::ExceptionStatus prevStatus = status; clearPendingException(); if (!compartment()->wrap(this, &exception)) { return false; } this->check(exception); setPendingException(exception, stack); status = prevStatus; rval.set(exception); return true; } SavedFrame* JSContext::getPendingExceptionStack() { return unwrappedExceptionStack(); } bool JSContext::isClosingGenerator() { return isExceptionPending() && unwrappedException().isMagic(JS_GENERATOR_CLOSING); } bool JSContext::isThrowingDebuggeeWouldRun() { return isExceptionPending() && unwrappedException().isObject() && unwrappedException().toObject().is() && unwrappedException().toObject().as().type() == JSEXN_DEBUGGEEWOULDRUN; } bool JSContext::isRuntimeCodeGenEnabled(JS::RuntimeCode kind, HandleString code) { // Make sure that the CSP callback is installed and that it permits runtime // code generation. if (JSCSPEvalChecker allows = runtime()->securityCallbacks->contentSecurityPolicyAllows) { return allows(this, kind, code); } return true; } size_t JSContext::sizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf) const { /* * There are other JSContext members that could be measured; the following * ones have been found by DMD to be worth measuring. More stuff may be * added later. */ return cycleDetectorVector().sizeOfExcludingThis(mallocSizeOf) + irregexp::IsolateSizeOfIncludingThis(isolate, mallocSizeOf); } size_t JSContext::sizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); } #ifdef DEBUG bool JSContext::inAtomsZone() const { return zone_->isAtomsZone(); } #endif void JSContext::trace(JSTracer* trc) { cycleDetectorVector().trace(trc); geckoProfiler().trace(trc); if (isolate) { irregexp::TraceIsolate(trc, isolate.ref()); } } JS::NativeStackLimit JSContext::stackLimitForJitCode(JS::StackKind kind) { MOZ_ASSERT(isMainThreadContext()); #ifdef JS_SIMULATOR return simulator()->stackLimit(); #else return stackLimit(kind); #endif } void JSContext::resetJitStackLimit() { MOZ_ASSERT(isMainThreadContext()); // Note that, for now, we use the untrusted limit for ion. This is fine, // because it's the most conservative limit, and if we hit it, we'll bail // out of ion into the interpreter, which will do a proper recursion check. #ifdef JS_SIMULATOR jitStackLimit = jit::Simulator::StackLimit(); #else jitStackLimit = nativeStackLimit[JS::StackForUntrustedScript]; #endif jitStackLimitNoInterrupt = jitStackLimit; } void JSContext::initJitStackLimit() { resetJitStackLimit(); } #ifdef JS_CRASH_DIAGNOSTICS void ContextChecks::check(AbstractFramePtr frame, int argIndex) { if (frame) { check(frame.realm(), argIndex); } } #endif void AutoEnterOOMUnsafeRegion::crash(const char* reason) { char msgbuf[1024]; js::NoteIntentionalCrash(); SprintfLiteral(msgbuf, "[unhandlable oom] %s", reason); #ifndef DEBUG // In non-DEBUG builds MOZ_CRASH normally doesn't print to stderr so we have // to do this explicitly (the jit-test allow-unhandlable-oom annotation and // fuzzers depend on it). fprintf(stderr, "Hit MOZ_CRASH(%s) at %s:%d\n", msgbuf, __FILE__, __LINE__); #endif MOZ_CRASH_UNSAFE(msgbuf); } mozilla::Atomic AutoEnterOOMUnsafeRegion::annotateOOMSizeCallback(nullptr); void AutoEnterOOMUnsafeRegion::crash(size_t size, const char* reason) { { JS::AutoSuppressGCAnalysis suppress; if (annotateOOMSizeCallback) { annotateOOMSizeCallback(size); } } crash(reason); } void ExternalValueArray::trace(JSTracer* trc) { if (Value* vp = begin()) { TraceRootRange(trc, length(), vp, "js::ExternalValueArray"); } } #ifdef DEBUG AutoUnsafeCallWithABI::AutoUnsafeCallWithABI(UnsafeABIStrictness strictness) : cx_(TlsContext.get()), nested_(cx_ ? cx_->hasAutoUnsafeCallWithABI : false), nogc(cx_) { if (!cx_) { // This is a helper thread doing Ion or Wasm compilation - nothing to do. return; } switch (strictness) { case UnsafeABIStrictness::NoExceptions: MOZ_ASSERT(!JS_IsExceptionPending(cx_)); checkForPendingException_ = true; break; case UnsafeABIStrictness::AllowPendingExceptions: checkForPendingException_ = !JS_IsExceptionPending(cx_); break; case UnsafeABIStrictness::AllowThrownExceptions: checkForPendingException_ = false; break; } cx_->hasAutoUnsafeCallWithABI = true; } AutoUnsafeCallWithABI::~AutoUnsafeCallWithABI() { if (!cx_) { return; } MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI); if (!nested_) { cx_->hasAutoUnsafeCallWithABI = false; cx_->inUnsafeCallWithABI = false; } MOZ_ASSERT_IF(checkForPendingException_, !JS_IsExceptionPending(cx_)); } #endif #ifdef __wasi__ JS_PUBLIC_API void js::IncWasiRecursionDepth(JSContext* cx) { ++JS::RootingContext::get(cx)->wasiRecursionDepth; } JS_PUBLIC_API void js::DecWasiRecursionDepth(JSContext* cx) { MOZ_ASSERT(JS::RootingContext::get(cx)->wasiRecursionDepth > 0); --JS::RootingContext::get(cx)->wasiRecursionDepth; } JS_PUBLIC_API bool js::CheckWasiRecursionLimit(JSContext* cx) { // WASI has two limits: // 1) The stack pointer in linear memory that grows to zero. See // --stack-first in js/src/shell/moz.build. // 2) The JS::RootingContext::wasiRecursionDepth that counts recursion depth. // Here we should check both. if (JS::RootingContext::get(cx)->wasiRecursionDepth >= JS::RootingContext::wasiRecursionDepthLimit) { return false; } return true; } #endif // __wasi__