summaryrefslogtreecommitdiffstats
path: root/js/src/vm/JSContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/vm/JSContext.cpp1386
1 files changed, 1386 insertions, 0 deletions
diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp
new file mode 100644
index 0000000000..fd24c4d673
--- /dev/null
+++ b/js/src/vm/JSContext.cpp
@@ -0,0 +1,1386 @@
+/* -*- 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 <string.h>
+#ifdef ANDROID
+# include <android/log.h>
+# include <fstream>
+# include <string>
+#endif // ANDROID
+#ifdef XP_WIN
+# include <processthreadsapi.h>
+#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<JSRuntime>(parentRuntime);
+ if (!runtime) {
+ return nullptr;
+ }
+
+ JSContext* cx = js_new<JSContext>(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<size_t> utf8Len(linebufLen);
+ utf8Len *= 3;
+ if (utf8Len.isValid()) {
+ line = UniqueChars(js_pod_malloc<char>(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[] = "<context unavailable>";
+ 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 <typename T>
+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<PropertyName*> 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<ArrayObject*> notesArray(cx, NewDenseEmptyArray(cx));
+ if (!notesArray) {
+ return nullptr;
+ }
+
+ if (!report->notes) {
+ return notesArray;
+ }
+
+ for (auto&& note : *report->notes) {
+ Rooted<PlainObject*> 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<JSString*> 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<InternalJobQueue>(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<ArrayObject*> jobs(cx, NewDenseEmptyArray(cx));
+ if (!jobs) {
+ return nullptr;
+ }
+
+ for (const JSObject* unwrappedJob : queue.get()) {
+ RootedObject job(cx, const_cast<JSObject*>(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<JSFunction>());
+ {
+ 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<Queue> saved;
+ bool draining_;
+};
+
+js::UniquePtr<JS::JobQueue::SavedJobQueue> InternalJobQueue::saveJobQueue(
+ JSContext* cx) {
+ auto saved =
+ js::MakeUnique<SavedQueue>(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<OOM> 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<JS::Error> 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<JS::RootingContext*>(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<SavedFrame*> 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<bool> 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<SavedFrame*> nstack(this);
+ if (captureStack == ShouldCaptureStack::Always ||
+ realm()->shouldCaptureStackForThrow()) {
+ RootedObject stack(this);
+ if (!CaptureStack(this, &stack)) {
+ clearPendingException();
+ }
+ if (stack) {
+ nstack = &stack->as<SavedFrame>();
+ }
+ }
+ setPendingException(value, nstack);
+}
+
+bool JSContext::getPendingException(MutableHandleValue rval) {
+ MOZ_ASSERT(isExceptionPending());
+
+ RootedValue exception(this, unwrappedException());
+ if (zone()->isAtomsZone()) {
+ rval.set(exception);
+ return true;
+ }
+
+ Rooted<SavedFrame*> 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<ErrorObject>() &&
+ unwrappedException().toObject().as<ErrorObject>().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::AnnotateOOMAllocationSizeCallback,
+ mozilla::Relaxed>
+ 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__