/* -*- 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/TextUtils.h" #include "mozilla/Unused.h" #include "mozilla/Utf8.h" // mozilla::ConvertUtf16ToUtf8 #include #include #ifdef ANDROID # include # include # include #endif // ANDROID #ifdef XP_WIN # include #endif // XP_WIN #include "jsexn.h" #include "jspubtd.h" #include "jstypes.h" #include "gc/FreeOp.h" #include "gc/Marking.h" #include "gc/PublicIterators.h" #include "irregexp/RegExpAPI.h" #include "jit/Ion.h" #include "jit/PcScriptCache.h" #include "jit/Simulator.h" #include "js/CharacterEncoding.h" #include "js/ContextOptions.h" // JS::ContextOptions #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/StackLimits.h" // js::ReportOverRecursed #include "js/Printf.h" #include "util/DiagnosticAssertions.h" #include "util/DifferentialTesting.h" #include "util/DoubleToString.h" #include "util/NativeStack.h" #include "util/Text.h" #include "util/Windows.h" #include "vm/BytecodeUtil.h" // JSDVG_IGNORE_STACK #include "vm/ErrorObject.h" #include "vm/ErrorReporting.h" #include "vm/HelperThreadState.h" #include "vm/Iteration.h" #include "vm/JSAtom.h" #include "vm/JSFunction.h" #include "vm/JSObject.h" #include "vm/JSScript.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/Realm.h" #include "vm/Shape.h" #include "vm/StringType.h" // StringToNewUTF8CharsZ #include "vm/ToSource.h" // js::ValueToSource #include "vm/Compartment-inl.h" #include "vm/JSObject-inl.h" #include "vm/JSScript-inl.h" #include "vm/Stack-inl.h" using namespace js; using mozilla::DebugOnly; using mozilla::PodArrayZero; #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(); if (!fx.initInstance()) { return false; } #ifdef JS_SIMULATOR simulator_ = jit::Simulator::Create(); if (!simulator_) { return false; } #endif } else { atomsZoneFreeLists_ = js_new(); if (!atomsZoneFreeLists_) { return false; } } 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; } 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; } return cx; } void js::DestroyContext(JSContext* cx) { JS_AbortIfWrongThread(cx); 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; } /* * Since memory has been exhausted, avoid the normal error-handling path which * allocates an error object, report and callstack. If code is running, simply * throw the static atom "out of memory". If code is not running, call the * error reporter directly. * * Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does * not occur, so GC must be avoided or suppressed. */ JS_FRIEND_API void js::ReportOutOfMemory(JSContext* cx) { /* * 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"); } if (cx->isHelperThreadContext()) { return cx->addPendingOutOfMemory(); } cx->runtime()->hadOutOfMemory = true; gc::AutoSuppressGC suppressGC(cx); /* Report the oom. */ if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback) { oomCallback(cx, cx->runtime()->oomCallbackData); } // If we OOM early in process startup, this may be unavailable so just return // instead of crashing unexpectedly. if (MOZ_UNLIKELY(!cx->runtime()->hasInitializedSelfHosting())) { return; } RootedValue oomMessage(cx, StringValue(cx->names().outOfMemory)); cx->setPendingException(oomMessage, nullptr); } mozilla::GenericErrorResult js::ReportOutOfMemoryResult(JSContext* cx) { ReportOutOfMemory(cx); return cx->alreadyReportedOOM(); } void js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber) { /* * 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"); } if (maybecx) { if (!maybecx->isHelperThreadContext()) { JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr, errorNumber); maybecx->overRecursed_ = true; } else { maybecx->addPendingOverRecursed(); } #ifdef DEBUG maybecx->hadOverRecursed_ = true; #endif } } JS_FRIEND_API void js::ReportOverRecursed(JSContext* maybecx) { ReportOverRecursed(maybecx, JSMSG_OVER_RECURSED); } void js::ReportAllocationOverflow(JSContext* cx) { if (!cx) { return; } if (cx->isHelperThreadContext()) { return; } gc::AutoSuppressGC suppressGC(cx); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ALLOC_OVERFLOW); } /* |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(JSContext* cx, 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); } mozilla::Unused << 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(JSContext* cx, 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(cx, file, toStringResult, report, kind); if (report->notes) { for (auto&& note : *report->notes) { PrintSingleError(cx, file, JS::ConstUTF8CharsZ(), note.get(), PrintErrorKind::Note); } } } JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file, JSErrorReport* report, bool reportWarnings) { PrintErrorImpl(cx, file, JS::ConstUTF8CharsZ(), report, reportWarnings); } JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file, const JS::ErrorReportBuilder& builder, bool reportWarnings) { PrintErrorImpl(cx, 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, HandlePropertyName 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) { RootedArrayObject notesArray(cx, NewDenseEmptyArray(cx)); if (!notesArray) { return nullptr; } if (!report->notes) { return notesArray; } for (auto&& note : *report->notes) { RootedPlainObject noteObj(cx, NewBuiltinClassInstance(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 (note->filename) { RootedString filenameStr(cx, NewStringCopyZ(cx, note->filename)); 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 (ParseTask* task = parseTask()) { task->outOfMemory = false; } } else { if (isExceptionPending()) { MOZ_ASSERT(isThrowingOutOfMemory()); clearPendingException(); } } } JS_FRIEND_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; } JS_FRIEND_API bool js::EnqueueJob(JSContext* cx, JS::HandleObject job) { MOZ_ASSERT(cx->jobQueue); return cx->jobQueue->enqueuePromiseJob(cx, nullptr, job, nullptr, nullptr); } JS_FRIEND_API void js::StopDrainingJobQueue(JSContext* cx) { MOZ_ASSERT(cx->internalJobQueue.ref()); cx->internalJobQueue->interrupt(); } JS_FRIEND_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 (ParseTask* task = parseTask()) { MOZ_ASSERT(task->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), nurserySuppressions_(this), options_(this, options), freeLists_(this, nullptr), atomsZoneFreeLists_(this), defaultFreeOp_(this, runtime, true), freeUnusedMemory(false), measuringExecutionTime_(this, false), jitActivation(this, nullptr), isolate(this, nullptr), activation_(this, nullptr), profilingActivation_(nullptr), nativeStackBase(GetNativeStackBase()), entryMonitor(this, nullptr), noExecuteDebuggerTop(this, nullptr), #ifdef DEBUG inUnsafeCallWithABI(this, false), hasAutoUnsafeCallWithABI(this, false), #endif #ifdef JS_SIMULATOR simulator_(this, nullptr), #endif #ifdef JS_TRACE_LOGGING traceLogger(nullptr), #endif dtoaState(this, nullptr), suppressGC(this, 0), #ifdef DEBUG gcUse(this, GCUse::None), gcSweepZone(this, nullptr), isTouchingGrayThings(this, false), 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), throwing(this, false), unwrappedException_(this), unwrappedExceptionStack_(this), overRecursed_(this, false), #ifdef DEBUG hadOverRecursed_(this, false), #endif propagatingForcedReturn_(this, false), 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(UINTPTR_MAX), jitStackLimitNoInterrupt(this, UINTPTR_MAX), 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 #ifdef JS_TRACE_LOGGING if (traceLogger) { DestroyTraceLogger(traceLogger); } #endif if (isolate) { irregexp::DestroyIsolate(isolate.ref()); } js_delete(atomsZoneFreeLists_.ref()); TlsContext.set(nullptr); } void JSContext::setHelperThread(const AutoLockHelperThreadState& locked) { MOZ_ASSERT(isHelperThreadContext()); MOZ_ASSERT_IF(!JSRuntime::hasLiveRuntimes(), !TlsContext.get()); MOZ_ASSERT(currentThread_ == ThreadId()); TlsContext.set(this); currentThread_ = ThreadId::ThisThreadId(); } void JSContext::clearHelperThread(const AutoLockHelperThreadState& locked) { MOZ_ASSERT(isHelperThreadContext()); MOZ_ASSERT(TlsContext.get() == this); MOZ_ASSERT(currentThread_ == ThreadId::ThisThreadId()); currentThread_ = ThreadId(); 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; } static bool IsOutOfMemoryException(JSContext* cx, const Value& v) { return v == StringValue(cx->names().outOfMemory); } void JSContext::setPendingException(HandleValue v, HandleSavedFrame 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->overRecursed_ = false; this->throwing = true; this->unwrappedException() = v; this->unwrappedExceptionStack() = stack; } void JSContext::setPendingExceptionAndCaptureStack(HandleValue value) { RootedObject stack(this); if (!CaptureStack(this, &stack)) { clearPendingException(); } RootedSavedFrame nstack(this); if (stack) { nstack = &stack->as(); } setPendingException(value, nstack); } bool JSContext::getPendingException(MutableHandleValue rval) { MOZ_ASSERT(throwing); rval.set(unwrappedException()); if (zone()->isAtomsZone()) { return true; } RootedSavedFrame stack(this, unwrappedExceptionStack()); bool wasOverRecursed = overRecursed_; clearPendingException(); if (!compartment()->wrap(this, rval)) { return false; } this->check(rval); setPendingException(rval, stack); overRecursed_ = wasOverRecursed; return true; } SavedFrame* JSContext::getPendingExceptionStack() { return unwrappedExceptionStack(); } bool JSContext::isThrowingOutOfMemory() { return throwing && IsOutOfMemoryException(this, unwrappedException()); } bool JSContext::isClosingGenerator() { return throwing && unwrappedException().isMagic(JS_GENERATOR_CLOSING); } bool JSContext::isThrowingDebuggeeWouldRun() { return throwing && unwrappedException().isObject() && unwrappedException().toObject().is() && unwrappedException().toObject().as().type() == JSEXN_DEBUGGEEWOULDRUN; } 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) + wasm_.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); } uintptr_t JSContext::stackLimitForJitCode(JS::StackKind kind) { #ifdef JS_SIMULATOR return simulator()->stackLimit(); #else return stackLimit(kind); #endif } void JSContext::resetJitStackLimit() { // 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). MOZ_ReportCrash(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