summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/TestingFunctions.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/builtin/TestingFunctions.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/TestingFunctions.cpp')
-rw-r--r--js/src/builtin/TestingFunctions.cpp9817
1 files changed, 9817 insertions, 0 deletions
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
new file mode 100644
index 0000000000..d9034c886d
--- /dev/null
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -0,0 +1,9817 @@
+/* -*- 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/. */
+
+#include "builtin/TestingFunctions.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
+#ifdef JS_HAS_INTL_API
+# include "mozilla/intl/ICU4CLibrary.h"
+# include "mozilla/intl/Locale.h"
+# include "mozilla/intl/String.h"
+# include "mozilla/intl/TimeZone.h"
+#endif
+#include "mozilla/Maybe.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Span.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/ThreadLocal.h"
+
+#include <algorithm>
+#include <cfloat>
+#include <cinttypes>
+#include <cmath>
+#include <cstdlib>
+#include <ctime>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <utility>
+
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
+# include <time.h>
+#else
+# include <chrono>
+#endif
+
+#include "fdlibm.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+#ifdef JS_HAS_INTL_API
+# include "builtin/intl/CommonFunctions.h"
+# include "builtin/intl/FormatBuffer.h"
+# include "builtin/intl/SharedIntlData.h"
+#endif
+#include "builtin/BigInt.h"
+#include "builtin/MapObject.h"
+#include "builtin/Promise.h"
+#include "builtin/TestingUtility.h" // js::ParseCompileOptions, js::ParseDebugMetadata
+#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScriptToExtensibleStencil, frontend::DelazifyCanonicalScriptedFunction
+#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToExtensibleStencil
+#include "frontend/CompilationStencil.h" // frontend::CompilationStencil
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "gc/Allocator.h"
+#include "gc/GC.h"
+#include "gc/GCLock.h"
+#include "gc/Zone.h"
+#include "jit/BaselineJIT.h"
+#include "jit/Disassemble.h"
+#include "jit/InlinableNatives.h"
+#include "jit/Invalidation.h"
+#include "jit/Ion.h"
+#include "jit/JitOptions.h"
+#include "jit/JitRuntime.h"
+#include "jit/TrialInlining.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/ArrayBuffer.h" // JS::{DetachArrayBuffer,GetArrayBufferLengthAndData,NewArrayBufferWithContents}
+#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable, JS::IsConstructor, JS_CallFunction
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/Date.h"
+#include "js/experimental/CodeCoverage.h" // js::GetCodeCoverageSummary
+#include "js/experimental/CompileScript.h" // JS::ParseGlobalScript, JS::PrepareForInstantiate
+#include "js/experimental/JSStencil.h" // JS::Stencil
+#include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
+#include "js/experimental/TypedData.h" // JS_GetObjectAsUint8Array
+#include "js/friend/DumpFunctions.h" // js::Dump{Backtrace,Heap,Object}, JS::FormatStackDump, js::IgnoreNurseryObjects
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/friend/WindowProxy.h" // js::ToWindowProxyIfWindow
+#include "js/GlobalObject.h"
+#include "js/HashTable.h"
+#include "js/Interrupt.h"
+#include "js/LocaleSensitive.h"
+#include "js/Printf.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperties, JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetElement, JS_SetProperty
+#include "js/PropertySpec.h"
+#include "js/SourceText.h"
+#include "js/StableStringChars.h"
+#include "js/Stack.h"
+#include "js/String.h" // JS::GetLinearStringLength, JS::StringToLinearString
+#include "js/StructuredClone.h"
+#include "js/UbiNode.h"
+#include "js/UbiNodeBreadthFirst.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "js/UniquePtr.h"
+#include "js/Vector.h"
+#include "js/Wrapper.h"
+#include "threading/CpuCount.h"
+#include "util/DifferentialTesting.h"
+#include "util/StringBuffer.h"
+#include "util/Text.h"
+#include "vm/BooleanObject.h"
+#include "vm/DateObject.h"
+#include "vm/DateTime.h"
+#include "vm/ErrorObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/HelperThreads.h"
+#include "vm/HelperThreadState.h"
+#include "vm/Interpreter.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/NumberObject.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseSlot_*
+#include "vm/ProxyObject.h"
+#include "vm/SavedStacks.h"
+#include "vm/ScopeKind.h"
+#include "vm/Stack.h"
+#include "vm/StencilObject.h" // StencilObject, StencilXDRBufferObject
+#include "vm/StringObject.h"
+#include "vm/StringType.h"
+#include "wasm/AsmJS.h"
+#include "wasm/WasmBaselineCompile.h"
+#include "wasm/WasmInstance.h"
+#include "wasm/WasmIntrinsic.h"
+#include "wasm/WasmIonCompile.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmModule.h"
+#include "wasm/WasmValType.h"
+#include "wasm/WasmValue.h"
+
+#include "debugger/DebugAPI-inl.h"
+#include "vm/Compartment-inl.h"
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectFlags-inl.h"
+#include "vm/StringType-inl.h"
+#include "wasm/WasmInstance-inl.h"
+
+using namespace js;
+
+using mozilla::AssertedCast;
+using mozilla::AsWritableChars;
+using mozilla::Maybe;
+using mozilla::Span;
+
+using JS::AutoStableStringChars;
+using JS::CompileOptions;
+using JS::SourceOwnership;
+using JS::SourceText;
+
+// If fuzzingSafe is set, remove functionality that could cause problems with
+// fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
+mozilla::Atomic<bool> js::fuzzingSafe(false);
+
+// If disableOOMFunctions is set, disable functionality that causes artificial
+// OOM conditions.
+static mozilla::Atomic<bool> disableOOMFunctions(false);
+
+static bool EnvVarIsDefined(const char* name) {
+ const char* value = getenv(name);
+ return value && *value;
+}
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+static bool EnvVarAsInt(const char* name, int* valueOut) {
+ if (!EnvVarIsDefined(name)) {
+ return false;
+ }
+
+ *valueOut = atoi(getenv(name));
+ return true;
+}
+#endif
+
+static bool GetRealmConfiguration(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info) {
+ return false;
+ }
+
+ bool importAssertions = cx->options().importAssertions();
+ if (!JS_SetProperty(cx, info, "importAssertions",
+ importAssertions ? TrueHandleValue : FalseHandleValue)) {
+ return false;
+ }
+
+#ifdef NIGHTLY_BUILD
+ bool arrayGrouping = cx->realm()->creationOptions().getArrayGroupingEnabled();
+ if (!JS_SetProperty(cx, info, "enableArrayGrouping",
+ arrayGrouping ? TrueHandleValue : FalseHandleValue)) {
+ return false;
+ }
+#endif
+
+ bool changeArrayByCopy =
+ cx->realm()->creationOptions().getChangeArrayByCopyEnabled();
+ if (!JS_SetProperty(cx, info, "enableChangeArrayByCopy",
+ changeArrayByCopy ? TrueHandleValue : FalseHandleValue)) {
+ return false;
+ }
+
+#ifdef ENABLE_NEW_SET_METHODS
+ bool newSetMethods = cx->realm()->creationOptions().getNewSetMethodsEnabled();
+ if (!JS_SetProperty(cx, info, "enableNewSetMethods",
+ newSetMethods ? TrueHandleValue : FalseHandleValue)) {
+ return false;
+ }
+#endif
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "rooting-analysis", FalseHandleValue)) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "exact-rooting", TrueHandleValue)) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "trace-jscalls-api", FalseHandleValue)) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "incremental-gc", TrueHandleValue)) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "generational-gc", TrueHandleValue)) {
+ return false;
+ }
+
+ if (!JS_SetProperty(cx, info, "oom-backtraces", FalseHandleValue)) {
+ return false;
+ }
+
+ RootedValue value(cx);
+#ifdef DEBUG
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "debug", value)) {
+ return false;
+ }
+
+#ifdef RELEASE_OR_BETA
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "release_or_beta", value)) {
+ return false;
+ }
+
+#ifdef EARLY_BETA_OR_EARLIER
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "early_beta_or_earlier", value)) {
+ return false;
+ }
+
+#ifdef MOZ_CODE_COVERAGE
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "coverage", value)) {
+ return false;
+ }
+
+#ifdef JS_HAS_CTYPES
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-ctypes", value)) {
+ return false;
+ }
+
+#if defined(_M_IX86) || defined(__i386__)
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x86", value)) {
+ return false;
+ }
+
+#if defined(_M_X64) || defined(__x86_64__)
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x64", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_ARM
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_ARM
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm-simulator", value)) {
+ return false;
+ }
+
+#ifdef ANDROID
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "android", value)) {
+ return false;
+ }
+
+#ifdef XP_WIN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "windows", value)) {
+ return false;
+ }
+
+#ifdef XP_MACOSX
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "osx", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_ARM64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm64", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_ARM64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm64-simulator", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_MIPS32
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "mips32", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_MIPS64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "mips64", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_MIPS32
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "mips32-simulator", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_MIPS64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "mips64-simulator", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "simulator", value)) {
+ return false;
+ }
+
+#ifdef __wasi__
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "wasi", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_LOONG64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "loong64", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_LOONG64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "loong64-simulator", value)) {
+ return false;
+ }
+
+#ifdef JS_CODEGEN_RISCV64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "riscv64", value)) {
+ return false;
+ }
+
+#ifdef JS_SIMULATOR_RISCV64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "riscv64-simulator", value)) {
+ return false;
+ }
+
+#ifdef MOZ_ASAN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "asan", value)) {
+ return false;
+ }
+
+#ifdef MOZ_TSAN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "tsan", value)) {
+ return false;
+ }
+
+#ifdef MOZ_UBSAN
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "ubsan", value)) {
+ return false;
+ }
+
+#ifdef JS_GC_ZEAL
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-gczeal", value)) {
+ return false;
+ }
+
+#ifdef MOZ_PROFILING
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "profiling", value)) {
+ return false;
+ }
+
+#ifdef INCLUDE_MOZILLA_DTRACE
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "dtrace", value)) {
+ return false;
+ }
+
+#ifdef MOZ_VALGRIND
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "valgrind", value)) {
+ return false;
+ }
+
+#ifdef JS_HAS_INTL_API
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "intl-api", value)) {
+ return false;
+ }
+
+#if defined(SOLARIS)
+ value = BooleanValue(false);
+#else
+ value = BooleanValue(true);
+#endif
+ if (!JS_SetProperty(cx, info, "mapped-array-buffer", value)) {
+ return false;
+ }
+
+#ifdef MOZ_MEMORY
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "moz-memory", value)) {
+ return false;
+ }
+
+ value.setInt32(sizeof(void*));
+ if (!JS_SetProperty(cx, info, "pointer-byte-size", value)) {
+ return false;
+ }
+
+#ifdef ENABLE_NEW_SET_METHODS
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "new-set-methods", value)) {
+ return false;
+ }
+
+#ifdef ENABLE_DECORATORS
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "decorators", value)) {
+ return false;
+ }
+
+#ifdef FUZZING
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "fuzzing-defined", value)) {
+ return false;
+ }
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool IsLCovEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(coverage::IsLCovEnabled());
+ return true;
+}
+
+static bool TrialInline(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ FrameIter iter(cx);
+ if (iter.done() || !iter.isBaseline() || iter.realm() != cx->realm()) {
+ return true;
+ }
+
+ jit::BaselineFrame* frame = iter.abstractFramePtr().asBaselineFrame();
+ if (!jit::CanIonCompileScript(cx, frame->script())) {
+ return true;
+ }
+
+ return jit::DoTrialInlining(cx, frame);
+}
+
+static bool ReturnStringCopy(JSContext* cx, CallArgs& args,
+ const char* message) {
+ JSString* str = JS_NewStringCopyZ(cx, message);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool MaybeGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JS_MaybeGC(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /*
+ * If the first argument is 'zone', we collect any zones previously
+ * scheduled for GC via schedulegc. If the first argument is an object, we
+ * collect the object's zone (and any other zones scheduled for
+ * GC). Otherwise, we collect all zones.
+ */
+ bool zone = false;
+ if (args.length() >= 1) {
+ Value arg = args[0];
+ if (arg.isString()) {
+ if (!JS_StringEqualsLiteral(cx, arg.toString(), "zone", &zone)) {
+ return false;
+ }
+ } else if (arg.isObject()) {
+ PrepareZoneForGC(cx, UncheckedUnwrap(&arg.toObject())->zone());
+ zone = true;
+ }
+ }
+
+ JS::GCOptions options = JS::GCOptions::Normal;
+ JS::GCReason reason = JS::GCReason::API;
+ if (args.length() >= 2) {
+ Value arg = args[1];
+ if (arg.isString()) {
+ bool shrinking = false;
+ bool last_ditch = false;
+ if (!JS_StringEqualsLiteral(cx, arg.toString(), "shrinking",
+ &shrinking)) {
+ return false;
+ }
+ if (!JS_StringEqualsLiteral(cx, arg.toString(), "last-ditch",
+ &last_ditch)) {
+ return false;
+ }
+ if (shrinking) {
+ options = JS::GCOptions::Shrink;
+ } else if (last_ditch) {
+ options = JS::GCOptions::Shrink;
+ reason = JS::GCReason::LAST_DITCH;
+ }
+ }
+ }
+
+ size_t preBytes = cx->runtime()->gc.heapSize.bytes();
+
+ if (zone) {
+ PrepareForDebugGC(cx->runtime());
+ } else {
+ JS::PrepareForFullGC(cx);
+ }
+
+ JS::NonIncrementalGC(cx, options, reason);
+
+ char buf[256] = {'\0'};
+ if (!js::SupportDifferentialTesting()) {
+ SprintfLiteral(buf, "before %zu, after %zu\n", preBytes,
+ cx->runtime()->gc.heapSize.bytes());
+ }
+ return ReturnStringCopy(cx, args, buf);
+}
+
+static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.get(0) == BooleanValue(true)) {
+ cx->runtime()->gc.storeBuffer().setAboutToOverflow(
+ JS::GCReason::FULL_GENERIC_BUFFER);
+ }
+
+ cx->minorGC(JS::GCReason::API);
+ args.rval().setUndefined();
+ return true;
+}
+
+#define PARAM_NAME_LIST_ENTRY(name, key, writable) " " name
+#define GC_PARAMETER_ARGS_LIST FOR_EACH_GC_PARAM(PARAM_NAME_LIST_ENTRY)
+
+static bool GCParameter(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JSString* str = ToString(cx, args.get(0));
+ if (!str) {
+ return false;
+ }
+
+ UniqueChars name = EncodeLatin1(cx, str);
+ if (!name) {
+ return false;
+ }
+
+ JSGCParamKey param;
+ bool writable;
+ if (!GetGCParameterInfo(name.get(), &param, &writable)) {
+ JS_ReportErrorASCII(
+ cx, "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
+ return false;
+ }
+
+ // Request mode.
+ if (args.length() == 1) {
+ uint32_t value = JS_GetGCParameter(cx, param);
+ args.rval().setNumber(value);
+ return true;
+ }
+
+ if (!writable) {
+ JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s",
+ name.get());
+ return false;
+ }
+
+ if (disableOOMFunctions) {
+ switch (param) {
+ case JSGC_MAX_BYTES:
+ case JSGC_MAX_NURSERY_BYTES:
+ args.rval().setUndefined();
+ return true;
+ default:
+ break;
+ }
+ }
+
+ double d;
+ if (!ToNumber(cx, args[1], &d)) {
+ return false;
+ }
+
+ if (d < 0 || d > UINT32_MAX) {
+ JS_ReportErrorASCII(cx, "Parameter value out of range");
+ return false;
+ }
+
+ uint32_t value = floor(d);
+ bool ok = cx->runtime()->gc.setParameter(cx, param, value);
+ if (!ok) {
+ JS_ReportErrorASCII(cx, "Parameter value out of range");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp) {
+ // Relazifying functions on GC is usually only done for compartments that are
+ // not active. To aid fuzzing, this testing function allows us to relazify
+ // even if the compartment is active.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Disable relazification of all scripts on stack. It is a pervasive
+ // assumption in the engine that running scripts still have bytecode.
+ for (AllScriptFramesIter i(cx); !i.done(); ++i) {
+ i.script()->clearAllowRelazify();
+ }
+
+ cx->runtime()->allowRelazificationForTesting = true;
+
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
+
+ cx->runtime()->allowRelazificationForTesting = false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool IsProxy(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "the function takes exactly one argument");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+ args.rval().setBoolean(args[0].toObject().is<ProxyObject>());
+ return true;
+}
+
+static bool WasmIsSupported(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::HasSupport(cx) &&
+ wasm::AnyCompilerAvailable(cx));
+ return true;
+}
+
+static bool WasmIsSupportedByHardware(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::HasPlatformSupport(cx));
+ return true;
+}
+
+static bool WasmDebuggingEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::HasSupport(cx) && wasm::BaselineAvailable(cx));
+ return true;
+}
+
+static bool WasmStreamingEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::StreamingCompilationAvailable(cx));
+ return true;
+}
+
+static bool WasmCachingEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::CodeCachingAvailable(cx));
+ return true;
+}
+
+static bool WasmHugeMemorySupported(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef WASM_SUPPORTS_HUGE_MEMORY
+ args.rval().setBoolean(true);
+#else
+ args.rval().setBoolean(false);
+#endif
+ return true;
+}
+
+static bool WasmMaxMemoryPages(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+ if (!args.get(0).isString()) {
+ JS_ReportErrorASCII(cx, "index type must be a string");
+ return false;
+ }
+ RootedString s(cx, args.get(0).toString());
+ Rooted<JSLinearString*> ls(cx, s->ensureLinear(cx));
+ if (!ls) {
+ return false;
+ }
+ if (StringEqualsLiteral(ls, "i32")) {
+ args.rval().setInt32(
+ int32_t(wasm::MaxMemoryPages(wasm::IndexType::I32).value()));
+ return true;
+ }
+ if (StringEqualsLiteral(ls, "i64")) {
+#ifdef ENABLE_WASM_MEMORY64
+ if (wasm::Memory64Available(cx)) {
+ args.rval().setInt32(
+ int32_t(wasm::MaxMemoryPages(wasm::IndexType::I64).value()));
+ return true;
+ }
+#endif
+ JS_ReportErrorASCII(cx, "memory64 not enabled");
+ return false;
+ }
+ JS_ReportErrorASCII(cx, "bad index type");
+ return false;
+}
+
+static bool WasmThreadsEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::ThreadsAvailable(cx));
+ return true;
+}
+
+#define WASM_FEATURE(NAME, ...) \
+ static bool Wasm##NAME##Enabled(JSContext* cx, unsigned argc, Value* vp) { \
+ CallArgs args = CallArgsFromVp(argc, vp); \
+ args.rval().setBoolean(wasm::NAME##Available(cx)); \
+ return true; \
+ }
+JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE);
+#undef WASM_FEATURE
+
+static bool WasmSimdEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(wasm::SimdAvailable(cx));
+ return true;
+}
+
+static bool WasmCompilersPresent(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ char buf[256];
+ *buf = 0;
+ if (wasm::BaselinePlatformSupport()) {
+ strcat(buf, "baseline");
+ }
+ if (wasm::IonPlatformSupport()) {
+ if (*buf) {
+ strcat(buf, ",");
+ }
+ strcat(buf, "ion");
+ }
+
+ JSString* result = JS_NewStringCopyZ(cx, buf);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setString(result);
+ return true;
+}
+
+static bool WasmCompileMode(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // This triplet of predicates will select zero or one baseline compiler and
+ // zero or one optimizing compiler.
+ bool baseline = wasm::BaselineAvailable(cx);
+ bool ion = wasm::IonAvailable(cx);
+ bool none = !baseline && !ion;
+ bool tiered = baseline && ion;
+
+ JSStringBuilder result(cx);
+ if (none && !result.append("none")) {
+ return false;
+ }
+ if (baseline && !result.append("baseline")) {
+ return false;
+ }
+ if (tiered && !result.append("+")) {
+ return false;
+ }
+ if (ion && !result.append("ion")) {
+ return false;
+ }
+ if (JSString* str = result.finishString()) {
+ args.rval().setString(str);
+ return true;
+ }
+ return false;
+}
+
+static bool WasmBaselineDisabledByFeatures(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ bool isDisabled = false;
+ JSStringBuilder reason(cx);
+ if (!wasm::BaselineDisabledByFeatures(cx, &isDisabled, &reason)) {
+ return false;
+ }
+ if (isDisabled) {
+ JSString* result = reason.finishString();
+ if (!result) {
+ return false;
+ }
+ args.rval().setString(result);
+ } else {
+ args.rval().setBoolean(false);
+ }
+ return true;
+}
+
+static bool WasmIonDisabledByFeatures(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ bool isDisabled = false;
+ JSStringBuilder reason(cx);
+ if (!wasm::IonDisabledByFeatures(cx, &isDisabled, &reason)) {
+ return false;
+ }
+ if (isDisabled) {
+ JSString* result = reason.finishString();
+ if (!result) {
+ return false;
+ }
+ args.rval().setString(result);
+ } else {
+ args.rval().setBoolean(false);
+ }
+ return true;
+}
+
+#ifdef ENABLE_WASM_SIMD
+# ifdef DEBUG
+static char lastAnalysisResult[1024];
+
+namespace js {
+namespace wasm {
+void ReportSimdAnalysis(const char* data) {
+ strncpy(lastAnalysisResult, data, sizeof(lastAnalysisResult));
+ lastAnalysisResult[sizeof(lastAnalysisResult) - 1] = 0;
+}
+} // namespace wasm
+} // namespace js
+
+// Unstable API for white-box testing of SIMD optimizations.
+//
+// Current API: takes no arguments, returns a string describing the last Simd
+// simplification applied.
+
+static bool WasmSimdAnalysis(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JSString* result =
+ JS_NewStringCopyZ(cx, *lastAnalysisResult ? lastAnalysisResult : "none");
+ if (!result) {
+ return false;
+ }
+ args.rval().setString(result);
+ *lastAnalysisResult = (char)0;
+ return true;
+}
+# endif
+#endif
+
+static bool WasmGlobalFromArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 2) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+
+ // Get the type of the value
+ wasm::ValType valType;
+ if (!wasm::ToValType(cx, args.get(0), &valType)) {
+ return false;
+ }
+
+ // Get the array buffer for the value
+ if (!args.get(1).isObject() ||
+ !args.get(1).toObject().is<ArrayBufferObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not an array buffer");
+ return false;
+ }
+ RootedArrayBufferObject buffer(
+ cx, &args.get(1).toObject().as<ArrayBufferObject>());
+
+ // Only allow POD to be created from bytes
+ switch (valType.kind()) {
+ case wasm::ValType::I32:
+ case wasm::ValType::I64:
+ case wasm::ValType::F32:
+ case wasm::ValType::F64:
+ case wasm::ValType::V128:
+ break;
+ default:
+ JS_ReportErrorASCII(
+ cx, "invalid valtype for creating WebAssembly.Global from bytes");
+ return false;
+ }
+
+ // Check we have all the bytes we need
+ if (valType.size() != buffer->byteLength()) {
+ JS_ReportErrorASCII(cx, "array buffer has incorrect size");
+ return false;
+ }
+
+ // Copy the bytes from buffer into a tagged val
+ wasm::RootedVal val(cx);
+ val.get().initFromRootedLocation(valType, buffer->dataPointer());
+
+ // Create the global object
+ RootedObject proto(
+ cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
+ if (!proto) {
+ return false;
+ }
+ Rooted<WasmGlobalObject*> result(
+ cx, WasmGlobalObject::create(cx, val, false, proto));
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result.get());
+ return true;
+}
+
+enum class LaneInterp {
+ I32x4,
+ I64x2,
+ F32x4,
+ F64x2,
+};
+
+size_t LaneInterpLanes(LaneInterp interp) {
+ switch (interp) {
+ case LaneInterp::I32x4:
+ return 4;
+ case LaneInterp::I64x2:
+ return 2;
+ case LaneInterp::F32x4:
+ return 4;
+ case LaneInterp::F64x2:
+ return 2;
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ return 0;
+ }
+}
+
+static bool ToLaneInterp(JSContext* cx, HandleValue v, LaneInterp* out) {
+ RootedString interpStr(cx, ToString(cx, v));
+ if (!interpStr) {
+ return false;
+ }
+ Rooted<JSLinearString*> interpLinearStr(cx, interpStr->ensureLinear(cx));
+ if (!interpLinearStr) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(interpLinearStr, "i32x4")) {
+ *out = LaneInterp::I32x4;
+ return true;
+ } else if (StringEqualsLiteral(interpLinearStr, "i64x2")) {
+ *out = LaneInterp::I64x2;
+ return true;
+ } else if (StringEqualsLiteral(interpLinearStr, "f32x4")) {
+ *out = LaneInterp::F32x4;
+ return true;
+ } else if (StringEqualsLiteral(interpLinearStr, "f64x2")) {
+ *out = LaneInterp::F64x2;
+ return true;
+ }
+
+ JS_ReportErrorASCII(cx, "invalid lane interpretation");
+ return false;
+}
+
+static bool WasmGlobalExtractLane(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 3) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+
+ // Get the global value
+ if (!args.get(0).isObject() ||
+ !args.get(0).toObject().is<WasmGlobalObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not wasm value");
+ return false;
+ }
+ Rooted<WasmGlobalObject*> global(
+ cx, &args.get(0).toObject().as<WasmGlobalObject>());
+
+ // Check that we have a v128 value
+ if (global->type().kind() != wasm::ValType::V128) {
+ JS_ReportErrorASCII(cx, "global is not a v128 value");
+ return false;
+ }
+ wasm::V128 v128 = global->val().get().v128();
+
+ // Get the passed interpretation of lanes
+ LaneInterp interp;
+ if (!ToLaneInterp(cx, args.get(1), &interp)) {
+ return false;
+ }
+
+ // Get the lane to extract
+ int32_t lane;
+ if (!ToInt32(cx, args.get(2), &lane)) {
+ return false;
+ }
+
+ // Check that the lane interp is valid
+ if (lane < 0 || size_t(lane) >= LaneInterpLanes(interp)) {
+ JS_ReportErrorASCII(cx, "invalid lane for interp");
+ return false;
+ }
+
+ wasm::RootedVal val(cx);
+ switch (interp) {
+ case LaneInterp::I32x4: {
+ uint32_t i;
+ v128.extractLane<uint32_t>(lane, &i);
+ val.set(wasm::Val(i));
+ break;
+ }
+ case LaneInterp::I64x2: {
+ uint64_t i;
+ v128.extractLane<uint64_t>(lane, &i);
+ val.set(wasm::Val(i));
+ break;
+ }
+ case LaneInterp::F32x4: {
+ float f;
+ v128.extractLane<float>(lane, &f);
+ val.set(wasm::Val(f));
+ break;
+ }
+ case LaneInterp::F64x2: {
+ double d;
+ v128.extractLane<double>(lane, &d);
+ val.set(wasm::Val(d));
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+
+ RootedObject proto(
+ cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
+ Rooted<WasmGlobalObject*> result(
+ cx, WasmGlobalObject::create(cx, val, false, proto));
+ args.rval().setObject(*result.get());
+ return true;
+}
+
+static bool WasmGlobalsEqual(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 2) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+
+ if (!args.get(0).isObject() ||
+ !args.get(0).toObject().is<WasmGlobalObject>() ||
+ !args.get(1).isObject() ||
+ !args.get(1).toObject().is<WasmGlobalObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not wasm value");
+ return false;
+ }
+
+ Rooted<WasmGlobalObject*> a(cx,
+ &args.get(0).toObject().as<WasmGlobalObject>());
+ Rooted<WasmGlobalObject*> b(cx,
+ &args.get(1).toObject().as<WasmGlobalObject>());
+
+ if (a->type() != b->type()) {
+ JS_ReportErrorASCII(cx, "globals are of different type");
+ return false;
+ }
+
+ bool result;
+ const wasm::Val& aVal = a->val().get();
+ const wasm::Val& bVal = b->val().get();
+ switch (a->type().kind()) {
+ case wasm::ValType::I32: {
+ result = aVal.i32() == bVal.i32();
+ break;
+ }
+ case wasm::ValType::I64: {
+ result = aVal.i64() == bVal.i64();
+ break;
+ }
+ case wasm::ValType::F32: {
+ result = mozilla::BitwiseCast<uint32_t>(aVal.f32()) ==
+ mozilla::BitwiseCast<uint32_t>(bVal.f32());
+ break;
+ }
+ case wasm::ValType::F64: {
+ result = mozilla::BitwiseCast<uint64_t>(aVal.f64()) ==
+ mozilla::BitwiseCast<uint64_t>(bVal.f64());
+ break;
+ }
+ case wasm::ValType::V128: {
+ // Don't know the interpretation of the v128, so we only can do an exact
+ // bitwise equality. Testing code can use wasmGlobalExtractLane to
+ // workaround this if needed.
+ result = aVal.v128() == bVal.v128();
+ break;
+ }
+ case wasm::ValType::Ref: {
+ result = aVal.ref() == bVal.ref();
+ break;
+ }
+ default:
+ JS_ReportErrorASCII(cx, "unsupported type");
+ return false;
+ }
+ args.rval().setBoolean(result);
+ return true;
+}
+
+// Flavors of NaN values for WebAssembly.
+// See
+// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
+enum class NaNFlavor {
+ // A canonical NaN value.
+ // - the sign bit is unspecified,
+ // - the 8-bit exponent is set to all 1s
+ // - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
+ Canonical,
+ // An arithmetic NaN. This is the same as a canonical NaN including that the
+ // payload MSB is set to 1, but one or more of the remaining payload bits MAY
+ // BE set to 1 (a canonical NaN specifies all 0s).
+ Arithmetic,
+};
+
+static bool IsNaNFlavor(uint32_t bits, NaNFlavor flavor) {
+ switch (flavor) {
+ case NaNFlavor::Canonical: {
+ return (bits & 0x7fffffff) == 0x7fc00000;
+ }
+ case NaNFlavor::Arithmetic: {
+ const uint32_t ArithmeticNaN = 0x7f800000;
+ const uint32_t ArithmeticPayloadMSB = 0x00400000;
+ bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
+ bool isMSBSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
+ return isNaN && isMSBSet;
+ }
+ default:
+ MOZ_CRASH();
+ }
+}
+
+static bool IsNaNFlavor(uint64_t bits, NaNFlavor flavor) {
+ switch (flavor) {
+ case NaNFlavor::Canonical: {
+ return (bits & 0x7fffffffffffffff) == 0x7ff8000000000000;
+ }
+ case NaNFlavor::Arithmetic: {
+ uint64_t ArithmeticNaN = 0x7ff0000000000000;
+ uint64_t ArithmeticPayloadMSB = 0x0008000000000000;
+ bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
+ bool isMsbSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
+ return isNaN && isMsbSet;
+ }
+ default:
+ MOZ_CRASH();
+ }
+}
+
+static bool ToNaNFlavor(JSContext* cx, HandleValue v, NaNFlavor* out) {
+ RootedString flavorStr(cx, ToString(cx, v));
+ if (!flavorStr) {
+ return false;
+ }
+ Rooted<JSLinearString*> flavorLinearStr(cx, flavorStr->ensureLinear(cx));
+ if (!flavorLinearStr) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(flavorLinearStr, "canonical_nan")) {
+ *out = NaNFlavor::Canonical;
+ return true;
+ } else if (StringEqualsLiteral(flavorLinearStr, "arithmetic_nan")) {
+ *out = NaNFlavor::Arithmetic;
+ return true;
+ }
+
+ JS_ReportErrorASCII(cx, "invalid nan flavor");
+ return false;
+}
+
+static bool WasmGlobalIsNaN(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 2) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+
+ if (!args.get(0).isObject() ||
+ !args.get(0).toObject().is<WasmGlobalObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not wasm value");
+ return false;
+ }
+ Rooted<WasmGlobalObject*> global(
+ cx, &args.get(0).toObject().as<WasmGlobalObject>());
+
+ NaNFlavor flavor;
+ if (!ToNaNFlavor(cx, args.get(1), &flavor)) {
+ return false;
+ }
+
+ bool result;
+ const wasm::Val& val = global->val().get();
+ switch (global->type().kind()) {
+ case wasm::ValType::F32: {
+ result = IsNaNFlavor(mozilla::BitwiseCast<uint32_t>(val.f32()), flavor);
+ break;
+ }
+ case wasm::ValType::F64: {
+ result = IsNaNFlavor(mozilla::BitwiseCast<uint64_t>(val.f64()), flavor);
+ break;
+ }
+ default:
+ JS_ReportErrorASCII(cx, "global is not a floating point value");
+ return false;
+ }
+ args.rval().setBoolean(result);
+ return true;
+}
+
+static bool WasmGlobalToString(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+ if (!args.get(0).isObject() ||
+ !args.get(0).toObject().is<WasmGlobalObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not wasm value");
+ return false;
+ }
+ Rooted<WasmGlobalObject*> global(
+ cx, &args.get(0).toObject().as<WasmGlobalObject>());
+ const wasm::Val& globalVal = global->val().get();
+
+ UniqueChars result;
+ switch (globalVal.type().kind()) {
+ case wasm::ValType::I32: {
+ result = JS_smprintf("i32:%" PRIx32, globalVal.i32());
+ break;
+ }
+ case wasm::ValType::I64: {
+ result = JS_smprintf("i64:%" PRIx64, globalVal.i64());
+ break;
+ }
+ case wasm::ValType::F32: {
+ result = JS_smprintf("f32:%f", globalVal.f32());
+ break;
+ }
+ case wasm::ValType::F64: {
+ result = JS_smprintf("f64:%lf", globalVal.f64());
+ break;
+ }
+ case wasm::ValType::V128: {
+ wasm::V128 v128 = globalVal.v128();
+ result = JS_smprintf(
+ "v128:%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", v128.bytes[0],
+ v128.bytes[1], v128.bytes[2], v128.bytes[3], v128.bytes[4],
+ v128.bytes[5], v128.bytes[6], v128.bytes[7], v128.bytes[8],
+ v128.bytes[9], v128.bytes[10], v128.bytes[11], v128.bytes[12],
+ v128.bytes[13], v128.bytes[14], v128.bytes[15]);
+ break;
+ }
+ case wasm::ValType::Ref: {
+ result = JS_smprintf("ref:%p", globalVal.ref().asJSObject());
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+
+ args.rval().setString(JS_NewStringCopyZ(cx, result.get()));
+ return true;
+}
+
+static bool WasmLosslessInvoke(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "not enough arguments");
+ return false;
+ }
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
+ if (!func || !wasm::IsWasmExportedFunction(func)) {
+ JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
+ return false;
+ }
+
+ // Get the instance and funcIndex for calling the function
+ wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
+ uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
+
+ // Set up a modified call frame following the standard JS
+ // [callee, this, arguments...] convention.
+ RootedValueVector wasmCallFrame(cx);
+ size_t len = 2 + args.length();
+ if (!wasmCallFrame.resize(len)) {
+ return false;
+ }
+ wasmCallFrame[0].set(args.calleev());
+ wasmCallFrame[1].set(args.thisv());
+ // Copy over the arguments needed to invoke the provided wasm function,
+ // skipping the wasm function we're calling that is at `args.get(0)`.
+ for (size_t i = 1; i < args.length(); i++) {
+ size_t wasmArg = i - 1;
+ wasmCallFrame[2 + wasmArg].set(args.get(i));
+ }
+ size_t wasmArgc = argc - 1;
+ CallArgs wasmCallArgs(CallArgsFromVp(wasmArgc, wasmCallFrame.begin()));
+
+ // Invoke the function with the new call frame
+ bool result = instance.callExport(cx, funcIndex, wasmCallArgs,
+ wasm::CoercionLevel::Lossless);
+ // Assign the wasm rval to our rval
+ args.rval().set(wasmCallArgs.rval());
+ return result;
+}
+
+static bool ConvertToTier(JSContext* cx, HandleValue value,
+ const wasm::Code& code, wasm::Tier* tier) {
+ RootedString option(cx, JS::ToString(cx, value));
+
+ if (!option) {
+ return false;
+ }
+
+ bool stableTier = false;
+ bool bestTier = false;
+ bool baselineTier = false;
+ bool ionTier = false;
+
+ if (!JS_StringEqualsLiteral(cx, option, "stable", &stableTier) ||
+ !JS_StringEqualsLiteral(cx, option, "best", &bestTier) ||
+ !JS_StringEqualsLiteral(cx, option, "baseline", &baselineTier) ||
+ !JS_StringEqualsLiteral(cx, option, "ion", &ionTier)) {
+ return false;
+ }
+
+ if (stableTier) {
+ *tier = code.stableTier();
+ } else if (bestTier) {
+ *tier = code.bestTier();
+ } else if (baselineTier) {
+ *tier = wasm::Tier::Baseline;
+ } else if (ionTier) {
+ *tier = wasm::Tier::Optimized;
+ } else {
+ // You can omit the argument but you can't pass just anything you like
+ return false;
+ }
+
+ return true;
+}
+
+static bool WasmExtractCode(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ Rooted<WasmModuleObject*> module(
+ cx, args[0].toObject().maybeUnwrapIf<WasmModuleObject>());
+ if (!module) {
+ JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
+ return false;
+ }
+
+ wasm::Tier tier = module->module().code().stableTier();
+ ;
+ if (args.length() > 1 &&
+ !ConvertToTier(cx, args[1], module->module().code(), &tier)) {
+ args.rval().setNull();
+ return false;
+ }
+
+ RootedValue result(cx);
+ if (!module->module().extractCode(cx, tier, &result)) {
+ return false;
+ }
+
+ args.rval().set(result);
+ return true;
+}
+
+struct DisasmBuffer {
+ JSStringBuilder builder;
+ bool oom;
+ explicit DisasmBuffer(JSContext* cx) : builder(cx), oom(false) {}
+};
+
+static bool HasDisassembler(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(jit::HasDisassembler());
+ return true;
+}
+
+MOZ_THREAD_LOCAL(DisasmBuffer*) disasmBuf;
+
+static void captureDisasmText(const char* text) {
+ DisasmBuffer* buf = disasmBuf.get();
+ if (!buf->builder.append(text, strlen(text)) || !buf->builder.append('\n')) {
+ buf->oom = true;
+ }
+}
+
+static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_MORE_ARGS_NEEDED, "disnative", "1", "",
+ "0");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument must be a function.");
+ return false;
+ }
+
+ Sprinter sprinter(cx);
+ if (!sprinter.init()) {
+ return false;
+ }
+
+ RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+
+ uint8_t* jit_begin = nullptr;
+ uint8_t* jit_end = nullptr;
+
+ if (fun->isAsmJSNative() || fun->isWasmWithJitEntry()) {
+ if (fun->isAsmJSNative() && !sprinter.jsprintf("; backend=asmjs\n")) {
+ return false;
+ }
+ if (!sprinter.jsprintf("; backend=wasm\n")) {
+ return false;
+ }
+
+ js::wasm::Instance& inst = fun->wasmInstance();
+ const js::wasm::Code& code = inst.code();
+ js::wasm::Tier tier = code.bestTier();
+
+ const js::wasm::MetadataTier& meta = inst.metadata(tier);
+
+ const js::wasm::CodeSegment& segment = code.segment(tier);
+ const uint32_t funcIndex = code.getFuncIndex(&*fun);
+ const js::wasm::FuncExport& func = meta.lookupFuncExport(funcIndex);
+ const js::wasm::CodeRange& codeRange = meta.codeRange(func);
+
+ jit_begin = segment.base() + codeRange.begin();
+ jit_end = segment.base() + codeRange.end();
+ } else if (fun->hasJitScript()) {
+ JSScript* script = fun->nonLazyScript();
+ if (script == nullptr) {
+ return false;
+ }
+
+ js::jit::IonScript* ion =
+ script->hasIonScript() ? script->ionScript() : nullptr;
+ js::jit::BaselineScript* baseline =
+ script->hasBaselineScript() ? script->baselineScript() : nullptr;
+ if (ion && ion->method()) {
+ if (!sprinter.jsprintf("; backend=ion\n")) {
+ return false;
+ }
+
+ jit_begin = ion->method()->raw();
+ jit_end = ion->method()->rawEnd();
+ } else if (baseline) {
+ if (!sprinter.jsprintf("; backend=baseline\n")) {
+ return false;
+ }
+
+ jit_begin = baseline->method()->raw();
+ jit_end = baseline->method()->rawEnd();
+ }
+ } else {
+ return false;
+ }
+
+ if (jit_begin == nullptr || jit_end == nullptr) {
+ return false;
+ }
+
+ DisasmBuffer buf(cx);
+ disasmBuf.set(&buf);
+ auto onFinish = mozilla::MakeScopeExit([&] { disasmBuf.set(nullptr); });
+
+ jit::Disassemble(jit_begin, jit_end - jit_begin, &captureDisasmText);
+
+ if (buf.oom) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ JSString* sresult = buf.builder.finishString();
+ if (!sresult) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ sprinter.putString(sresult);
+
+ if (args.length() > 1 && args[1].isString()) {
+ RootedString str(cx, args[1].toString());
+ JS::UniqueChars fileNameBytes = JS_EncodeStringToUTF8(cx, str);
+
+ const char* fileName = fileNameBytes.get();
+ if (!fileName) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ FILE* f = fopen(fileName, "w");
+ if (!f) {
+ JS_ReportErrorASCII(cx, "Could not open file for writing.");
+ return false;
+ }
+
+ uintptr_t expected_length = reinterpret_cast<uintptr_t>(jit_end) -
+ reinterpret_cast<uintptr_t>(jit_begin);
+ if (expected_length != fwrite(jit_begin, jit_end - jit_begin, 1, f)) {
+ JS_ReportErrorASCII(cx, "Did not write all function bytes to the file.");
+ fclose(f);
+ return false;
+ }
+ fclose(f);
+ }
+
+ JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
+ if (!str) {
+ return false;
+ }
+
+ args[0].setUndefined();
+ args.rval().setString(str);
+
+ return true;
+}
+
+static bool ComputeTier(JSContext* cx, const wasm::Code& code,
+ HandleValue tierSelection, wasm::Tier* tier) {
+ *tier = code.stableTier();
+ if (!tierSelection.isUndefined() &&
+ !ConvertToTier(cx, tierSelection, code, tier)) {
+ JS_ReportErrorASCII(cx, "invalid tier");
+ return false;
+ }
+
+ if (!code.hasTier(*tier)) {
+ JS_ReportErrorASCII(cx, "function missing selected tier");
+ return false;
+ }
+
+ return true;
+}
+
+template <typename DisasmFunction>
+static bool DisassembleIt(JSContext* cx, bool asString, MutableHandleValue rval,
+ DisasmFunction&& disassembleIt) {
+ if (asString) {
+ DisasmBuffer buf(cx);
+ disasmBuf.set(&buf);
+ auto onFinish = mozilla::MakeScopeExit([&] { disasmBuf.set(nullptr); });
+ disassembleIt(captureDisasmText);
+ if (buf.oom) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ JSString* sresult = buf.builder.finishString();
+ if (!sresult) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ rval.setString(sresult);
+ return true;
+ }
+
+ disassembleIt([](const char* text) { fprintf(stderr, "%s\n", text); });
+ return true;
+}
+
+static bool WasmDisassembleFunction(JSContext* cx, const HandleFunction& func,
+ HandleValue tierSelection, bool asString,
+ MutableHandleValue rval) {
+ wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
+ wasm::Tier tier;
+
+ if (!ComputeTier(cx, instance.code(), tierSelection, &tier)) {
+ return false;
+ }
+
+ uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
+ return DisassembleIt(
+ cx, asString, rval, [&](void (*captureText)(const char*)) {
+ instance.disassembleExport(cx, funcIndex, tier, captureText);
+ });
+}
+
+static bool WasmDisassembleCode(JSContext* cx, const wasm::Code& code,
+ HandleValue tierSelection, int kindSelection,
+ bool asString, MutableHandleValue rval) {
+ wasm::Tier tier;
+ if (!ComputeTier(cx, code, tierSelection, &tier)) {
+ return false;
+ }
+
+ return DisassembleIt(cx, asString, rval,
+ [&](void (*captureText)(const char*)) {
+ code.disassemble(cx, tier, kindSelection, captureText);
+ });
+}
+
+static bool WasmDisassemble(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ args.rval().set(UndefinedValue());
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ bool asString = false;
+ RootedValue tierSelection(cx);
+ int kindSelection = (1 << wasm::CodeRange::Function);
+ if (args.length() > 1 && args[1].isObject()) {
+ RootedObject options(cx, &args[1].toObject());
+ RootedValue val(cx);
+
+ if (!JS_GetProperty(cx, options, "asString", &val)) {
+ return false;
+ }
+ asString = val.isBoolean() && val.toBoolean();
+
+ if (!JS_GetProperty(cx, options, "tier", &tierSelection)) {
+ return false;
+ }
+
+ if (!JS_GetProperty(cx, options, "kinds", &val)) {
+ return false;
+ }
+ if (val.isString() && val.toString()->hasLatin1Chars()) {
+ AutoStableStringChars stable(cx);
+ if (!stable.init(cx, val.toString())) {
+ return false;
+ }
+ const char* p = (const char*)(stable.latin1Chars());
+ const char* end = p + val.toString()->length();
+ int selection = 0;
+ for (;;) {
+ if (strncmp(p, "Function", 8) == 0) {
+ selection |= (1 << wasm::CodeRange::Function);
+ p += 8;
+ } else if (strncmp(p, "InterpEntry", 11) == 0) {
+ selection |= (1 << wasm::CodeRange::InterpEntry);
+ p += 11;
+ } else if (strncmp(p, "JitEntry", 8) == 0) {
+ selection |= (1 << wasm::CodeRange::JitEntry);
+ p += 8;
+ } else if (strncmp(p, "ImportInterpExit", 16) == 0) {
+ selection |= (1 << wasm::CodeRange::ImportInterpExit);
+ p += 16;
+ } else if (strncmp(p, "ImportJitExit", 13) == 0) {
+ selection |= (1 << wasm::CodeRange::ImportJitExit);
+ p += 13;
+ } else if (strncmp(p, "all", 3) == 0) {
+ selection = ~0;
+ p += 3;
+ } else {
+ break;
+ }
+ if (p == end || *p != ',') {
+ break;
+ }
+ p++;
+ }
+ if (p == end) {
+ kindSelection = selection;
+ } else {
+ JS_ReportErrorASCII(cx, "argument object has invalid `kinds`");
+ return false;
+ }
+ }
+ }
+
+ RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
+ if (func && wasm::IsWasmExportedFunction(func)) {
+ return WasmDisassembleFunction(cx, func, tierSelection, asString,
+ args.rval());
+ }
+ if (args[0].toObject().is<WasmModuleObject>()) {
+ return WasmDisassembleCode(
+ cx, args[0].toObject().as<WasmModuleObject>().module().code(),
+ tierSelection, kindSelection, asString, args.rval());
+ }
+ if (args[0].toObject().is<WasmInstanceObject>()) {
+ return WasmDisassembleCode(
+ cx, args[0].toObject().as<WasmInstanceObject>().instance().code(),
+ tierSelection, kindSelection, asString, args.rval());
+ }
+ JS_ReportErrorASCII(
+ cx, "argument is not an exported wasm function or a wasm module");
+ return false;
+}
+
+enum class Flag { Tier2Complete, Deserialized };
+
+static bool WasmReturnFlag(JSContext* cx, unsigned argc, Value* vp, Flag flag) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ Rooted<WasmModuleObject*> module(
+ cx, args[0].toObject().maybeUnwrapIf<WasmModuleObject>());
+ if (!module) {
+ JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
+ return false;
+ }
+
+ bool b;
+ switch (flag) {
+ case Flag::Tier2Complete:
+ b = !module->module().testingTier2Active();
+ break;
+ case Flag::Deserialized:
+ b = module->module().loggingDeserialized();
+ break;
+ }
+
+ args.rval().set(BooleanValue(b));
+ return true;
+}
+
+static bool WasmHasTier2CompilationCompleted(JSContext* cx, unsigned argc,
+ Value* vp) {
+ return WasmReturnFlag(cx, argc, vp, Flag::Tier2Complete);
+}
+
+static bool WasmLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) {
+ return WasmReturnFlag(cx, argc, vp, Flag::Deserialized);
+}
+
+static bool WasmIntrinsicI8VecMul(JSContext* cx, unsigned argc, Value* vp) {
+ if (!wasm::HasSupport(cx)) {
+ JS_ReportErrorASCII(cx, "wasm support unavailable");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ wasm::IntrinsicId ids[] = {wasm::IntrinsicId::I8VecMul};
+ Rooted<WasmModuleObject*> module(cx);
+ if (!wasm::CompileIntrinsicModule(cx, ids, wasm::Shareable::False, &module)) {
+ return false;
+ }
+ args.rval().set(ObjectValue(*module.get()));
+ return true;
+}
+
+static bool LargeArrayBufferSupported(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(ArrayBufferObject::MaxByteLength >
+ ArrayBufferObject::MaxByteLengthForSmallBuffer);
+ return true;
+}
+
+static bool IsLazyFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ args.rval().setBoolean(fun->isInterpreted() && !fun->hasBytecode());
+ return true;
+}
+
+static bool IsRelazifiableFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ args.rval().setBoolean(fun->hasBytecode() &&
+ fun->nonLazyScript()->allowRelazify());
+ return true;
+}
+
+static bool IsInStencilCache(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+
+ if (fuzzingSafe) {
+ // When running code concurrently to fill-up the stencil cache, the content
+ // is not garanteed to be present.
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ BaseScript* script = fun->baseScript();
+ RefPtr<ScriptSource> ss = script->scriptSource();
+ StencilCache& cache = cx->runtime()->caches().delazificationCache;
+ auto guard = cache.isSourceCached(ss);
+ if (!guard) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ StencilContext key(ss, script->extent());
+ frontend::CompilationStencil* stencil = cache.lookup(guard, key);
+ args.rval().setBoolean(bool(stencil));
+ return true;
+}
+
+static bool WaitForStencilCache(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+ args.rval().setUndefined();
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ BaseScript* script = fun->baseScript();
+ RefPtr<ScriptSource> ss = script->scriptSource();
+ StencilCache& cache = cx->runtime()->caches().delazificationCache;
+ StencilContext key(ss, script->extent());
+
+ AutoLockHelperThreadState lock;
+ if (!HelperThreadState().isInitialized(lock)) {
+ return true;
+ }
+
+ while (true) {
+ {
+ // This capture a Mutex that we have to release before using the wait
+ // function.
+ auto guard = cache.isSourceCached(ss);
+ if (!guard) {
+ return true;
+ }
+
+ frontend::CompilationStencil* stencil = cache.lookup(guard, key);
+ if (stencil) {
+ break;
+ }
+ }
+
+ HelperThreadState().wait(lock);
+ }
+ return true;
+}
+
+static bool HasSameBytecodeData(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "The function takes exactly two argument.");
+ return false;
+ }
+
+ auto GetSharedData = [](JSContext* cx,
+ HandleValue v) -> SharedImmutableScriptData* {
+ if (!v.isObject()) {
+ JS_ReportErrorASCII(cx, "The arguments must be interpreted functions.");
+ return nullptr;
+ }
+
+ RootedObject obj(cx, CheckedUnwrapDynamic(&v.toObject(), cx));
+ if (!obj) {
+ return nullptr;
+ }
+
+ if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) {
+ JS_ReportErrorASCII(cx, "The arguments must be interpreted functions.");
+ return nullptr;
+ }
+
+ AutoRealm ar(cx, obj);
+ RootedFunction fun(cx, &obj->as<JSFunction>());
+ RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
+ if (!script) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(script->sharedData());
+ return script->sharedData();
+ };
+
+ // NOTE: We use RefPtr below to keep the data alive across possible GC since
+ // the functions may be in different Zones.
+
+ RefPtr<SharedImmutableScriptData> sharedData1 = GetSharedData(cx, args[0]);
+ if (!sharedData1) {
+ return false;
+ }
+
+ RefPtr<SharedImmutableScriptData> sharedData2 = GetSharedData(cx, args[1]);
+ if (!sharedData2) {
+ return false;
+ }
+
+ args.rval().setBoolean(sharedData1 == sharedData2);
+ return true;
+}
+
+static bool InternalConst(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ JS_ReportErrorASCII(cx, "the function takes exactly one argument");
+ return false;
+ }
+
+ JSString* str = ToString(cx, args[0]);
+ if (!str) {
+ return false;
+ }
+ JSLinearString* linear = JS_EnsureLinearString(cx, str);
+ if (!linear) {
+ return false;
+ }
+
+ if (JS_LinearStringEqualsLiteral(linear, "MARK_STACK_BASE_CAPACITY")) {
+ args.rval().setNumber(uint32_t(js::MARK_STACK_BASE_CAPACITY));
+ } else {
+ JS_ReportErrorASCII(cx, "unknown const name");
+ return false;
+ }
+ return true;
+}
+
+static bool GCPreserveCode(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setAlwaysPreserveCode();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef JS_GC_ZEAL
+
+static bool ParseGCZealMode(JSContext* cx, const CallArgs& args,
+ uint8_t* zeal) {
+ uint32_t value;
+ if (!ToUint32(cx, args.get(0), &value)) {
+ return false;
+ }
+
+ if (value > uint32_t(gc::ZealMode::Limit)) {
+ JS_ReportErrorASCII(cx, "gczeal argument out of range");
+ return false;
+ }
+
+ *zeal = static_cast<uint8_t>(value);
+ return true;
+}
+
+static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 2) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ uint8_t zeal;
+ if (!ParseGCZealMode(cx, args, &zeal)) {
+ return false;
+ }
+
+ uint32_t frequency = JS_DEFAULT_ZEAL_FREQ;
+ if (args.length() >= 2) {
+ if (!ToUint32(cx, args.get(1), &frequency)) {
+ return false;
+ }
+ }
+
+ JS_SetGCZeal(cx, zeal, frequency);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool UnsetGCZeal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ uint8_t zeal;
+ if (!ParseGCZealMode(cx, args, &zeal)) {
+ return false;
+ }
+
+ JS_UnsetGCZeal(cx, zeal);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool ScheduleGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ if (args.length() == 0) {
+ /* Fetch next zeal trigger only. */
+ } else if (args[0].isNumber()) {
+ /* Schedule a GC to happen after |arg| allocations. */
+ JS_ScheduleGC(cx, std::max(int(args[0].toNumber()), 0));
+ } else {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Bad argument - expecting number");
+ return false;
+ }
+
+ uint32_t zealBits;
+ uint32_t freq;
+ uint32_t next;
+ JS_GetGCZealBits(cx, &zealBits, &freq, &next);
+ args.rval().setInt32(next);
+ return true;
+}
+
+static bool SelectForGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ /*
+ * The selectedForMarking set is intended to be manually marked at slice
+ * start to detect missing pre-barriers. It is invalid for nursery things
+ * to be in the set, so evict the nursery before adding items.
+ */
+ cx->runtime()->gc.evictNursery();
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ if (args[i].isObject()) {
+ if (!cx->runtime()->gc.selectForMarking(&args[i].toObject())) {
+ return false;
+ }
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool VerifyPreBarriers(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ gc::VerifyBarriers(cx->runtime(), gc::PreBarrierVerifier);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool VerifyPostBarriers(JSContext* cx, unsigned argc, Value* vp) {
+ // This is a no-op since the post barrier verifier was removed.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool CurrentGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ RootedObject result(cx, JS_NewPlainObject(cx));
+ if (!result) {
+ return false;
+ }
+
+ js::gc::GCRuntime& gc = cx->runtime()->gc;
+ const char* state = StateName(gc.state());
+
+ RootedString str(cx, JS_NewStringCopyZ(cx, state));
+ if (!str) {
+ return false;
+ }
+ RootedValue val(cx, StringValue(str));
+ if (!JS_DefineProperty(cx, result, "incrementalState", val,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (gc.state() == js::gc::State::Sweep) {
+ val = Int32Value(gc.getCurrentSweepGroupIndex());
+ if (!JS_DefineProperty(cx, result, "sweepGroup", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ val = BooleanValue(gc.isIncrementalGCInProgress() && gc.isShrinkingGC());
+ if (!JS_DefineProperty(cx, result, "isShrinking", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ val = Int32Value(gc.gcNumber());
+ if (!JS_DefineProperty(cx, result, "number", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ val = Int32Value(gc.minorGCCount());
+ if (!JS_DefineProperty(cx, result, "minorCount", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ val = Int32Value(gc.majorGCCount());
+ if (!JS_DefineProperty(cx, result, "majorCount", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ val = BooleanValue(gc.isFullGc());
+ if (!JS_DefineProperty(cx, result, "isFull", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ val = BooleanValue(gc.isCompactingGc());
+ if (!JS_DefineProperty(cx, result, "isCompacting", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+# ifdef DEBUG
+ val = Int32Value(gc.testMarkQueuePos());
+ if (!JS_DefineProperty(cx, result, "queuePos", val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+# endif
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+static bool DeterministicGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setDeterministic(ToBoolean(args[0]));
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool DumpGCArenaInfo(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ js::gc::DumpArenaInfo();
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool SetMarkStackLimit(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ int32_t value;
+ if (!ToInt32(cx, args[0], &value) || value <= 0) {
+ JS_ReportErrorASCII(cx, "Bad argument to SetMarkStackLimit");
+ return false;
+ }
+
+ if (JS::IsIncrementalGCInProgress(cx)) {
+ JS_ReportErrorASCII(
+ cx, "Attempt to set markStackLimit while a GC is in progress");
+ return false;
+ }
+
+ JSRuntime* runtime = cx->runtime();
+ AutoLockGC lock(runtime);
+ runtime->gc.setMarkStackLimit(value, lock);
+ args.rval().setUndefined();
+ return true;
+}
+
+#endif /* JS_GC_ZEAL */
+
+static bool SetMallocMaxDirtyPageModifier(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ constexpr int32_t MinSupportedValue = -5;
+ constexpr int32_t MaxSupportedValue = 16;
+
+ int32_t value;
+ if (!ToInt32(cx, args[0], &value)) {
+ return false;
+ }
+ if (value < MinSupportedValue || value > MaxSupportedValue) {
+ JS_ReportErrorASCII(cx, "Bad argument to setMallocMaxDirtyPageModifier");
+ return false;
+ }
+
+ moz_set_max_dirty_page_modifier(value);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GCState(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ const char* state;
+
+ if (args.length() == 1) {
+ if (!args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+ JSObject* obj = UncheckedUnwrap(&args[0].toObject());
+ state = gc::StateName(obj->zone()->gcState());
+ } else {
+ state = gc::StateName(cx->runtime()->gc.state());
+ }
+
+ return ReturnStringCopy(cx, args, state);
+}
+
+static bool ScheduleZoneForGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expecting a single argument");
+ return false;
+ }
+
+ if (args[0].isObject()) {
+ // Ensure that |zone| is collected during the next GC.
+ Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone();
+ PrepareZoneForGC(cx, zone);
+ } else if (args[0].isString()) {
+ // This allows us to schedule the atoms zone for GC.
+ Zone* zone = args[0].toString()->zoneFromAnyThread();
+ if (!CurrentThreadCanAccessZone(zone)) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Specified zone not accessible for GC");
+ return false;
+ }
+ PrepareZoneForGC(cx, zone);
+ } else {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "Bad argument - expecting object or string");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool StartGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 2) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ auto budget = SliceBudget::unlimited();
+ if (args.length() >= 1) {
+ uint32_t work = 0;
+ if (!ToUint32(cx, args[0], &work)) {
+ return false;
+ }
+ budget = SliceBudget(WorkBudget(work));
+ }
+
+ bool shrinking = false;
+ if (args.length() >= 2) {
+ Value arg = args[1];
+ if (arg.isString()) {
+ if (!JS_StringEqualsLiteral(cx, arg.toString(), "shrinking",
+ &shrinking)) {
+ return false;
+ }
+ }
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->gc.isIncrementalGCInProgress()) {
+ RootedObject callee(cx, &args.callee());
+ JS_ReportErrorASCII(cx, "Incremental GC already in progress");
+ return false;
+ }
+
+ JS::GCOptions options =
+ shrinking ? JS::GCOptions::Shrink : JS::GCOptions::Normal;
+ rt->gc.startDebugGC(options, budget);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool FinishGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->gc.isIncrementalGCInProgress()) {
+ rt->gc.finishGC(JS::GCReason::DEBUG_GC);
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GCSlice(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 2) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ auto budget = SliceBudget::unlimited();
+ if (args.length() >= 1) {
+ uint32_t work = 0;
+ if (!ToUint32(cx, args[0], &work)) {
+ return false;
+ }
+ budget = SliceBudget(WorkBudget(work));
+ }
+
+ bool dontStart = false;
+ if (args.get(1).isObject()) {
+ RootedObject options(cx, &args[1].toObject());
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, options, "dontStart", &v)) {
+ return false;
+ }
+ dontStart = ToBoolean(v);
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->gc.isIncrementalGCInProgress()) {
+ rt->gc.debugGCSlice(budget);
+ } else if (!dontStart) {
+ rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool AbortGC(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ JS::AbortIncrementalGC(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool FullCompartmentChecks(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ cx->runtime()->gc.setFullCompartmentChecks(ToBoolean(args[0]));
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool NondeterministicGetWeakMapKeys(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "nondeterministicGetWeakMapKeys", "WeakMap",
+ InformalValueTypeName(args[0]));
+ return false;
+ }
+ RootedObject arr(cx);
+ RootedObject mapObj(cx, &args[0].toObject());
+ if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &arr)) {
+ return false;
+ }
+ if (!arr) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "nondeterministicGetWeakMapKeys", "WeakMap",
+ args[0].toObject().getClass()->name);
+ return false;
+ }
+ args.rval().setObject(*arr);
+ return true;
+}
+
+class HasChildTracer final : public JS::CallbackTracer {
+ RootedValue child_;
+ bool found_;
+
+ void onChild(JS::GCCellPtr thing, const char* name) override {
+ if (thing.asCell() == child_.toGCThing()) {
+ found_ = true;
+ }
+ }
+
+ public:
+ HasChildTracer(JSContext* cx, HandleValue child)
+ : JS::CallbackTracer(cx, JS::TracerKind::Callback,
+ JS::WeakMapTraceAction::TraceKeysAndValues),
+ child_(cx, child),
+ found_(false) {}
+
+ bool found() const { return found_; }
+};
+
+static bool HasChild(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedValue parent(cx, args.get(0));
+ RootedValue child(cx, args.get(1));
+
+ if (!parent.isGCThing() || !child.isGCThing()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ HasChildTracer trc(cx, child);
+ TraceChildren(&trc, JS::GCCellPtr(parent.toGCThing(), parent.traceKind()));
+ args.rval().setBoolean(trc.found());
+ return true;
+}
+
+static bool SetSavedStacksRNGState(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setSavedStacksRNGState", 1)) {
+ return false;
+ }
+
+ int32_t seed;
+ if (!ToInt32(cx, args[0], &seed)) {
+ return false;
+ }
+
+ // Either one or the other of the seed arguments must be non-zero;
+ // make this true no matter what value 'seed' has.
+ cx->realm()->savedStacks().setRNGState(seed, (seed + 1) * 33);
+ return true;
+}
+
+static bool GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setNumber(cx->realm()->savedStacks().count());
+ return true;
+}
+
+static bool ClearSavedFrames(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ js::SavedStacks& savedStacks = cx->realm()->savedStacks();
+ savedStacks.clear();
+
+ for (ActivationIterator iter(cx); !iter.done(); ++iter) {
+ iter->clearLiveSavedFrameCache();
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool SaveStack(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::StackCapture capture((JS::AllFrames()));
+ if (args.length() >= 1) {
+ double maxDouble;
+ if (!ToNumber(cx, args[0], &maxDouble)) {
+ return false;
+ }
+ if (std::isnan(maxDouble) || maxDouble < 0 || maxDouble > UINT32_MAX) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not a valid maximum frame count");
+ return false;
+ }
+ uint32_t max = uint32_t(maxDouble);
+ if (max > 0) {
+ capture = JS::StackCapture(JS::MaxFrames(max));
+ }
+ }
+
+ RootedObject compartmentObject(cx);
+ if (args.length() >= 2) {
+ if (!args[1].isObject()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not an object");
+ return false;
+ }
+ compartmentObject = UncheckedUnwrap(&args[1].toObject());
+ if (!compartmentObject) {
+ return false;
+ }
+ }
+
+ RootedObject stack(cx);
+ {
+ Maybe<AutoRealm> ar;
+ if (compartmentObject) {
+ ar.emplace(cx, compartmentObject);
+ }
+ if (!JS::CaptureCurrentStack(cx, &stack, std::move(capture))) {
+ return false;
+ }
+ }
+
+ if (stack && !cx->compartment()->wrap(cx, &stack)) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(stack);
+ return true;
+}
+
+static bool CaptureFirstSubsumedFrame(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "captureFirstSubsumedFrame", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "The argument must be an object");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ obj = CheckedUnwrapStatic(obj);
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Denied permission to object.");
+ return false;
+ }
+
+ JS::StackCapture capture(
+ JS::FirstSubsumedFrame(cx, obj->nonCCWRealm()->principals()));
+ if (args.length() > 1) {
+ capture.as<JS::FirstSubsumedFrame>().ignoreSelfHosted =
+ JS::ToBoolean(args[1]);
+ }
+
+ JS::RootedObject capturedStack(cx);
+ if (!JS::CaptureCurrentStack(cx, &capturedStack, std::move(capture))) {
+ return false;
+ }
+
+ args.rval().setObjectOrNull(capturedStack);
+ return true;
+}
+
+static bool CallFunctionFromNativeFrame(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
+ return false;
+ }
+ if (!args[0].isObject() || !IsCallable(args[0])) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+
+ RootedObject function(cx, &args[0].toObject());
+ return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
+ args.rval());
+}
+
+static bool CallFunctionWithAsyncStack(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 3) {
+ JS_ReportErrorASCII(cx, "The function takes exactly three arguments.");
+ return false;
+ }
+ if (!args[0].isObject() || !IsCallable(args[0])) {
+ JS_ReportErrorASCII(cx, "The first argument should be a function.");
+ return false;
+ }
+ if (!args[1].isObject() || !args[1].toObject().is<SavedFrame>()) {
+ JS_ReportErrorASCII(cx, "The second argument should be a SavedFrame.");
+ return false;
+ }
+ if (!args[2].isString() || args[2].toString()->empty()) {
+ JS_ReportErrorASCII(cx, "The third argument should be a non-empty string.");
+ return false;
+ }
+
+ RootedObject function(cx, &args[0].toObject());
+ RootedObject stack(cx, &args[1].toObject());
+ RootedString asyncCause(cx, args[2].toString());
+ UniqueChars utf8Cause = JS_EncodeStringToUTF8(cx, asyncCause);
+ if (!utf8Cause) {
+ MOZ_ASSERT(cx->isExceptionPending());
+ return false;
+ }
+
+ JS::AutoSetAsyncStackForNewCalls sas(
+ cx, stack, utf8Cause.get(),
+ JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
+ return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
+ args.rval());
+}
+
+static bool EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) {
+ SetAllocationMetadataBuilder(cx, &SavedStacks::metadataBuilder);
+ return true;
+}
+
+static bool DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) {
+ SetAllocationMetadataBuilder(cx, nullptr);
+ return true;
+}
+
+static bool SetTestFilenameValidationCallback(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Accept all filenames that start with "safe". In system code also accept
+ // filenames starting with "system".
+ auto testCb = [](JSContext* cx, const char* filename) -> bool {
+ if (strstr(filename, "safe") == filename) {
+ return true;
+ }
+ if (cx->realm()->isSystem() && strstr(filename, "system") == filename) {
+ return true;
+ }
+
+ return false;
+ };
+ JS::SetFilenameValidationCallback(testCb);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static JSAtom* GetPropertiesAddedName(JSContext* cx) {
+ const char* propName = "_propertiesAdded";
+ return Atomize(cx, propName, strlen(propName));
+}
+
+static bool NewObjectWithAddPropertyHook(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ auto addPropHook = [](JSContext* cx, HandleObject obj, HandleId id,
+ HandleValue v) -> bool {
+ Rooted<JSAtom*> propName(cx, GetPropertiesAddedName(cx));
+ if (!propName) {
+ return false;
+ }
+ // Don't do anything if we're adding the _propertiesAdded property.
+ RootedId propId(cx, AtomToId(propName));
+ if (id == propId) {
+ return true;
+ }
+ // Increment _propertiesAdded.
+ RootedValue val(cx);
+ if (!JS_GetPropertyById(cx, obj, propId, &val)) {
+ return false;
+ }
+ if (!val.isInt32() || val.toInt32() == INT32_MAX) {
+ return true;
+ }
+ val.setInt32(val.toInt32() + 1);
+ return JS_DefinePropertyById(cx, obj, propId, val, 0);
+ };
+
+ static const JSClassOps classOps = {
+ addPropHook, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+ };
+ static const JSClass cls = {
+ "ObjectWithAddPropHook",
+ 0,
+ &classOps,
+ };
+
+ RootedObject obj(cx, JS_NewObject(cx, &cls));
+ if (!obj) {
+ return false;
+ }
+
+ // Initialize _propertiesAdded to 0.
+ Rooted<JSAtom*> propName(cx, GetPropertiesAddedName(cx));
+ if (!propName) {
+ return false;
+ }
+ RootedId propId(cx, AtomToId(propName));
+ RootedValue val(cx, Int32Value(0));
+ if (!JS_DefinePropertyById(cx, obj, propId, val, 0)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool NewObjectWithCallHook(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ static auto hookShared = [](JSContext* cx, CallArgs& args) {
+ Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ // Define |this|. We can't expose the MagicValue to JS, so we use
+ // "<is_constructing>" in that case.
+ Rooted<Value> thisv(cx, args.thisv());
+ if (thisv.isMagic(JS_IS_CONSTRUCTING)) {
+ JSString* str = NewStringCopyZ<CanGC>(cx, "<is_constructing>");
+ if (!str) {
+ return false;
+ }
+ thisv.setString(str);
+ }
+ if (!DefineDataProperty(cx, obj, cx->names().this_, thisv,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Define |callee|.
+ if (!DefineDataProperty(cx, obj, cx->names().callee, args.calleev(),
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Define |arguments| array.
+ Rooted<ArrayObject*> arr(
+ cx, NewDenseCopiedArray(cx, args.length(), args.array()));
+ if (!arr) {
+ return false;
+ }
+ Rooted<Value> arrVal(cx, ObjectValue(*arr));
+ if (!DefineDataProperty(cx, obj, cx->names().arguments, arrVal,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ // Define |newTarget| if constructing.
+ if (args.isConstructing()) {
+ const char* propName = "newTarget";
+ Rooted<JSAtom*> name(cx, Atomize(cx, propName, strlen(propName)));
+ if (!name) {
+ return false;
+ }
+ Rooted<PropertyKey> key(cx, NameToId(name->asPropertyName()));
+ if (!DefineDataProperty(cx, obj, key, args.newTarget(),
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ };
+
+ static auto callHook = [](JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(!args.isConstructing());
+ return hookShared(cx, args);
+ };
+ static auto constructHook = [](JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.isConstructing());
+ return hookShared(cx, args);
+ };
+
+ static const JSClassOps classOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ nullptr, // finalize
+ callHook, // call
+ constructHook, // construct
+ nullptr, // trace
+ };
+ static const JSClass cls = {
+ "ObjectWithCallHook",
+ 0,
+ &classOps,
+ };
+
+ Rooted<JSObject*> obj(cx, JS_NewObject(cx, &cls));
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static constexpr JSClass ObjectWithManyReservedSlotsClass = {
+ "ObjectWithManyReservedSlots", JSCLASS_HAS_RESERVED_SLOTS(40)};
+
+static bool NewObjectWithManyReservedSlots(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ static constexpr size_t NumReservedSlots =
+ JSCLASS_RESERVED_SLOTS(&ObjectWithManyReservedSlotsClass);
+ static_assert(NumReservedSlots > NativeObject::MAX_FIXED_SLOTS);
+
+ RootedObject obj(cx, JS_NewObject(cx, &ObjectWithManyReservedSlotsClass));
+ if (!obj) {
+ return false;
+ }
+
+ for (size_t i = 0; i < NumReservedSlots; i++) {
+ JS_SetReservedSlot(obj, i, Int32Value(i));
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool CheckObjectWithManyReservedSlots(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isObject() ||
+ args[0].toObject().getClass() != &ObjectWithManyReservedSlotsClass) {
+ JS_ReportErrorASCII(cx,
+ "Expected object from newObjectWithManyReservedSlots");
+ return false;
+ }
+
+ JSObject* obj = &args[0].toObject();
+
+ static constexpr size_t NumReservedSlots =
+ JSCLASS_RESERVED_SLOTS(&ObjectWithManyReservedSlotsClass);
+
+ for (size_t i = 0; i < NumReservedSlots; i++) {
+ MOZ_RELEASE_ASSERT(JS::GetReservedSlot(obj, i).toInt32() == int32_t(i));
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetWatchtowerLog(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+
+ if (auto* log = cx->runtime()->watchtowerTestingLog.ref().get()) {
+ Rooted<JSObject*> elem(cx);
+ for (PlainObject* obj : *log) {
+ elem = obj;
+ if (!cx->compartment()->wrap(cx, &elem)) {
+ return false;
+ }
+ if (!values.append(ObjectValue(*elem))) {
+ return false;
+ }
+ }
+ log->clearAndFree();
+ }
+
+ ArrayObject* arr = NewDenseCopiedArray(cx, values.length(), values.begin());
+ if (!arr) {
+ return false;
+ }
+
+ args.rval().setObject(*arr);
+ return true;
+}
+
+static bool AddWatchtowerTarget(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "Expected a single object argument.");
+ return false;
+ }
+
+ if (!cx->runtime()->watchtowerTestingLog.ref()) {
+ auto vec = cx->make_unique<JSRuntime::RootedPlainObjVec>(cx);
+ if (!vec) {
+ return false;
+ }
+ cx->runtime()->watchtowerTestingLog = std::move(vec);
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ if (!JSObject::setUseWatchtowerTestingLog(cx, obj)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+struct TestExternalString : public JSExternalStringCallbacks {
+ void finalize(char16_t* chars) const override { js_free(chars); }
+ size_t sizeOfBuffer(const char16_t* chars,
+ mozilla::MallocSizeOf mallocSizeOf) const override {
+ return mallocSizeOf(chars);
+ }
+};
+
+static constexpr TestExternalString TestExternalStringCallbacks;
+
+static bool NewString(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedString src(cx, ToString(cx, args.get(0)));
+ if (!src) {
+ return false;
+ }
+
+ gc::Heap heap = gc::Heap::Default;
+ bool wantTwoByte = false;
+ bool forceExternal = false;
+ bool maybeExternal = false;
+ uint32_t capacity = 0;
+
+ if (args.get(1).isObject()) {
+ RootedObject options(cx, &args[1].toObject());
+ RootedValue v(cx);
+ bool requestTenured = false;
+ struct BoolSetting {
+ const char* name;
+ bool* value;
+ };
+ for (auto [name, setting] :
+ {BoolSetting{"tenured", &requestTenured},
+ BoolSetting{"twoByte", &wantTwoByte},
+ BoolSetting{"external", &forceExternal},
+ BoolSetting{"maybeExternal", &maybeExternal}}) {
+ if (!JS_GetProperty(cx, options, name, &v)) {
+ return false;
+ }
+ *setting = ToBoolean(v); // false if not given (or otherwise undefined)
+ }
+ struct Uint32Setting {
+ const char* name;
+ uint32_t* value;
+ };
+ for (auto [name, setting] : {Uint32Setting{"capacity", &capacity}}) {
+ if (!JS_GetProperty(cx, options, name, &v)) {
+ return false;
+ }
+ int32_t i32;
+ if (!ToInt32(cx, v, &i32)) {
+ return false;
+ }
+ if (i32 < 0) {
+ JS_ReportErrorASCII(cx, "nonnegative value required");
+ return false;
+ }
+ *setting = static_cast<uint32_t>(i32);
+ }
+
+ heap = requestTenured ? gc::Heap::Tenured : gc::Heap::Default;
+ if (forceExternal || maybeExternal) {
+ wantTwoByte = true;
+ if (capacity != 0) {
+ JS_ReportErrorASCII(cx,
+ "strings cannot be both external and extensible");
+ return false;
+ }
+ }
+ }
+
+ auto len = src->length();
+ RootedString dest(cx);
+
+ if (forceExternal || maybeExternal) {
+ auto buf = cx->make_pod_array<char16_t>(len);
+ if (!buf) {
+ return false;
+ }
+
+ if (!JS_CopyStringChars(cx, mozilla::Range<char16_t>(buf.get(), len),
+ src)) {
+ return false;
+ }
+
+ bool isExternal = true;
+ if (forceExternal) {
+ dest = JSExternalString::new_(cx, buf.get(), len,
+ &TestExternalStringCallbacks);
+ } else {
+ dest = NewMaybeExternalString(
+ cx, buf.get(), len, &TestExternalStringCallbacks, &isExternal, heap);
+ }
+ if (dest && isExternal) {
+ (void)buf.release(); // Ownership was transferred.
+ }
+ } else {
+ AutoStableStringChars stable(cx);
+ if (!wantTwoByte && src->hasLatin1Chars()) {
+ if (!stable.init(cx, src)) {
+ return false;
+ }
+ } else {
+ if (!stable.initTwoByte(cx, src)) {
+ return false;
+ }
+ }
+ if (capacity) {
+ if (capacity < len) {
+ capacity = len;
+ }
+ if (len == 0) {
+ JS_ReportErrorASCII(cx, "Cannot set capacity of empty string");
+ return false;
+ }
+ if (stable.isLatin1()) {
+ auto news = cx->make_pod_arena_array<JS::Latin1Char>(
+ js::StringBufferArena, capacity);
+ if (!news) {
+ return false;
+ }
+ mozilla::PodCopy(news.get(), stable.latin1Chars(), len);
+ dest = JSLinearString::newValidLength<CanGC>(cx, std::move(news), len,
+ heap);
+ } else {
+ auto news =
+ cx->make_pod_arena_array<char16_t>(js::StringBufferArena, capacity);
+ if (!news) {
+ return false;
+ }
+ mozilla::PodCopy(news.get(), stable.twoByteChars(), len);
+ dest = JSLinearString::newValidLength<CanGC>(cx, std::move(news), len,
+ heap);
+ }
+ if (dest) {
+ dest->asLinear().makeExtensible(capacity);
+ }
+ } else if (wantTwoByte) {
+ dest = NewStringCopyNDontDeflate<CanGC>(cx, stable.twoByteChars(), len,
+ heap);
+ } else if (stable.isLatin1()) {
+ dest = NewStringCopyN<CanGC>(cx, stable.latin1Chars(), len, heap);
+ } else {
+ // Normal behavior: auto-deflate to latin1 if possible.
+ dest = NewStringCopyN<CanGC>(cx, stable.twoByteChars(), len, heap);
+ }
+ }
+
+ if (!dest) {
+ return false;
+ }
+
+ args.rval().setString(dest);
+ return true;
+}
+
+// Warning! This will let you create ropes that I'm not sure would be possible
+// otherwise, specifically:
+//
+// - a rope with a zero-length child
+// - a rope that would fit into an inline string
+//
+static bool NewRope(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isString() || !args.get(1).isString()) {
+ JS_ReportErrorASCII(cx, "newRope requires two string arguments.");
+ return false;
+ }
+
+ gc::Heap heap = js::gc::Heap::Default;
+ if (args.get(2).isObject()) {
+ RootedObject options(cx, &args[2].toObject());
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, options, "nursery", &v)) {
+ return false;
+ }
+ if (!v.isUndefined() && !ToBoolean(v)) {
+ heap = js::gc::Heap::Tenured;
+ }
+ }
+
+ RootedString left(cx, args[0].toString());
+ RootedString right(cx, args[1].toString());
+ size_t length = JS_GetStringLength(left) + JS_GetStringLength(right);
+ if (length > JSString::MAX_LENGTH) {
+ JS_ReportErrorASCII(cx, "rope length exceeds maximum string length");
+ return false;
+ }
+
+ // Disallow creating ropes where one side is empty.
+ if (left->empty() || right->empty()) {
+ JS_ReportErrorASCII(cx, "rope child mustn't be the empty string");
+ return false;
+ }
+
+ auto* str = JSRope::new_<CanGC>(cx, left, right, length, heap);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool IsRope(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isString()) {
+ JS_ReportErrorASCII(cx, "isRope requires a string argument.");
+ return false;
+ }
+
+ JSString* str = args[0].toString();
+ args.rval().setBoolean(str->isRope());
+ return true;
+}
+
+static bool EnsureLinearString(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorASCII(
+ cx, "ensureLinearString takes exactly one string argument.");
+ return false;
+ }
+
+ JSLinearString* linear = args[0].toString()->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ args.rval().setString(linear);
+ return true;
+}
+
+static bool RepresentativeStringArray(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject array(cx, JS::NewArrayObject(cx, 0));
+ if (!array) {
+ return false;
+ }
+
+ if (!JSString::fillWithRepresentatives(cx, array.as<ArrayObject>())) {
+ return false;
+ }
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
+static bool OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(js::THREAD_TYPE_MAX);
+ return true;
+}
+
+static bool CheckCanSimulateOOM(JSContext* cx) {
+ if (js::oom::GetThreadType() != js::THREAD_TYPE_MAIN) {
+ JS_ReportErrorASCII(
+ cx, "Simulated OOM failure is only supported on the main thread");
+ return false;
+ }
+
+ return true;
+}
+
+static bool SetupOOMFailure(JSContext* cx, bool failAlways, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (disableOOMFunctions) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "Count argument required");
+ return false;
+ }
+
+ if (args.length() > 2) {
+ JS_ReportErrorASCII(cx, "Too many arguments");
+ return false;
+ }
+
+ int32_t count;
+ if (!JS::ToInt32(cx, args.get(0), &count)) {
+ return false;
+ }
+
+ if (count <= 0) {
+ JS_ReportErrorASCII(cx, "OOM cutoff should be positive");
+ return false;
+ }
+
+ uint32_t targetThread = js::THREAD_TYPE_MAIN;
+ if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread)) {
+ return false;
+ }
+
+ if (targetThread == js::THREAD_TYPE_NONE ||
+ targetThread == js::THREAD_TYPE_WORKER ||
+ targetThread >= js::THREAD_TYPE_MAX) {
+ JS_ReportErrorASCII(cx, "Invalid thread type specified");
+ return false;
+ }
+
+ if (!CheckCanSimulateOOM(cx)) {
+ return false;
+ }
+
+ js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM,
+ count, targetThread, failAlways);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp) {
+ return SetupOOMFailure(cx, true, argc, vp);
+}
+
+static bool OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp) {
+ return SetupOOMFailure(cx, false, argc, vp);
+}
+
+static bool ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!CheckCanSimulateOOM(cx)) {
+ return false;
+ }
+
+ args.rval().setBoolean(js::oom::HadSimulatedOOM());
+ js::oom::simulator.reset();
+ return true;
+}
+
+static size_t CountCompartments(JSContext* cx) {
+ size_t count = 0;
+ for (auto zone : cx->runtime()->gc.zones()) {
+ count += zone->compartments().length();
+ }
+ return count;
+}
+
+// Iterative failure testing: test a function by simulating failures at indexed
+// locations throughout the normal execution path and checking that the
+// resulting state of the environment is consistent with the error result.
+//
+// For example, trigger OOM at every allocation point and test that the function
+// either recovers and succeeds or raises an exception and fails.
+
+class MOZ_STACK_CLASS IterativeFailureTest {
+ public:
+ struct FailureSimulator {
+ virtual void setup(JSContext* cx) {}
+ virtual void teardown(JSContext* cx) {}
+ virtual void startSimulating(JSContext* cx, unsigned iteration,
+ unsigned thread, bool keepFailing) = 0;
+ virtual bool stopSimulating() = 0;
+ virtual void cleanup(JSContext* cx) {}
+ };
+
+ IterativeFailureTest(JSContext* cx, FailureSimulator& simulator);
+ bool initParams(const CallArgs& args);
+ bool test();
+
+ private:
+ bool setup();
+ bool testThread(unsigned thread);
+ bool testIteration(unsigned thread, unsigned iteration,
+ bool& failureWasSimulated, MutableHandleValue exception);
+ void cleanup();
+ void teardown();
+
+ JSContext* const cx;
+ FailureSimulator& simulator;
+ size_t compartmentCount;
+
+ // Test parameters set by initParams.
+ RootedFunction testFunction;
+ unsigned threadStart = 0;
+ unsigned threadEnd = 0;
+ bool expectExceptionOnFailure = true;
+ bool keepFailing = false;
+ bool verbose = false;
+};
+
+bool RunIterativeFailureTest(
+ JSContext* cx, const CallArgs& args,
+ IterativeFailureTest::FailureSimulator& simulator) {
+ IterativeFailureTest test(cx, simulator);
+ return test.initParams(args) && test.test();
+}
+
+IterativeFailureTest::IterativeFailureTest(JSContext* cx,
+ FailureSimulator& simulator)
+ : cx(cx), simulator(simulator), testFunction(cx) {}
+
+bool IterativeFailureTest::test() {
+ if (disableOOMFunctions) {
+ return true;
+ }
+
+ if (!setup()) {
+ return false;
+ }
+
+ auto onExit = mozilla::MakeScopeExit([this] { teardown(); });
+
+ for (unsigned thread = threadStart; thread <= threadEnd; thread++) {
+ if (!testThread(thread)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IterativeFailureTest::setup() {
+ if (!CheckCanSimulateOOM(cx)) {
+ return false;
+ }
+
+ // Disallow nested tests.
+ if (cx->runningOOMTest) {
+ JS_ReportErrorASCII(
+ cx, "Nested call to iterative failure test is not allowed.");
+ return false;
+ }
+ cx->runningOOMTest = true;
+
+ MOZ_ASSERT(!cx->isExceptionPending());
+
+# ifdef JS_GC_ZEAL
+ JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
+# endif
+
+ // Delazify the function here if necessary so we don't end up testing that.
+ if (testFunction->isInterpreted() &&
+ !JSFunction::getOrCreateScript(cx, testFunction)) {
+ return false;
+ }
+
+ compartmentCount = CountCompartments(cx);
+
+ simulator.setup(cx);
+
+ return true;
+}
+
+bool IterativeFailureTest::testThread(unsigned thread) {
+ if (verbose) {
+ fprintf(stderr, "thread %u\n", thread);
+ }
+
+ RootedValue exception(cx);
+
+ unsigned iteration = 1;
+ bool failureWasSimulated;
+ do {
+ if (!testIteration(thread, iteration, failureWasSimulated, &exception)) {
+ return false;
+ }
+
+ iteration++;
+ } while (failureWasSimulated);
+
+ if (verbose) {
+ fprintf(stderr, " finished after %u iterations\n", iteration - 1);
+ if (!exception.isUndefined()) {
+ RootedString str(cx, JS::ToString(cx, exception));
+ if (!str) {
+ fprintf(stderr, " error while trying to print exception, giving up\n");
+ return false;
+ }
+ UniqueChars bytes(JS_EncodeStringToUTF8(cx, str));
+ if (!bytes) {
+ return false;
+ }
+ fprintf(stderr, " threw %s\n", bytes.get());
+ }
+ }
+
+ return true;
+}
+
+bool IterativeFailureTest::testIteration(unsigned thread, unsigned iteration,
+ bool& failureWasSimulated,
+ MutableHandleValue exception) {
+ if (verbose) {
+ fprintf(stderr, " iteration %u\n", iteration);
+ }
+
+ MOZ_RELEASE_ASSERT(!cx->isExceptionPending());
+
+ simulator.startSimulating(cx, iteration, thread, keepFailing);
+
+ RootedValue result(cx);
+ bool ok = JS_CallFunction(cx, cx->global(), testFunction,
+ HandleValueArray::empty(), &result);
+
+ failureWasSimulated = simulator.stopSimulating();
+
+ if (ok && cx->isExceptionPending()) {
+ MOZ_CRASH(
+ "Thunk execution succeeded but an exception was raised - missing error "
+ "check?");
+ }
+
+ if (!ok && !cx->isExceptionPending() && expectExceptionOnFailure) {
+ MOZ_CRASH(
+ "Thunk execution failed but no exception was raised - missing call to "
+ "js::ReportOutOfMemory()?");
+ }
+
+ // Note that it is possible that the function throws an exception unconnected
+ // to the simulated failure, in which case we ignore it. More correct would be
+ // to have the caller pass some kind of exception specification and to check
+ // the exception against it.
+ if (!failureWasSimulated && cx->isExceptionPending()) {
+ if (!cx->getPendingException(exception)) {
+ return false;
+ }
+ }
+ cx->clearPendingException();
+
+ cleanup();
+
+ return true;
+}
+
+void IterativeFailureTest::cleanup() {
+ simulator.cleanup(cx);
+
+ gc::FinishGC(cx);
+
+ // Some tests create a new compartment or zone on every iteration. Our GC is
+ // triggered by GC allocations and not by number of compartments or zones, so
+ // these won't normally get cleaned up. The check here stops some tests
+ // running out of memory. ("Gentlemen, you can't fight in here! This is the
+ // War oom!")
+ if (CountCompartments(cx) > compartmentCount + 100) {
+ JS_GC(cx);
+ compartmentCount = CountCompartments(cx);
+ }
+}
+
+void IterativeFailureTest::teardown() {
+ simulator.teardown(cx);
+
+ cx->runningOOMTest = false;
+}
+
+bool IterativeFailureTest::initParams(const CallArgs& args) {
+ if (args.length() < 1 || args.length() > 2) {
+ JS_ReportErrorASCII(cx, "function takes between 1 and 2 arguments.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument must be the function to test.");
+ return false;
+ }
+ testFunction = &args[0].toObject().as<JSFunction>();
+
+ if (args.length() == 2) {
+ if (args[1].isBoolean()) {
+ expectExceptionOnFailure = args[1].toBoolean();
+ } else if (args[1].isObject()) {
+ RootedObject options(cx, &args[1].toObject());
+ RootedValue value(cx);
+
+ if (!JS_GetProperty(cx, options, "expectExceptionOnFailure", &value)) {
+ return false;
+ }
+ if (!value.isUndefined()) {
+ expectExceptionOnFailure = ToBoolean(value);
+ }
+
+ if (!JS_GetProperty(cx, options, "keepFailing", &value)) {
+ return false;
+ }
+ if (!value.isUndefined()) {
+ keepFailing = ToBoolean(value);
+ }
+ } else {
+ JS_ReportErrorASCII(
+ cx, "The optional second argument must be an object or a boolean.");
+ return false;
+ }
+ }
+
+ // There are some places where we do fail without raising an exception, so
+ // we can't expose this to the fuzzers by default.
+ if (fuzzingSafe) {
+ expectExceptionOnFailure = false;
+ }
+
+ // Test all threads by default except worker threads.
+ threadStart = oom::FirstThreadTypeToTest;
+ threadEnd = oom::LastThreadTypeToTest;
+
+ // Test a single thread type if specified by the OOM_THREAD environment
+ // variable.
+ int threadOption = 0;
+ if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
+ if (threadOption < oom::FirstThreadTypeToTest ||
+ threadOption > oom::LastThreadTypeToTest) {
+ JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
+ return false;
+ }
+
+ threadStart = threadOption;
+ threadEnd = threadOption;
+ }
+
+ verbose = EnvVarIsDefined("OOM_VERBOSE");
+
+ return true;
+}
+
+struct OOMSimulator : public IterativeFailureTest::FailureSimulator {
+ void setup(JSContext* cx) override { cx->runtime()->hadOutOfMemory = false; }
+
+ void startSimulating(JSContext* cx, unsigned i, unsigned thread,
+ bool keepFailing) override {
+ MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
+ js::oom::simulator.simulateFailureAfter(
+ js::oom::FailureSimulator::Kind::OOM, i, thread, keepFailing);
+ }
+
+ bool stopSimulating() override {
+ bool handledOOM = js::oom::HadSimulatedOOM();
+ js::oom::simulator.reset();
+ return handledOOM;
+ }
+
+ void cleanup(JSContext* cx) override {
+ cx->runtime()->hadOutOfMemory = false;
+ }
+};
+
+static bool OOMTest(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ OOMSimulator simulator;
+ if (!RunIterativeFailureTest(cx, args, simulator)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+struct StackOOMSimulator : public IterativeFailureTest::FailureSimulator {
+ void startSimulating(JSContext* cx, unsigned i, unsigned thread,
+ bool keepFailing) override {
+ js::oom::simulator.simulateFailureAfter(
+ js::oom::FailureSimulator::Kind::StackOOM, i, thread, keepFailing);
+ }
+
+ bool stopSimulating() override {
+ bool handledOOM = js::oom::HadSimulatedStackOOM();
+ js::oom::simulator.reset();
+ return handledOOM;
+ }
+};
+
+static bool StackTest(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ StackOOMSimulator simulator;
+ if (!RunIterativeFailureTest(cx, args, simulator)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+struct FailingIterruptSimulator
+ : public IterativeFailureTest::FailureSimulator {
+ JSInterruptCallback* prevEnd = nullptr;
+
+ static bool failingInterruptCallback(JSContext* cx) { return false; }
+
+ void setup(JSContext* cx) override {
+ prevEnd = cx->interruptCallbacks().end();
+ JS_AddInterruptCallback(cx, failingInterruptCallback);
+ }
+
+ void teardown(JSContext* cx) override {
+ cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
+ }
+
+ void startSimulating(JSContext* cx, unsigned i, unsigned thread,
+ bool keepFailing) override {
+ js::oom::simulator.simulateFailureAfter(
+ js::oom::FailureSimulator::Kind::Interrupt, i, thread, keepFailing);
+ }
+
+ bool stopSimulating() override {
+ bool handledInterrupt = js::oom::HadSimulatedInterrupt();
+ js::oom::simulator.reset();
+ return handledInterrupt;
+ }
+};
+
+static bool InterruptTest(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ FailingIterruptSimulator simulator;
+ if (!RunIterativeFailureTest(cx, args, simulator)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
+static bool SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "settlePromiseNow", 1)) {
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<PromiseObject>()) {
+ JS_ReportErrorASCII(cx, "first argument must be a Promise object");
+ return false;
+ }
+
+ Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
+ if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
+ JS_ReportErrorASCII(
+ cx, "async function/generator's promise shouldn't be manually settled");
+ return false;
+ }
+
+ if (promise->state() != JS::PromiseState::Pending) {
+ JS_ReportErrorASCII(cx, "cannot settle an already-resolved promise");
+ return false;
+ }
+
+ if (IsPromiseWithDefaultResolvingFunction(promise)) {
+ SetAlreadyResolvedPromiseWithDefaultResolvingFunction(promise);
+ }
+
+ int32_t flags = promise->flags();
+ promise->setFixedSlot(
+ PromiseSlot_Flags,
+ Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
+
+ DebugAPI::onPromiseSettled(cx, promise);
+ return true;
+}
+
+static bool GetWaitForAllPromise(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getWaitForAllPromise", 1)) {
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<ArrayObject>() ||
+ args[0].toObject().as<NativeObject>().isIndexed()) {
+ JS_ReportErrorASCII(
+ cx, "first argument must be a dense Array of Promise objects");
+ return false;
+ }
+ Rooted<NativeObject*> list(cx, &args[0].toObject().as<NativeObject>());
+ RootedObjectVector promises(cx);
+ uint32_t count = list->getDenseInitializedLength();
+ if (!promises.resize(count)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ RootedValue elem(cx, list->getDenseElement(i));
+ if (!elem.isObject() || !elem.toObject().is<PromiseObject>()) {
+ JS_ReportErrorASCII(
+ cx, "Each entry in the passed-in Array must be a Promise");
+ return false;
+ }
+ promises[i].set(&elem.toObject());
+ }
+
+ RootedObject resultPromise(cx, JS::GetWaitForAllPromise(cx, promises));
+ if (!resultPromise) {
+ return false;
+ }
+
+ args.rval().set(ObjectValue(*resultPromise));
+ return true;
+}
+
+static bool ResolvePromise(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "resolvePromise", 2)) {
+ return false;
+ }
+ if (!args[0].isObject() ||
+ !UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
+ JS_ReportErrorASCII(
+ cx, "first argument must be a maybe-wrapped Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ RootedValue resolution(cx, args[1]);
+ mozilla::Maybe<AutoRealm> ar;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ar.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &resolution)) {
+ return false;
+ }
+ }
+
+ if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
+ JS_ReportErrorASCII(
+ cx,
+ "async function/generator's promise shouldn't be manually resolved");
+ return false;
+ }
+
+ bool result = JS::ResolvePromise(cx, promise, resolution);
+ if (result) {
+ args.rval().setUndefined();
+ }
+ return result;
+}
+
+static bool RejectPromise(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "rejectPromise", 2)) {
+ return false;
+ }
+ if (!args[0].isObject() ||
+ !UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
+ JS_ReportErrorASCII(
+ cx, "first argument must be a maybe-wrapped Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ RootedValue reason(cx, args[1]);
+ mozilla::Maybe<AutoRealm> ar;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ar.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &reason)) {
+ return false;
+ }
+ }
+
+ if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
+ JS_ReportErrorASCII(
+ cx,
+ "async function/generator's promise shouldn't be manually rejected");
+ return false;
+ }
+
+ bool result = JS::RejectPromise(cx, promise, reason);
+ if (result) {
+ args.rval().setUndefined();
+ }
+ return result;
+}
+
+static unsigned finalizeCount = 0;
+
+static void finalize_counter_finalize(JS::GCContext* gcx, JSObject* obj) {
+ ++finalizeCount;
+}
+
+static const JSClassOps FinalizeCounterClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ finalize_counter_finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+static const JSClass FinalizeCounterClass = {
+ "FinalizeCounter", JSCLASS_FOREGROUND_FINALIZE, &FinalizeCounterClassOps};
+
+static bool MakeFinalizeObserver(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JSObject* obj =
+ JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool FinalizeCount(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(finalizeCount);
+ return true;
+}
+
+static bool ResetFinalizeCount(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ finalizeCount = 0;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool DumpHeap(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ FILE* dumpFile = stdout;
+ auto closeFile = mozilla::MakeScopeExit([&dumpFile] {
+ if (dumpFile != stdout) {
+ fclose(dumpFile);
+ }
+ });
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ if (!args.get(0).isUndefined()) {
+ RootedString str(cx, ToString(cx, args[0]));
+ if (!str) {
+ return false;
+ }
+ if (!fuzzingSafe) {
+ UniqueChars fileNameBytes = JS_EncodeStringToUTF8(cx, str);
+ if (!fileNameBytes) {
+ return false;
+ }
+#ifdef XP_WIN
+ UniqueWideChars wideFileNameBytes =
+ JS::EncodeUtf8ToWide(cx, fileNameBytes.get());
+ if (!wideFileNameBytes) {
+ return false;
+ }
+ dumpFile = _wfopen(wideFileNameBytes.get(), L"w");
+#else
+ UniqueChars narrowFileNameBytes =
+ JS::EncodeUtf8ToNarrow(cx, fileNameBytes.get());
+ if (!narrowFileNameBytes) {
+ return false;
+ }
+ dumpFile = fopen(narrowFileNameBytes.get(), "w");
+#endif
+ if (!dumpFile) {
+ JS_ReportErrorUTF8(cx, "can't open %s", fileNameBytes.get());
+ return false;
+ }
+ }
+ }
+
+ js::DumpHeap(cx, dumpFile, js::IgnoreNurseryObjects);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool Terminate(JSContext* cx, unsigned arg, Value* vp) {
+ // Print a message to stderr in differential testing to help jsfunfuzz
+ // find uncatchable-exception bugs.
+ if (js::SupportDifferentialTesting()) {
+ fprintf(stderr, "terminate called\n");
+ }
+
+ JS_ClearPendingException(cx);
+ return false;
+}
+
+static bool ReadGeckoProfilingStack(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ // Return boolean 'false' if profiler is not enabled.
+ if (!cx->runtime()->geckoProfiler().enabled()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ // Array holding physical jit stack frames.
+ RootedObject stack(cx, NewDenseEmptyArray(cx));
+ if (!stack) {
+ return false;
+ }
+
+ // If profiler sampling has been suppressed, return an empty
+ // stack.
+ if (!cx->isProfilerSamplingEnabled()) {
+ args.rval().setObject(*stack);
+ return true;
+ }
+
+ struct InlineFrameInfo {
+ InlineFrameInfo(const char* kind, UniqueChars label)
+ : kind(kind), label(std::move(label)) {}
+ const char* kind;
+ UniqueChars label;
+ };
+
+ Vector<Vector<InlineFrameInfo, 0, TempAllocPolicy>, 0, TempAllocPolicy>
+ frameInfo(cx);
+
+ JS::ProfilingFrameIterator::RegisterState state;
+ for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
+ MOZ_ASSERT(i.stackAddress() != nullptr);
+
+ if (!frameInfo.emplaceBack(cx)) {
+ return false;
+ }
+
+ const size_t MaxInlineFrames = 16;
+ JS::ProfilingFrameIterator::Frame frames[MaxInlineFrames];
+ uint32_t nframes = i.extractStack(frames, 0, MaxInlineFrames);
+ MOZ_ASSERT(nframes <= MaxInlineFrames);
+ for (uint32_t i = 0; i < nframes; i++) {
+ const char* frameKindStr = nullptr;
+ switch (frames[i].kind) {
+ case JS::ProfilingFrameIterator::Frame_BaselineInterpreter:
+ frameKindStr = "baseline-interpreter";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Baseline:
+ frameKindStr = "baseline-jit";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Ion:
+ frameKindStr = "ion";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Wasm:
+ frameKindStr = "wasm";
+ break;
+ default:
+ frameKindStr = "unknown";
+ }
+
+ UniqueChars label =
+ DuplicateStringToArena(js::StringBufferArena, cx, frames[i].label);
+ if (!label) {
+ return false;
+ }
+
+ if (!frameInfo.back().emplaceBack(frameKindStr, std::move(label))) {
+ return false;
+ }
+ }
+ }
+
+ RootedObject inlineFrameInfo(cx);
+ RootedString frameKind(cx);
+ RootedString frameLabel(cx);
+ RootedId idx(cx);
+
+ const unsigned propAttrs = JSPROP_ENUMERATE;
+
+ uint32_t physicalFrameNo = 0;
+ for (auto& frame : frameInfo) {
+ // Array holding all inline frames in a single physical jit stack frame.
+ RootedObject inlineStack(cx, NewDenseEmptyArray(cx));
+ if (!inlineStack) {
+ return false;
+ }
+
+ uint32_t inlineFrameNo = 0;
+ for (auto& inlineFrame : frame) {
+ // Object holding frame info.
+ RootedObject inlineFrameInfo(cx, NewPlainObject(cx));
+ if (!inlineFrameInfo) {
+ return false;
+ }
+
+ frameKind = NewStringCopyZ<CanGC>(cx, inlineFrame.kind);
+ if (!frameKind) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, inlineFrameInfo, "kind", frameKind,
+ propAttrs)) {
+ return false;
+ }
+
+ frameLabel = NewLatin1StringZ(cx, std::move(inlineFrame.label));
+ if (!frameLabel) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel,
+ propAttrs)) {
+ return false;
+ }
+
+ idx = PropertyKey::Int(inlineFrameNo);
+ if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0)) {
+ return false;
+ }
+
+ ++inlineFrameNo;
+ }
+
+ // Push inline array into main array.
+ idx = PropertyKey::Int(physicalFrameNo);
+ if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0)) {
+ return false;
+ }
+
+ ++physicalFrameNo;
+ }
+
+ args.rval().setObject(*stack);
+ return true;
+}
+
+static bool ReadGeckoInterpProfilingStack(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ // Return boolean 'false' if profiler is not enabled.
+ if (!cx->runtime()->geckoProfiler().enabled()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ // Array with information about each frame.
+ Rooted<JSObject*> stack(cx, NewDenseEmptyArray(cx));
+ if (!stack) {
+ return false;
+ }
+ uint32_t stackIndex = 0;
+
+ ProfilingStack* profStack = cx->geckoProfiler().getProfilingStack();
+ MOZ_ASSERT(profStack);
+
+ for (size_t i = 0; i < profStack->stackSize(); i++) {
+ const auto& frame = profStack->frames[i];
+ if (!frame.isJsFrame()) {
+ continue;
+ }
+
+ // Skip fake JS frame pushed for js::RunScript by GeckoProfilerEntryMarker.
+ const char* dynamicStr = frame.dynamicString();
+ if (!dynamicStr) {
+ continue;
+ }
+
+ Rooted<PlainObject*> frameInfo(cx, NewPlainObject(cx));
+ if (!frameInfo) {
+ return false;
+ }
+
+ Rooted<JSString*> dynamicString(
+ cx, JS_NewStringCopyUTF8Z(
+ cx, JS::ConstUTF8CharsZ(dynamicStr, strlen(dynamicStr))));
+ if (!dynamicString) {
+ return false;
+ }
+ if (!JS_DefineProperty(cx, frameInfo, "dynamicString", dynamicString,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (!JS_DefineElement(cx, stack, stackIndex, frameInfo, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ stackIndex++;
+ }
+
+ args.rval().setObject(*stack);
+ return true;
+}
+
+static bool EnableOsiPointRegisterChecks(JSContext*, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef CHECK_OSIPOINT_REGISTERS
+ jit::JitOptions.checkOsiPointRegisters = true;
+#endif
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool DisplayName(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject() || !args[0].toObject().is<JSFunction>()) {
+ RootedObject arg(cx, &args.callee());
+ ReportUsageErrorASCII(cx, arg, "Must have one function argument");
+ return false;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ JSString* str = fun->displayAtom();
+ args.rval().setString(str ? str : cx->runtime()->emptyString.ref());
+ return true;
+}
+
+static bool IsAvxPresent(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+ int minVersion = 1;
+ if (argc > 0 && args.get(0).isNumber()) {
+ minVersion = std::max(1, int(args[0].toNumber()));
+ }
+ switch (minVersion) {
+ case 1:
+ args.rval().setBoolean(jit::Assembler::HasAVX());
+ return true;
+ case 2:
+ args.rval().setBoolean(jit::Assembler::HasAVX2());
+ return true;
+ }
+#endif
+ args.rval().setBoolean(false);
+ return true;
+}
+
+class ShellAllocationMetadataBuilder : public AllocationMetadataBuilder {
+ public:
+ ShellAllocationMetadataBuilder() : AllocationMetadataBuilder() {}
+
+ virtual JSObject* build(JSContext* cx, HandleObject,
+ AutoEnterOOMUnsafeRegion& oomUnsafe) const override;
+
+ static const ShellAllocationMetadataBuilder metadataBuilder;
+};
+
+JSObject* ShellAllocationMetadataBuilder::build(
+ JSContext* cx, HandleObject, AutoEnterOOMUnsafeRegion& oomUnsafe) const {
+ RootedObject obj(cx, NewPlainObject(cx));
+ if (!obj) {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ RootedObject stack(cx, NewDenseEmptyArray(cx));
+ if (!stack) {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ static int createdIndex = 0;
+ createdIndex++;
+
+ if (!JS_DefineProperty(cx, obj, "index", createdIndex, 0)) {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ if (!JS_DefineProperty(cx, obj, "stack", stack, 0)) {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ int stackIndex = 0;
+ RootedId id(cx);
+ RootedValue callee(cx);
+ for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) {
+ if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) {
+ id = PropertyKey::Int(stackIndex);
+ RootedObject callee(cx, iter.callee(cx));
+ if (!JS_DefinePropertyById(cx, stack, id, callee, JSPROP_ENUMERATE)) {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+ stackIndex++;
+ }
+ }
+
+ return obj;
+}
+
+const ShellAllocationMetadataBuilder
+ ShellAllocationMetadataBuilder::metadataBuilder;
+
+static bool EnableShellAllocationMetadataBuilder(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ SetAllocationMetadataBuilder(
+ cx, &ShellAllocationMetadataBuilder::metadataBuilder);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetAllocationMetadata(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "Argument must be an object");
+ return false;
+ }
+
+ args.rval().setObjectOrNull(GetAllocationMetadata(&args[0].toObject()));
+ return true;
+}
+
+static bool testingFunc_bailout(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool testingFunc_bailAfter(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
+ JS_ReportErrorASCII(
+ cx, "Argument must be a positive number that fits in an int32");
+ return false;
+ }
+
+#ifdef DEBUG
+ if (auto* jitRuntime = cx->runtime()->jitRuntime()) {
+ uint32_t bailAfter = args[0].toInt32();
+ bool enableBailAfter = bailAfter > 0;
+ if (jitRuntime->ionBailAfterEnabled() != enableBailAfter) {
+ // Force JIT code to be recompiled with (or without) instrumentation.
+ ReleaseAllJITCode(cx->gcContext());
+ jitRuntime->setIonBailAfterEnabled(enableBailAfter);
+ }
+ jitRuntime->setIonBailAfterCounter(bailAfter);
+ }
+#endif
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool testingFunc_invalidate(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // If the topmost frame is Ion/Warp, find the IonScript and invalidate it.
+ FrameIter iter(cx);
+ if (!iter.done() && iter.isIon()) {
+ while (!iter.isPhysicalJitFrame()) {
+ ++iter;
+ }
+ if (iter.script()->hasIonScript()) {
+ js::jit::Invalidate(cx, iter.script());
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static constexpr unsigned JitWarmupResetLimit = 20;
+static_assert(JitWarmupResetLimit <=
+ unsigned(JSScript::MutableFlags::WarmupResets_MASK),
+ "JitWarmupResetLimit exceeds max value");
+
+static bool testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!jit::IsBaselineJitEnabled(cx)) {
+ return ReturnStringCopy(cx, args, "Baseline is disabled.");
+ }
+
+ // Use frame iterator to inspect caller.
+ FrameIter iter(cx);
+
+ // We may be invoked directly, not in a JS context, e.g. if inJit is added as
+ // a callback on the event queue.
+ if (iter.done()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ if (iter.hasScript()) {
+ // Detect repeated attempts to compile, resetting the counter if inJit
+ // succeeds. Note: This script may have be inlined into its caller.
+ if (iter.isJSJit()) {
+ iter.script()->resetWarmUpResetCounter();
+ } else if (iter.script()->getWarmUpResetCount() >= JitWarmupResetLimit) {
+ return ReturnStringCopy(
+ cx, args, "Compilation is being repeatedly prevented. Giving up.");
+ }
+ }
+
+ // Returns true for any JIT (including WASM).
+ MOZ_ASSERT_IF(iter.isJSJit(), cx->currentlyRunningInJit());
+ args.rval().setBoolean(cx->currentlyRunningInJit());
+ return true;
+}
+
+static bool testingFunc_inIon(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!jit::IsIonEnabled(cx)) {
+ return ReturnStringCopy(cx, args, "Ion is disabled.");
+ }
+
+ // Use frame iterator to inspect caller.
+ FrameIter iter(cx);
+
+ // We may be invoked directly, not in a JS context, e.g. if inJson is added as
+ // a callback on the event queue.
+ if (iter.done()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ if (iter.hasScript()) {
+ // Detect repeated attempts to compile, resetting the counter if inIon
+ // succeeds. Note: This script may have be inlined into its caller.
+ if (iter.isIon()) {
+ iter.script()->resetWarmUpResetCounter();
+ } else if (!iter.script()->canIonCompile()) {
+ return ReturnStringCopy(cx, args, "Unable to Ion-compile this script.");
+ } else if (iter.script()->getWarmUpResetCount() >= JitWarmupResetLimit) {
+ return ReturnStringCopy(
+ cx, args, "Compilation is being repeatedly prevented. Giving up.");
+ }
+ }
+
+ args.rval().setBoolean(iter.isIon());
+ return true;
+}
+
+bool js::testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Expects only 2 arguments");
+ return false;
+ }
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool TestingFunc_assertJitStackInvariants(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ jit::AssertJitStackInvariants(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+bool js::testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Expects only 2 arguments");
+ return false;
+ }
+
+ // NOP when not in IonMonkey
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetJitCompilerOptions(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info) {
+ return false;
+ }
+
+ uint32_t intValue = 0;
+ RootedValue value(cx);
+
+#define JIT_COMPILER_MATCH(key, string) \
+ opt = JSJITCOMPILER_##key; \
+ if (JS_GetGlobalJitCompilerOption(cx, opt, &intValue)) { \
+ value.setInt32(intValue); \
+ if (!JS_SetProperty(cx, info, string, value)) return false; \
+ }
+
+ JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
+ JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
+#undef JIT_COMPILER_MATCH
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool SetIonCheckGraphCoherency(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ jit::JitOptions.checkGraphConsistency = ToBoolean(args.get(0));
+ args.rval().setUndefined();
+ return true;
+}
+
+// A JSObject that holds structured clone data, similar to the C++ class
+// JSAutoStructuredCloneBuffer.
+class CloneBufferObject : public NativeObject {
+ static const JSPropertySpec props_[3];
+
+ static const size_t DATA_SLOT = 0;
+ static const size_t SYNTHETIC_SLOT = 1;
+ static const size_t NUM_SLOTS = 2;
+
+ public:
+ static const JSClass class_;
+
+ static CloneBufferObject* Create(JSContext* cx) {
+ RootedObject obj(cx, JS_NewObject(cx, &class_));
+ if (!obj) {
+ return nullptr;
+ }
+ obj->as<CloneBufferObject>().setReservedSlot(DATA_SLOT,
+ PrivateValue(nullptr));
+ obj->as<CloneBufferObject>().setReservedSlot(SYNTHETIC_SLOT,
+ BooleanValue(false));
+
+ if (!JS_DefineProperties(cx, obj, props_)) {
+ return nullptr;
+ }
+
+ return &obj->as<CloneBufferObject>();
+ }
+
+ static CloneBufferObject* Create(JSContext* cx,
+ JSAutoStructuredCloneBuffer* buffer) {
+ Rooted<CloneBufferObject*> obj(cx, Create(cx));
+ if (!obj) {
+ return nullptr;
+ }
+ auto data = js::MakeUnique<JSStructuredCloneData>(buffer->scope());
+ if (!data) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ buffer->giveTo(data.get());
+ obj->setData(data.release(), false);
+ return obj;
+ }
+
+ JSStructuredCloneData* data() const {
+ return static_cast<JSStructuredCloneData*>(
+ getReservedSlot(DATA_SLOT).toPrivate());
+ }
+
+ bool isSynthetic() const {
+ return getReservedSlot(SYNTHETIC_SLOT).toBoolean();
+ }
+
+ void setData(JSStructuredCloneData* aData, bool synthetic) {
+ MOZ_ASSERT(!data());
+ setReservedSlot(DATA_SLOT, PrivateValue(aData));
+ setReservedSlot(SYNTHETIC_SLOT, BooleanValue(synthetic));
+ }
+
+ // Discard an owned clone buffer.
+ void discard() {
+ js_delete(data());
+ setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
+ }
+
+ static bool setCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ Rooted<CloneBufferObject*> obj(
+ cx, &args.thisv().toObject().as<CloneBufferObject>());
+
+ const char* data = nullptr;
+ UniqueChars dataOwner;
+ size_t nbytes;
+
+ if (args.get(0).isObject() && args[0].toObject().is<ArrayBufferObject>()) {
+ ArrayBufferObject* buffer = &args[0].toObject().as<ArrayBufferObject>();
+ bool isSharedMemory;
+ uint8_t* dataBytes = nullptr;
+ JS::GetArrayBufferLengthAndData(buffer, &nbytes, &isSharedMemory,
+ &dataBytes);
+ MOZ_ASSERT(!isSharedMemory);
+ data = reinterpret_cast<char*>(dataBytes);
+ } else {
+ JSString* str = JS::ToString(cx, args.get(0));
+ if (!str) {
+ return false;
+ }
+ dataOwner = JS_EncodeStringToLatin1(cx, str);
+ if (!dataOwner) {
+ return false;
+ }
+ data = dataOwner.get();
+ nbytes = JS_GetStringLength(str);
+ }
+
+ if (nbytes == 0 || (nbytes % sizeof(uint64_t) != 0)) {
+ JS_ReportErrorASCII(cx, "Invalid length for clonebuffer data");
+ return false;
+ }
+
+ auto buf = js::MakeUnique<JSStructuredCloneData>(
+ JS::StructuredCloneScope::DifferentProcess);
+ if (!buf || !buf->Init(nbytes)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ MOZ_ALWAYS_TRUE(buf->AppendBytes(data, nbytes));
+ obj->discard();
+ obj->setData(buf.release(), true);
+
+ args.rval().setUndefined();
+ return true;
+ }
+
+ static bool is(HandleValue v) {
+ return v.isObject() && v.toObject().is<CloneBufferObject>();
+ }
+
+ static bool setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, setCloneBuffer_impl>(cx, args);
+ }
+
+ static bool getData(JSContext* cx, Handle<CloneBufferObject*> obj,
+ JSStructuredCloneData** data) {
+ if (!obj->data()) {
+ *data = nullptr;
+ return true;
+ }
+
+ bool hasTransferable;
+ if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable)) {
+ return false;
+ }
+
+ if (hasTransferable) {
+ JS_ReportErrorASCII(
+ cx, "cannot retrieve structured clone buffer with transferables");
+ return false;
+ }
+
+ *data = obj->data();
+ return true;
+ }
+
+ static bool getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ Rooted<CloneBufferObject*> obj(
+ cx, &args.thisv().toObject().as<CloneBufferObject>());
+ MOZ_ASSERT(args.length() == 0);
+
+ JSStructuredCloneData* data;
+ if (!getData(cx, obj, &data)) {
+ return false;
+ }
+
+ size_t size = data->Size();
+ UniqueChars buffer(js_pod_malloc<char>(size));
+ if (!buffer) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto iter = data->Start();
+ if (!data->ReadBytes(iter, buffer.get(), size)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ JSString* str = JS_NewStringCopyN(cx, buffer.get(), size);
+ if (!str) {
+ return false;
+ }
+ args.rval().setString(str);
+ return true;
+ }
+
+ static bool getCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args);
+ }
+
+ static bool getCloneBufferAsArrayBuffer_impl(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<CloneBufferObject*> obj(
+ cx, &args.thisv().toObject().as<CloneBufferObject>());
+ MOZ_ASSERT(args.length() == 0);
+
+ JSStructuredCloneData* data;
+ if (!getData(cx, obj, &data)) {
+ return false;
+ }
+
+ size_t size = data->Size();
+ UniqueChars buffer(js_pod_malloc<char>(size));
+ if (!buffer) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto iter = data->Start();
+ if (!data->ReadBytes(iter, buffer.get(), size)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ auto* rawBuffer = buffer.release();
+ JSObject* arrayBuffer = JS::NewArrayBufferWithContents(cx, size, rawBuffer);
+ if (!arrayBuffer) {
+ js_free(rawBuffer);
+ return false;
+ }
+
+ args.rval().setObject(*arrayBuffer);
+ return true;
+ }
+
+ static bool getCloneBufferAsArrayBuffer(JSContext* cx, unsigned int argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, getCloneBufferAsArrayBuffer_impl>(cx, args);
+ }
+
+ static void Finalize(JS::GCContext* gcx, JSObject* obj) {
+ obj->as<CloneBufferObject>().discard();
+ }
+};
+
+static const JSClassOps CloneBufferObjectClassOps = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ CloneBufferObject::Finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+const JSClass CloneBufferObject::class_ = {
+ "CloneBuffer",
+ JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &CloneBufferObjectClassOps};
+
+const JSPropertySpec CloneBufferObject::props_[] = {
+ JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
+ JS_PSGS("arraybuffer", getCloneBufferAsArrayBuffer, setCloneBuffer, 0),
+ JS_PS_END};
+
+static mozilla::Maybe<JS::StructuredCloneScope> ParseCloneScope(
+ JSContext* cx, HandleString str) {
+ mozilla::Maybe<JS::StructuredCloneScope> scope;
+
+ JSLinearString* scopeStr = str->ensureLinear(cx);
+ if (!scopeStr) {
+ return scope;
+ }
+
+ if (StringEqualsLiteral(scopeStr, "SameProcess")) {
+ scope.emplace(JS::StructuredCloneScope::SameProcess);
+ } else if (StringEqualsLiteral(scopeStr, "DifferentProcess")) {
+ scope.emplace(JS::StructuredCloneScope::DifferentProcess);
+ } else if (StringEqualsLiteral(scopeStr, "DifferentProcessForIndexedDB")) {
+ scope.emplace(JS::StructuredCloneScope::DifferentProcessForIndexedDB);
+ }
+
+ return scope;
+}
+
+// A custom object that is serializable and transferable using
+// the engine's custom hooks. The callbacks log their activity
+// to a JSRuntime-wide log (tagging actions with IDs to distinguish them).
+class CustomSerializableObject : public NativeObject {
+ static const size_t ID_SLOT = 0;
+ static const size_t DETACHED_SLOT = 1;
+ static const size_t BEHAVIOR_SLOT = 2;
+ static const size_t NUM_SLOTS = 3;
+
+ static constexpr size_t MAX_LOG_LEN = 100;
+
+ // The activity log should be specific to a JSRuntime.
+ struct ActivityLog {
+ uint32_t buffer[MAX_LOG_LEN];
+ size_t length = 0;
+
+ static MOZ_THREAD_LOCAL(ActivityLog*) self;
+ static ActivityLog* getThreadLog() {
+ if (!self.initialized() || !self.get()) {
+ self.infallibleInit();
+ self.set(js_new<ActivityLog>());
+ MOZ_RELEASE_ASSERT(self.get());
+ }
+ return self.get();
+ }
+
+ static bool log(int32_t id, char action) {
+ return getThreadLog()->logImpl(id, action);
+ }
+
+ bool logImpl(int32_t id, char action) {
+ if (length + 2 > MAX_LOG_LEN) {
+ return false;
+ }
+ buffer[length++] = id;
+ buffer[length++] = uint32_t(action);
+ return true;
+ }
+ };
+
+ public:
+ enum class Behavior {
+ Nothing = 0,
+ FailDuringReadTransfer = 1,
+ FailDuringRead = 2
+ };
+
+ static constexpr JSClass class_ = {"CustomSerializable",
+ JSCLASS_HAS_RESERVED_SLOTS(NUM_SLOTS)};
+
+ static bool is(HandleValue v) {
+ return v.isObject() && v.toObject().is<CustomSerializableObject>();
+ }
+
+ static CustomSerializableObject* Create(JSContext* cx, int32_t id,
+ Behavior behavior) {
+ Rooted<CustomSerializableObject*> obj(
+ cx, static_cast<CustomSerializableObject*>(JS_NewObject(cx, &class_)));
+ if (!obj) {
+ return nullptr;
+ }
+ obj->setReservedSlot(ID_SLOT, Int32Value(id));
+ obj->setReservedSlot(DETACHED_SLOT, BooleanValue(false));
+ obj->setReservedSlot(BEHAVIOR_SLOT,
+ Int32Value(static_cast<int32_t>(behavior)));
+
+ if (!JS_DefineProperty(cx, obj, "log", getLog, clearLog, 0)) {
+ return nullptr;
+ }
+
+ return obj;
+ }
+
+ public:
+ static uint32_t tag() { return JS_SCTAG_USER_MIN; }
+
+ static bool log(int32_t id, char action) {
+ return ActivityLog::log(id, action);
+ }
+ bool log(char action) {
+ return log(getReservedSlot(ID_SLOT).toInt32(), action);
+ }
+
+ void detach() { setReservedSlot(DETACHED_SLOT, BooleanValue(true)); }
+ bool isDetached() { return getReservedSlot(DETACHED_SLOT).toBoolean(); }
+
+ uint32_t id() const { return getReservedSlot(ID_SLOT).toInt32(); }
+ Behavior behavior() {
+ return static_cast<Behavior>(getReservedSlot(BEHAVIOR_SLOT).toInt32());
+ }
+
+ static bool getLog(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<is, getLog_impl>(cx, args);
+ }
+
+ static bool getLog_impl(JSContext* cx, const CallArgs& args) {
+ Rooted<CustomSerializableObject*> obj(
+ cx, &args.thisv().toObject().as<CustomSerializableObject>());
+
+ size_t len = ActivityLog::getThreadLog()->length;
+ uint32_t* logBuffer = ActivityLog::getThreadLog()->buffer;
+
+ Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, len));
+ if (!result) {
+ return false;
+ }
+ result->ensureDenseInitializedLength(0, len);
+
+ for (size_t p = 0; p < len; p += 2) {
+ int32_t id = int32_t(logBuffer[p]);
+ char action = char(logBuffer[p + 1]);
+ result->setDenseElement(p, Int32Value(id));
+ JSString* str = JS_NewStringCopyN(cx, &action, 1);
+ if (!str) {
+ return false;
+ }
+ result->setDenseElement(p + 1, StringValue(str));
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+
+ static bool clearLog(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isNullOrUndefined()) {
+ JS_ReportErrorASCII(cx, "log may only be assigned null/undefined");
+ return false;
+ }
+ ActivityLog::getThreadLog()->length = 0;
+ args.rval().setUndefined();
+ return true;
+ }
+
+ static bool Write(JSContext* cx, JSStructuredCloneWriter* w,
+ JS::HandleObject aObj, bool* sameProcessScopeRequired,
+ void* closure) {
+ Rooted<CustomSerializableObject*> obj(cx);
+
+ if ((obj = aObj->maybeUnwrapIf<CustomSerializableObject>())) {
+ obj->log('w');
+ // Write a regular clone as a <tag, id> pair, followed by <0, behavior>.
+ // Note that transferring will communicate the behavior via a different
+ // mechanism.
+ return JS_WriteUint32Pair(w, obj->tag(), obj->id()) &&
+ JS_WriteUint32Pair(w, 0, static_cast<uint32_t>(obj->behavior()));
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SC_UNSUPPORTED_TYPE);
+ return false;
+ }
+
+ static JSObject* Read(JSContext* cx, JSStructuredCloneReader* r,
+ const JS::CloneDataPolicy& cloneDataPolicy,
+ uint32_t tag, uint32_t id, void* closure) {
+ uint32_t dummy, behaviorData;
+ if (!JS_ReadUint32Pair(r, &dummy, &behaviorData)) {
+ return nullptr;
+ }
+ if (dummy != 0 || id > INT32_MAX) {
+ JS_ReportErrorASCII(cx, "out of range");
+ return nullptr;
+ }
+
+ auto b = static_cast<Behavior>(behaviorData);
+ Rooted<CustomSerializableObject*> obj(
+ cx, Create(cx, static_cast<int32_t>(id), b));
+ if (!obj) {
+ return nullptr;
+ }
+
+ obj->log('r');
+ if (obj->behavior() == Behavior::FailDuringRead) {
+ JS_ReportErrorASCII(cx,
+ "Failed as requested in read during deserialization");
+ return nullptr;
+ }
+ return obj;
+ }
+
+ static bool CanTransfer(JSContext* cx, JS::Handle<JSObject*> wrapped,
+ bool* sameProcessScopeRequired, void* closure) {
+ Rooted<CustomSerializableObject*> obj(cx);
+
+ if ((obj = wrapped->maybeUnwrapIf<CustomSerializableObject>())) {
+ obj->log('?');
+ // For now, all CustomSerializable objects are considered to be
+ // transferable.
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool WriteTransfer(JSContext* cx, JS::Handle<JSObject*> aObj,
+ void* closure, uint32_t* tag,
+ JS::TransferableOwnership* ownership,
+ void** content, uint64_t* extraData) {
+ Rooted<CustomSerializableObject*> obj(cx);
+
+ if ((obj = aObj->maybeUnwrapIf<CustomSerializableObject>())) {
+ if (obj->isDetached()) {
+ JS_ReportErrorASCII(cx, "Attempted to transfer detached object");
+ return false;
+ }
+ obj->log('W');
+ *content = reinterpret_cast<void*>(obj->id());
+ *extraData = static_cast<uint64_t>(obj->behavior());
+ *tag = obj->tag();
+ *ownership = JS::SCTAG_TMO_CUSTOM;
+ obj->detach();
+ return true;
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_SC_NOT_TRANSFERABLE);
+ return false;
+ }
+
+ static bool ReadTransfer(JSContext* cx, JSStructuredCloneReader* r,
+ uint32_t tag, void* content, uint64_t extraData,
+ void* closure,
+ JS::MutableHandleObject returnObject) {
+ if (tag == CustomSerializableObject::tag()) {
+ int32_t id = int32_t(reinterpret_cast<uintptr_t>(content));
+ Rooted<CustomSerializableObject*> obj(
+ cx, CustomSerializableObject::Create(
+ cx, id, static_cast<Behavior>(extraData)));
+ if (!obj) {
+ return false;
+ }
+ obj->log('R');
+ if (obj->behavior() == Behavior::FailDuringReadTransfer) {
+ return false;
+ }
+ returnObject.set(obj);
+ return true;
+ }
+
+ return false;
+ }
+
+ static void FreeTransfer(uint32_t tag, JS::TransferableOwnership ownership,
+ void* content, uint64_t extraData, void* closure) {
+ CustomSerializableObject::log(uint32_t(reinterpret_cast<intptr_t>(content)),
+ 'F');
+ }
+};
+
+MOZ_THREAD_LOCAL(CustomSerializableObject::ActivityLog*)
+CustomSerializableObject::ActivityLog::self;
+
+static bool MakeSerializable(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ int32_t id = 0;
+ if (args.get(0).isInt32()) {
+ id = args[0].toInt32();
+ if (id < 0) {
+ JS_ReportErrorASCII(cx, "id out of range");
+ return false;
+ }
+ }
+ CustomSerializableObject::Behavior behavior =
+ CustomSerializableObject::Behavior::Nothing;
+ if (args.get(1).isInt32()) {
+ int32_t iv = args[1].toInt32();
+ constexpr int32_t min =
+ static_cast<int32_t>(CustomSerializableObject::Behavior::Nothing);
+ constexpr int32_t max = static_cast<int32_t>(
+ CustomSerializableObject::Behavior::FailDuringRead);
+ if (iv < min || iv > max) {
+ JS_ReportErrorASCII(cx, "behavior out of range");
+ return false;
+ }
+ behavior = static_cast<CustomSerializableObject::Behavior>(iv);
+ }
+
+ JSObject* obj = CustomSerializableObject::Create(cx, id, behavior);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static JSStructuredCloneCallbacks gCloneCallbacks = {
+ .read = CustomSerializableObject::Read,
+ .write = CustomSerializableObject::Write,
+ .reportError = nullptr,
+ .readTransfer = CustomSerializableObject::ReadTransfer,
+ .writeTransfer = CustomSerializableObject::WriteTransfer,
+ .freeTransfer = CustomSerializableObject::FreeTransfer,
+ .canTransfer = CustomSerializableObject::CanTransfer,
+ .sabCloned = nullptr};
+
+bool js::testingFunc_serialize(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (js::SupportDifferentialTesting()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "Function unavailable in differential testing mode.");
+ return false;
+ }
+
+ mozilla::Maybe<JSAutoStructuredCloneBuffer> clonebuf;
+ JS::CloneDataPolicy policy;
+
+ if (!args.get(2).isUndefined()) {
+ RootedObject opts(cx, ToObject(cx, args.get(2)));
+ if (!opts) {
+ return false;
+ }
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "SharedArrayBuffer", &v)) {
+ return false;
+ }
+
+ if (!v.isUndefined()) {
+ JSString* str = JS::ToString(cx, v);
+ if (!str) {
+ return false;
+ }
+ JSLinearString* poli = str->ensureLinear(cx);
+ if (!poli) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(poli, "allow")) {
+ policy.allowSharedMemoryObjects();
+ policy.allowIntraClusterClonableSharedObjects();
+ } else if (StringEqualsLiteral(poli, "deny")) {
+ // default
+ } else {
+ JS_ReportErrorASCII(cx, "Invalid policy value for 'SharedArrayBuffer'");
+ return false;
+ }
+ }
+
+ if (!JS_GetProperty(cx, opts, "scope", &v)) {
+ return false;
+ }
+
+ if (!v.isUndefined()) {
+ RootedString str(cx, JS::ToString(cx, v));
+ if (!str) {
+ return false;
+ }
+ auto scope = ParseCloneScope(cx, str);
+ if (!scope) {
+ JS_ReportErrorASCII(cx, "Invalid structured clone scope");
+ return false;
+ }
+ clonebuf.emplace(*scope, &gCloneCallbacks, nullptr);
+ }
+ }
+
+ if (!clonebuf) {
+ clonebuf.emplace(JS::StructuredCloneScope::SameProcess, &gCloneCallbacks,
+ nullptr);
+ }
+
+ if (!clonebuf->write(cx, args.get(0), args.get(1), policy)) {
+ return false;
+ }
+
+ RootedObject obj(cx, CloneBufferObject::Create(cx, clonebuf.ptr()));
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool Deserialize(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (js::SupportDifferentialTesting()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "Function unavailable in differential testing mode.");
+ return false;
+ }
+
+ if (!args.get(0).isObject() || !args[0].toObject().is<CloneBufferObject>()) {
+ JS_ReportErrorASCII(cx, "deserialize requires a clonebuffer argument");
+ return false;
+ }
+ Rooted<CloneBufferObject*> obj(cx,
+ &args[0].toObject().as<CloneBufferObject>());
+
+ JS::CloneDataPolicy policy;
+
+ JS::StructuredCloneScope scope =
+ obj->isSynthetic() ? JS::StructuredCloneScope::DifferentProcess
+ : JS::StructuredCloneScope::SameProcess;
+ if (args.get(1).isObject()) {
+ RootedObject opts(cx, &args[1].toObject());
+ if (!opts) {
+ return false;
+ }
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "SharedArrayBuffer", &v)) {
+ return false;
+ }
+
+ if (!v.isUndefined()) {
+ JSString* str = JS::ToString(cx, v);
+ if (!str) {
+ return false;
+ }
+ JSLinearString* poli = str->ensureLinear(cx);
+ if (!poli) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(poli, "allow")) {
+ policy.allowSharedMemoryObjects();
+ policy.allowIntraClusterClonableSharedObjects();
+ } else if (StringEqualsLiteral(poli, "deny")) {
+ // default
+ } else {
+ JS_ReportErrorASCII(cx, "Invalid policy value for 'SharedArrayBuffer'");
+ return false;
+ }
+ }
+
+ if (!JS_GetProperty(cx, opts, "scope", &v)) {
+ return false;
+ }
+
+ if (!v.isUndefined()) {
+ RootedString str(cx, JS::ToString(cx, v));
+ if (!str) {
+ return false;
+ }
+ auto maybeScope = ParseCloneScope(cx, str);
+ if (!maybeScope) {
+ JS_ReportErrorASCII(cx, "Invalid structured clone scope");
+ return false;
+ }
+
+ if (*maybeScope < scope) {
+ JS_ReportErrorASCII(cx,
+ "Cannot use less restrictive scope "
+ "than the deserialized clone buffer's scope");
+ return false;
+ }
+
+ scope = *maybeScope;
+ }
+ }
+
+ // Clone buffer was already consumed?
+ if (!obj->data()) {
+ JS_ReportErrorASCII(cx,
+ "deserialize given invalid clone buffer "
+ "(transferables already consumed?)");
+ return false;
+ }
+
+ bool hasTransferable;
+ if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable)) {
+ return false;
+ }
+
+ RootedValue deserialized(cx);
+ if (!JS_ReadStructuredClone(cx, *obj->data(), JS_STRUCTURED_CLONE_VERSION,
+ scope, &deserialized, policy, &gCloneCallbacks,
+ nullptr)) {
+ return false;
+ }
+ args.rval().set(deserialized);
+
+ // Consume any clone buffer with transferables; throw an error if it is
+ // deserialized again.
+ if (hasTransferable) {
+ obj->discard();
+ }
+
+ return true;
+}
+
+static bool DetachArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "detachArrayBuffer() requires a single argument");
+ return false;
+ }
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "detachArrayBuffer must be passed an object");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ if (!JS::DetachArrayBuffer(cx, obj)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool HelperThreadCount(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (js::SupportDifferentialTesting()) {
+ // Always return 0 to get consistent output with and without --no-threads.
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ if (CanUseExtraThreads()) {
+ args.rval().setInt32(GetHelperThreadCount());
+ } else {
+ args.rval().setInt32(0);
+ }
+ return true;
+}
+
+static bool EnableShapeConsistencyChecks(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef DEBUG
+ NativeObject::enableShapeConsistencyChecks();
+#endif
+ args.rval().setUndefined();
+ return true;
+}
+
+// ShapeSnapshot holds information about an object's properties. This is used
+// for checking object and shape changes between two points in time.
+class ShapeSnapshot {
+ HeapPtr<JSObject*> object_;
+ HeapPtr<Shape*> shape_;
+ HeapPtr<BaseShape*> baseShape_;
+ ObjectFlags objectFlags_;
+
+ GCVector<HeapPtr<Value>, 8> slots_;
+
+ struct PropertySnapshot {
+ HeapPtr<PropMap*> propMap;
+ uint32_t propMapIndex;
+ HeapPtr<PropertyKey> key;
+ PropertyInfo prop;
+
+ explicit PropertySnapshot(PropMap* map, uint32_t index)
+ : propMap(map),
+ propMapIndex(index),
+ key(map->getKey(index)),
+ prop(map->getPropertyInfo(index)) {}
+
+ void trace(JSTracer* trc) {
+ TraceEdge(trc, &propMap, "propMap");
+ TraceEdge(trc, &key, "key");
+ }
+
+ bool operator==(const PropertySnapshot& other) const {
+ return propMap == other.propMap && propMapIndex == other.propMapIndex &&
+ key == other.key && prop == other.prop;
+ }
+ bool operator!=(const PropertySnapshot& other) const {
+ return !operator==(other);
+ }
+ };
+ GCVector<PropertySnapshot, 8> properties_;
+
+ public:
+ explicit ShapeSnapshot(JSContext* cx) : slots_(cx), properties_(cx) {}
+ void checkSelf(JSContext* cx) const;
+ void check(JSContext* cx, const ShapeSnapshot& other) const;
+ bool init(JSObject* obj);
+ void trace(JSTracer* trc);
+
+ JSObject* object() const { return object_; }
+};
+
+// A JSObject that holds a ShapeSnapshot.
+class ShapeSnapshotObject : public NativeObject {
+ static constexpr size_t SnapshotSlot = 0;
+ static constexpr size_t ReservedSlots = 1;
+
+ public:
+ static const JSClassOps classOps_;
+ static const JSClass class_;
+
+ bool hasSnapshot() const {
+ // The snapshot may not be present yet if we GC during initialization.
+ return !getReservedSlot(SnapshotSlot).isUndefined();
+ }
+
+ ShapeSnapshot& snapshot() const {
+ void* ptr = getReservedSlot(SnapshotSlot).toPrivate();
+ MOZ_ASSERT(ptr);
+ return *static_cast<ShapeSnapshot*>(ptr);
+ }
+
+ static ShapeSnapshotObject* create(JSContext* cx, HandleObject obj);
+
+ static void finalize(JS::GCContext* gcx, JSObject* obj) {
+ if (obj->as<ShapeSnapshotObject>().hasSnapshot()) {
+ js_delete(&obj->as<ShapeSnapshotObject>().snapshot());
+ }
+ }
+ static void trace(JSTracer* trc, JSObject* obj) {
+ if (obj->as<ShapeSnapshotObject>().hasSnapshot()) {
+ obj->as<ShapeSnapshotObject>().snapshot().trace(trc);
+ }
+ }
+};
+
+/*static */ const JSClassOps ShapeSnapshotObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ ShapeSnapshotObject::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ ShapeSnapshotObject::trace, // trace
+};
+
+/*static */ const JSClass ShapeSnapshotObject::class_ = {
+ "ShapeSnapshotObject",
+ JSCLASS_HAS_RESERVED_SLOTS(ShapeSnapshotObject::ReservedSlots) |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &ShapeSnapshotObject::classOps_};
+
+bool ShapeSnapshot::init(JSObject* obj) {
+ object_ = obj;
+ shape_ = obj->shape();
+ baseShape_ = shape_->base();
+ objectFlags_ = shape_->objectFlags();
+
+ if (obj->is<NativeObject>()) {
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ // Snapshot the slot values.
+ size_t slotSpan = nobj->slotSpan();
+ if (!slots_.growBy(slotSpan)) {
+ return false;
+ }
+ for (size_t i = 0; i < slotSpan; i++) {
+ slots_[i] = nobj->getSlot(i);
+ }
+
+ // Snapshot property information.
+ if (uint32_t len = nobj->shape()->propMapLength(); len > 0) {
+ PropMap* map = nobj->shape()->propMap();
+ while (true) {
+ for (uint32_t i = 0; i < len; i++) {
+ if (!map->hasKey(i)) {
+ continue;
+ }
+ if (!properties_.append(PropertySnapshot(map, i))) {
+ return false;
+ }
+ }
+ if (!map->hasPrevious()) {
+ break;
+ }
+ map = map->asLinked()->previous();
+ len = PropMap::Capacity;
+ }
+ }
+ }
+
+ return true;
+}
+
+void ShapeSnapshot::trace(JSTracer* trc) {
+ TraceEdge(trc, &object_, "object");
+ TraceEdge(trc, &shape_, "shape");
+ TraceEdge(trc, &baseShape_, "baseShape");
+ slots_.trace(trc);
+ properties_.trace(trc);
+}
+
+void ShapeSnapshot::checkSelf(JSContext* cx) const {
+ // Assertions based on a single snapshot.
+
+ // Non-dictionary shapes must not be mutated.
+ if (!shape_->isDictionary()) {
+ MOZ_RELEASE_ASSERT(shape_->base() == baseShape_);
+ MOZ_RELEASE_ASSERT(shape_->objectFlags() == objectFlags_);
+ }
+
+ for (const PropertySnapshot& propSnapshot : properties_) {
+ PropMap* propMap = propSnapshot.propMap;
+ uint32_t propMapIndex = propSnapshot.propMapIndex;
+ PropertyInfo prop = propSnapshot.prop;
+
+ // Skip if the map no longer matches the snapshotted data. This can
+ // only happen for non-configurable dictionary properties.
+ if (PropertySnapshot(propMap, propMapIndex) != propSnapshot) {
+ MOZ_RELEASE_ASSERT(propMap->isDictionary());
+ MOZ_RELEASE_ASSERT(prop.configurable());
+ continue;
+ }
+
+ // Ensure ObjectFlags depending on property information are set if needed.
+ ObjectFlags expectedFlags = GetObjectFlagsForNewProperty(
+ shape_->getObjectClass(), shape_->objectFlags(), propSnapshot.key,
+ prop.flags(), cx);
+ MOZ_RELEASE_ASSERT(expectedFlags == objectFlags_);
+
+ // Accessors must have a PrivateGCThingValue(GetterSetter*) slot value.
+ if (prop.isAccessorProperty()) {
+ Value slotVal = slots_[prop.slot()];
+ MOZ_RELEASE_ASSERT(slotVal.isPrivateGCThing());
+ MOZ_RELEASE_ASSERT(slotVal.toGCThing()->is<GetterSetter>());
+ }
+
+ // Data properties must not have a PrivateGCThingValue slot value.
+ if (prop.isDataProperty()) {
+ Value slotVal = slots_[prop.slot()];
+ MOZ_RELEASE_ASSERT(!slotVal.isPrivateGCThing());
+ }
+ }
+}
+
+void ShapeSnapshot::check(JSContext* cx, const ShapeSnapshot& later) const {
+ checkSelf(cx);
+ later.checkSelf(cx);
+
+ if (object_ != later.object_) {
+ // Snapshots are for different objects. Assert dictionary shapes aren't
+ // shared.
+ if (object_->is<NativeObject>()) {
+ NativeObject* nobj = &object_->as<NativeObject>();
+ if (nobj->inDictionaryMode()) {
+ MOZ_RELEASE_ASSERT(shape_ != later.shape_);
+ }
+ }
+ return;
+ }
+
+ // We have two snapshots for the same object. Check the shape information
+ // wasn't changed in invalid ways.
+
+ // If the Shape is still the same, the object must have the same BaseShape,
+ // ObjectFlags and property information.
+ if (shape_ == later.shape_) {
+ MOZ_RELEASE_ASSERT(objectFlags_ == later.objectFlags_);
+ MOZ_RELEASE_ASSERT(baseShape_ == later.baseShape_);
+ MOZ_RELEASE_ASSERT(slots_.length() == later.slots_.length());
+ MOZ_RELEASE_ASSERT(properties_.length() == later.properties_.length());
+
+ for (size_t i = 0; i < properties_.length(); i++) {
+ MOZ_RELEASE_ASSERT(properties_[i] == later.properties_[i]);
+ // Non-configurable accessor properties and non-configurable, non-writable
+ // data properties shouldn't have had their slot mutated.
+ PropertyInfo prop = properties_[i].prop;
+ if (!prop.configurable()) {
+ if (prop.isAccessorProperty() ||
+ (prop.isDataProperty() && !prop.writable())) {
+ size_t slot = prop.slot();
+ MOZ_RELEASE_ASSERT(slots_[slot] == later.slots_[slot]);
+ }
+ }
+ }
+ }
+
+ // Object flags should not be lost. The exception is the Indexed flag, it
+ // can be cleared when densifying elements, so clear that flag first.
+ {
+ ObjectFlags flags = objectFlags_;
+ ObjectFlags flagsLater = later.objectFlags_;
+ flags.clearFlag(ObjectFlag::Indexed);
+ flagsLater.clearFlag(ObjectFlag::Indexed);
+ MOZ_RELEASE_ASSERT((flags.toRaw() & flagsLater.toRaw()) == flags.toRaw());
+ }
+
+ // If the HadGetterSetterChange flag wasn't set, all GetterSetter slots must
+ // be unchanged.
+ if (!later.objectFlags_.hasFlag(ObjectFlag::HadGetterSetterChange)) {
+ for (size_t i = 0; i < slots_.length(); i++) {
+ if (slots_[i].isPrivateGCThing() &&
+ slots_[i].toGCThing()->is<GetterSetter>()) {
+ MOZ_RELEASE_ASSERT(i < later.slots_.length());
+ MOZ_RELEASE_ASSERT(later.slots_[i] == slots_[i]);
+ }
+ }
+ }
+}
+
+// static
+ShapeSnapshotObject* ShapeSnapshotObject::create(JSContext* cx,
+ HandleObject obj) {
+ Rooted<UniquePtr<ShapeSnapshot>> snapshot(cx,
+ cx->make_unique<ShapeSnapshot>(cx));
+ if (!snapshot || !snapshot->init(obj)) {
+ return nullptr;
+ }
+
+ auto* snapshotObj = NewObjectWithGivenProto<ShapeSnapshotObject>(cx, nullptr);
+ if (!snapshotObj) {
+ return nullptr;
+ }
+ snapshotObj->initReservedSlot(SnapshotSlot, PrivateValue(snapshot.release()));
+ return snapshotObj;
+}
+
+static bool CreateShapeSnapshot(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "createShapeSnapshot requires an object argument");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ auto* res = ShapeSnapshotObject::create(cx, obj);
+ if (!res) {
+ return false;
+ }
+
+ res->snapshot().check(cx, res->snapshot());
+
+ args.rval().setObject(*res);
+ return true;
+}
+
+static bool CheckShapeSnapshot(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject() ||
+ !args[0].toObject().is<ShapeSnapshotObject>()) {
+ JS_ReportErrorASCII(cx, "checkShapeSnapshot requires a snapshot argument");
+ return false;
+ }
+
+ // Get the object to use from the snapshot if the second argument is not an
+ // object.
+ RootedObject obj(cx);
+ if (args.get(1).isObject()) {
+ obj = &args[1].toObject();
+ } else {
+ auto& snapshot = args[0].toObject().as<ShapeSnapshotObject>().snapshot();
+ obj = snapshot.object();
+ }
+
+ RootedObject otherSnapshot(cx, ShapeSnapshotObject::create(cx, obj));
+ if (!otherSnapshot) {
+ return false;
+ }
+
+ auto& snapshot1 = args[0].toObject().as<ShapeSnapshotObject>().snapshot();
+ auto& snapshot2 = otherSnapshot->as<ShapeSnapshotObject>().snapshot();
+ snapshot1.check(cx, snapshot2);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+static bool DumpObject(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, ToObject(cx, args.get(0)));
+ if (!obj) {
+ return false;
+ }
+
+ DumpObject(obj);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool SharedMemoryEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(
+ cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
+ return true;
+}
+
+static bool SharedArrayRawBufferRefcount(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1 || !args[0].isObject()) {
+ JS_ReportErrorASCII(cx, "Expected SharedArrayBuffer object");
+ return false;
+ }
+ RootedObject obj(cx, &args[0].toObject());
+ if (!obj->is<SharedArrayBufferObject>()) {
+ JS_ReportErrorASCII(cx, "Expected SharedArrayBuffer object");
+ return false;
+ }
+ args.rval().setInt32(
+ obj->as<SharedArrayBufferObject>().rawBufferObject()->refcount());
+ return true;
+}
+
+#ifdef NIGHTLY_BUILD
+static bool ObjectAddress(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (js::SupportDifferentialTesting()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "Function unavailable in differential testing mode.");
+ return false;
+ }
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+ void* ptr = js::UncheckedUnwrap(&args[0].toObject(), true);
+ char buffer[64];
+ SprintfLiteral(buffer, "%p", ptr);
+
+ return ReturnStringCopy(cx, args, buffer);
+}
+
+static bool SharedAddress(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (js::SupportDifferentialTesting()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "Function unavailable in differential testing mode.");
+ return false;
+ }
+
+ if (args.length() != 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+ if (!args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+ RootedObject obj(cx, CheckedUnwrapStatic(&args[0].toObject()));
+ if (!obj) {
+ ReportAccessDenied(cx);
+ return false;
+ }
+ if (!obj->is<SharedArrayBufferObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a SharedArrayBuffer");
+ return false;
+ }
+ char buffer[64];
+ uint32_t nchar = SprintfLiteral(
+ buffer, "%p",
+ obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(
+ /*safeish*/));
+
+ JSString* str = JS_NewStringCopyN(cx, buffer, nchar);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+
+ return true;
+}
+#endif
+
+static bool HasInvalidatedTeleporting(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isObject()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected single object argument");
+ return false;
+ }
+
+ args.rval().setBoolean(args[0].toObject().hasInvalidatedTeleporting());
+ return true;
+}
+
+static bool DumpBacktrace(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ DumpBacktrace(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetBacktrace(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool showArgs = false;
+ bool showLocals = false;
+ bool showThisProps = false;
+
+ if (args.length() > 1) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Too many arguments");
+ return false;
+ }
+
+ if (args.length() == 1) {
+ RootedObject cfg(cx, ToObject(cx, args[0]));
+ if (!cfg) {
+ return false;
+ }
+ RootedValue v(cx);
+
+ if (!JS_GetProperty(cx, cfg, "args", &v)) {
+ return false;
+ }
+ showArgs = ToBoolean(v);
+
+ if (!JS_GetProperty(cx, cfg, "locals", &v)) {
+ return false;
+ }
+ showLocals = ToBoolean(v);
+
+ if (!JS_GetProperty(cx, cfg, "thisprops", &v)) {
+ return false;
+ }
+ showThisProps = ToBoolean(v);
+ }
+
+ JS::UniqueChars buf =
+ JS::FormatStackDump(cx, showArgs, showLocals, showThisProps);
+ if (!buf) {
+ return false;
+ }
+
+ size_t len;
+ UniqueTwoByteChars ucbuf(JS::LossyUTF8CharsToNewTwoByteCharsZ(
+ cx, JS::UTF8Chars(buf.get(), strlen(buf.get())),
+ &len, js::MallocArena)
+ .get());
+ if (!ucbuf) {
+ return false;
+ }
+ JSString* str = JS_NewUCStringCopyN(cx, ucbuf.get(), len);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool ReportOutOfMemory(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JS_ReportOutOfMemory(cx);
+ cx->clearPendingException();
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool ThrowOutOfMemory(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+}
+
+static bool ReportLargeAllocationFailure(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ size_t bytes = JSRuntime::LARGE_ALLOCATION;
+ if (args.length() >= 1) {
+ if (!args[0].isInt32()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee,
+ "First argument must be an integer if specified.");
+ return false;
+ }
+ bytes = args[0].toInt32();
+ }
+
+ void* buf = cx->runtime()->onOutOfMemoryCanGC(AllocFunction::Malloc,
+ js::MallocArena, bytes);
+
+ js_free(buf);
+ args.rval().setUndefined();
+ return true;
+}
+
+namespace heaptools {
+
+using EdgeName = UniqueTwoByteChars;
+
+// An edge to a node from its predecessor in a path through the graph.
+class BackEdge {
+ // The node from which this edge starts.
+ JS::ubi::Node predecessor_;
+
+ // The name of this edge.
+ EdgeName name_;
+
+ public:
+ BackEdge() : name_(nullptr) {}
+ // Construct an initialized back edge, taking ownership of |name|.
+ BackEdge(JS::ubi::Node predecessor, EdgeName name)
+ : predecessor_(predecessor), name_(std::move(name)) {}
+ BackEdge(BackEdge&& rhs)
+ : predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) {}
+ BackEdge& operator=(BackEdge&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~BackEdge();
+ new (this) BackEdge(std::move(rhs));
+ return *this;
+ }
+
+ EdgeName forgetName() { return std::move(name_); }
+ JS::ubi::Node predecessor() const { return predecessor_; }
+
+ private:
+ // No copy constructor or copying assignment.
+ BackEdge(const BackEdge&) = delete;
+ BackEdge& operator=(const BackEdge&) = delete;
+};
+
+// A path-finding handler class for use with JS::ubi::BreadthFirst.
+struct FindPathHandler {
+ using NodeData = BackEdge;
+ using Traversal = JS::ubi::BreadthFirst<FindPathHandler>;
+
+ FindPathHandler(JSContext* cx, JS::ubi::Node start, JS::ubi::Node target,
+ MutableHandle<GCVector<Value>> nodes, Vector<EdgeName>& edges)
+ : cx(cx),
+ start(start),
+ target(target),
+ foundPath(false),
+ nodes(nodes),
+ edges(edges) {}
+
+ bool operator()(Traversal& traversal, JS::ubi::Node origin,
+ const JS::ubi::Edge& edge, BackEdge* backEdge, bool first) {
+ // We take care of each node the first time we visit it, so there's
+ // nothing to be done on subsequent visits.
+ if (!first) {
+ return true;
+ }
+
+ // Record how we reached this node. This is the last edge on a
+ // shortest path to this node.
+ EdgeName edgeName =
+ DuplicateStringToArena(js::StringBufferArena, cx, edge.name.get());
+ if (!edgeName) {
+ return false;
+ }
+ *backEdge = BackEdge(origin, std::move(edgeName));
+
+ // Have we reached our final target node?
+ if (edge.referent == target) {
+ // Record the path that got us here, which must be a shortest path.
+ if (!recordPath(traversal, backEdge)) {
+ return false;
+ }
+ foundPath = true;
+ traversal.stop();
+ }
+
+ return true;
+ }
+
+ // We've found a path to our target. Walk the backlinks to produce the
+ // (reversed) path, saving the path in |nodes| and |edges|. |nodes| is
+ // rooted, so it can hold the path's nodes as we leave the scope of
+ // the AutoCheckCannotGC. Note that nodes are added to |visited| after we
+ // return from operator() so we have to pass the target BackEdge* to this
+ // function.
+ bool recordPath(Traversal& traversal, BackEdge* targetBackEdge) {
+ JS::ubi::Node here = target;
+
+ do {
+ BackEdge* backEdge = targetBackEdge;
+ if (here != target) {
+ Traversal::NodeMap::Ptr p = traversal.visited.lookup(here);
+ MOZ_ASSERT(p);
+ backEdge = &p->value();
+ }
+ JS::ubi::Node predecessor = backEdge->predecessor();
+ if (!nodes.append(predecessor.exposeToJS()) ||
+ !edges.append(backEdge->forgetName())) {
+ return false;
+ }
+ here = predecessor;
+ } while (here != start);
+
+ return true;
+ }
+
+ JSContext* cx;
+
+ // The node we're starting from.
+ JS::ubi::Node start;
+
+ // The node we're looking for.
+ JS::ubi::Node target;
+
+ // True if we found a path to target, false if we didn't.
+ bool foundPath;
+
+ // The nodes and edges of the path --- should we find one. The path is
+ // stored in reverse order, because that's how it's easiest for us to
+ // construct it:
+ // - edges[i] is the name of the edge from nodes[i] to nodes[i-1].
+ // - edges[0] is the name of the edge from nodes[0] to the target.
+ // - The last node, nodes[n-1], is the start node.
+ MutableHandle<GCVector<Value>> nodes;
+ Vector<EdgeName>& edges;
+};
+
+} // namespace heaptools
+
+static bool FindPath(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "findPath", 2)) {
+ return false;
+ }
+
+ // We don't ToString non-objects given as 'start' or 'target', because this
+ // test is all about object identity, and ToString doesn't preserve that.
+ // Non-GCThing endpoints don't make much sense.
+ if (!args[0].isObject() && !args[0].isString() && !args[0].isSymbol()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not an object, string, or symbol");
+ return false;
+ }
+
+ if (!args[1].isObject() && !args[1].isString() && !args[1].isSymbol()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not an object, string, or symbol");
+ return false;
+ }
+
+ Rooted<GCVector<Value>> nodes(cx, GCVector<Value>(cx));
+ Vector<heaptools::EdgeName> edges(cx);
+
+ {
+ // We can't tolerate the GC moving things around while we're searching
+ // the heap. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node start(args[0]), target(args[1]);
+
+ heaptools::FindPathHandler handler(cx, start, target, &nodes, edges);
+ heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC);
+ if (!traversal.addStart(start)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!traversal.traverse()) {
+ if (!cx->isExceptionPending()) {
+ ReportOutOfMemory(cx);
+ }
+ return false;
+ }
+
+ if (!handler.foundPath) {
+ // We didn't find any paths from the start to the target.
+ args.rval().setUndefined();
+ return true;
+ }
+ }
+
+ // |nodes| and |edges| contain the path from |start| to |target|, reversed.
+ // Construct a JavaScript array describing the path from the start to the
+ // target. Each element has the form:
+ //
+ // {
+ // node: <object or string or symbol>,
+ // edge: <string describing outgoing edge from node>
+ // }
+ //
+ // or, if the node is some internal thing that isn't a proper JavaScript
+ // value:
+ //
+ // { node: undefined, edge: <string> }
+ size_t length = nodes.length();
+ Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!result) {
+ return false;
+ }
+ result->ensureDenseInitializedLength(0, length);
+
+ // Walk |nodes| and |edges| in the stored order, and construct the result
+ // array in start-to-target order.
+ for (size_t i = 0; i < length; i++) {
+ // Build an object describing the node and edge.
+ RootedObject obj(cx, NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ // Only define the "node" property if we're not fuzzing, to prevent the
+ // fuzzers from messing with internal objects that we don't want to expose
+ // to arbitrary JS.
+ if (!fuzzingSafe) {
+ RootedValue wrapped(cx, nodes[i]);
+ if (!cx->compartment()->wrap(cx, &wrapped)) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "node", wrapped, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ heaptools::EdgeName edgeName = std::move(edges[i]);
+
+ size_t edgeNameLength = js_strlen(edgeName.get());
+ RootedString edgeStr(
+ cx, NewString<CanGC>(cx, std::move(edgeName), edgeNameLength));
+ if (!edgeStr) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "edge", edgeStr, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ result->setDenseElement(length - i - 1, ObjectValue(*obj));
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+static bool ShortestPaths(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "shortestPaths", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<ArrayObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not an array object");
+ return false;
+ }
+
+ Rooted<ArrayObject*> objs(cx, &args[0].toObject().as<ArrayObject>());
+
+ RootedValue start(cx, NullValue());
+ int32_t maxNumPaths = 3;
+
+ if (!args.get(1).isUndefined()) {
+ if (!args[1].isObject()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[1],
+ nullptr, "not an options object");
+ return false;
+ }
+
+ RootedObject options(cx, &args[1].toObject());
+ bool exists;
+ if (!JS_HasProperty(cx, options, "start", &exists)) {
+ return false;
+ }
+ if (exists) {
+ if (!JS_GetProperty(cx, options, "start", &start)) {
+ return false;
+ }
+
+ // Non-GCThing endpoints don't make much sense.
+ if (!start.isGCThing()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, start,
+ nullptr, "not a GC thing");
+ return false;
+ }
+ }
+
+ RootedValue v(cx, Int32Value(maxNumPaths));
+ if (!JS_HasProperty(cx, options, "maxNumPaths", &exists)) {
+ return false;
+ }
+ if (exists) {
+ if (!JS_GetProperty(cx, options, "maxNumPaths", &v)) {
+ return false;
+ }
+ if (!JS::ToInt32(cx, v, &maxNumPaths)) {
+ return false;
+ }
+ }
+ if (maxNumPaths <= 0) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, v,
+ nullptr, "not greater than 0");
+ return false;
+ }
+ }
+
+ // Ensure we have at least one target.
+ size_t length = objs->getDenseInitializedLength();
+ if (length == 0) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr,
+ "not a dense array object with one or more elements");
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ RootedValue el(cx, objs->getDenseElement(i));
+ if (!el.isGCThing()) {
+ JS_ReportErrorASCII(cx, "Each target must be a GC thing");
+ return false;
+ }
+ }
+
+ // We accumulate the results into a GC-stable form, due to the fact that the
+ // JS::ubi::ShortestPaths lifetime (when operating on the live heap graph)
+ // is bounded within an AutoCheckCannotGC.
+ Rooted<GCVector<GCVector<GCVector<Value>>>> values(
+ cx, GCVector<GCVector<GCVector<Value>>>(cx));
+ Vector<Vector<Vector<JS::ubi::EdgeName>>> names(cx);
+
+ {
+ JS::ubi::Node root;
+
+ JS::ubi::RootList rootList(cx, true);
+ if (start.isNull()) {
+ auto [ok, nogc] = rootList.init();
+ (void)nogc; // Old compilers get anxious about nogc being unused.
+ if (!ok) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ root = JS::ubi::Node(&rootList);
+ } else {
+ root = JS::ubi::Node(start);
+ }
+ JS::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+
+ for (size_t i = 0; i < length; i++) {
+ RootedValue val(cx, objs->getDenseElement(i));
+ JS::ubi::Node node(val);
+ if (!targets.put(node)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(
+ cx, noGC, maxNumPaths, root, std::move(targets));
+ if (maybeShortestPaths.isNothing()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto& shortestPaths = *maybeShortestPaths;
+
+ for (size_t i = 0; i < length; i++) {
+ if (!values.append(GCVector<GCVector<Value>>(cx)) ||
+ !names.append(Vector<Vector<JS::ubi::EdgeName>>(cx))) {
+ return false;
+ }
+
+ RootedValue val(cx, objs->getDenseElement(i));
+ JS::ubi::Node target(val);
+
+ bool ok = shortestPaths.forEachPath(target, [&](JS::ubi::Path& path) {
+ Rooted<GCVector<Value>> pathVals(cx, GCVector<Value>(cx));
+ Vector<JS::ubi::EdgeName> pathNames(cx);
+
+ for (auto& part : path) {
+ if (!pathVals.append(part->predecessor().exposeToJS()) ||
+ !pathNames.append(std::move(part->name()))) {
+ return false;
+ }
+ }
+
+ return values.back().append(std::move(pathVals.get())) &&
+ names.back().append(std::move(pathNames));
+ });
+
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+
+ MOZ_ASSERT(values.length() == names.length());
+ MOZ_ASSERT(values.length() == length);
+
+ Rooted<ArrayObject*> results(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!results) {
+ return false;
+ }
+ results->ensureDenseInitializedLength(0, length);
+
+ for (size_t i = 0; i < length; i++) {
+ size_t numPaths = values[i].length();
+ MOZ_ASSERT(names[i].length() == numPaths);
+
+ Rooted<ArrayObject*> pathsArray(cx,
+ NewDenseFullyAllocatedArray(cx, numPaths));
+ if (!pathsArray) {
+ return false;
+ }
+ pathsArray->ensureDenseInitializedLength(0, numPaths);
+
+ for (size_t j = 0; j < numPaths; j++) {
+ size_t pathLength = values[i][j].length();
+ MOZ_ASSERT(names[i][j].length() == pathLength);
+
+ Rooted<ArrayObject*> path(cx,
+ NewDenseFullyAllocatedArray(cx, pathLength));
+ if (!path) {
+ return false;
+ }
+ path->ensureDenseInitializedLength(0, pathLength);
+
+ for (size_t k = 0; k < pathLength; k++) {
+ Rooted<PlainObject*> part(cx, NewPlainObject(cx));
+ if (!part) {
+ return false;
+ }
+
+ RootedValue predecessor(cx, values[i][j][k]);
+ if (!cx->compartment()->wrap(cx, &predecessor) ||
+ !JS_DefineProperty(cx, part, "predecessor", predecessor,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (names[i][j][k]) {
+ RootedString edge(cx,
+ NewStringCopyZ<CanGC>(cx, names[i][j][k].get()));
+ if (!edge ||
+ !JS_DefineProperty(cx, part, "edge", edge, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ path->setDenseElement(k, ObjectValue(*part));
+ }
+
+ pathsArray->setDenseElement(j, ObjectValue(*path));
+ }
+
+ results->setDenseElement(i, ObjectValue(*pathsArray));
+ }
+
+ args.rval().setObject(*results);
+ return true;
+}
+
+static bool EvalReturningScope(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "evalReturningScope", 1)) {
+ return false;
+ }
+
+ RootedString str(cx, ToString(cx, args[0]));
+ if (!str) {
+ return false;
+ }
+
+ RootedObject global(cx);
+ if (args.hasDefined(1)) {
+ global = ToObject(cx, args[1]);
+ if (!global) {
+ return false;
+ }
+ }
+
+ JS::AutoFilename filename;
+ unsigned lineno;
+
+ JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filename.get(), lineno);
+ options.setNoScriptRval(true);
+ options.setNonSyntacticScope(true);
+
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, str)) {
+ return false;
+ }
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
+ return false;
+ }
+
+ if (global) {
+ global = CheckedUnwrapDynamic(global, cx, /* stopAtWindowProxy = */ false);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "Permission denied to access global");
+ return false;
+ }
+ if (!global->is<GlobalObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a global object");
+ return false;
+ }
+ } else {
+ global = JS::CurrentGlobalOrNull(cx);
+ }
+
+ RootedObject varObj(cx);
+
+ {
+ // ExecuteInFrameScriptEnvironment requires the script be in the same
+ // realm as the global. The script compilation should be done after
+ // switching globals.
+ AutoRealm ar(cx, global);
+
+ RootedScript script(cx, JS::Compile(cx, options, srcBuf));
+ if (!script) {
+ return false;
+ }
+
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ RootedObject lexicalScope(cx);
+ if (!js::ExecuteInFrameScriptEnvironment(cx, obj, script, &lexicalScope)) {
+ return false;
+ }
+
+ varObj = lexicalScope->enclosingEnvironment()->enclosingEnvironment();
+ MOZ_ASSERT(varObj->is<NonSyntacticVariablesObject>());
+ }
+
+ RootedValue varObjVal(cx, ObjectValue(*varObj));
+ if (!cx->compartment()->wrap(cx, &varObjVal)) {
+ return false;
+ }
+
+ args.rval().set(varObjVal);
+ return true;
+}
+
+static bool ByteSize(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf;
+
+ {
+ // We can't tolerate the GC moving things around while we're using a
+ // ubi::Node. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node node = args.get(0);
+ if (node) {
+ args.rval().setNumber(uint32_t(node.size(mallocSizeOf)));
+ } else {
+ args.rval().setUndefined();
+ }
+ }
+ return true;
+}
+
+static bool ByteSizeOfScript(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "byteSizeOfScript", 1)) {
+ return false;
+ }
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a Function object");
+ return false;
+ }
+
+ RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+ if (fun->isNativeFun()) {
+ JS_ReportErrorASCII(cx, "Argument must be a scripted function");
+ return false;
+ }
+
+ RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
+ if (!script) {
+ return false;
+ }
+
+ mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf;
+
+ {
+ // We can't tolerate the GC moving things around while we're using a
+ // ubi::Node. Check that nothing we do causes a GC.
+ JS::AutoCheckCannotGC autoCannotGC;
+
+ JS::ubi::Node node = script;
+ if (node) {
+ args.rval().setNumber(uint32_t(node.size(mallocSizeOf)));
+ } else {
+ args.rval().setUndefined();
+ }
+ }
+ return true;
+}
+
+static bool SetImmutablePrototype(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "setImmutablePrototype: object expected");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+
+ bool succeeded;
+ if (!js::SetImmutablePrototype(cx, obj, &succeeded)) {
+ return false;
+ }
+
+ args.rval().setBoolean(succeeded);
+ return true;
+}
+
+#ifdef DEBUG
+static bool DumpStringRepresentation(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedString str(cx, ToString(cx, args.get(0)));
+ if (!str) {
+ return false;
+ }
+
+ Fprinter out(stderr);
+ str->dumpRepresentation(out, 0);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetStringRepresentation(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedString str(cx, ToString(cx, args.get(0)));
+ if (!str) {
+ return false;
+ }
+
+ Sprinter out(cx, true);
+ if (!out.init()) {
+ return false;
+ }
+ str->dumpRepresentation(out, 0);
+
+ if (out.hadOutOfMemory()) {
+ return false;
+ }
+
+ JSString* rep = JS_NewStringCopyN(cx, out.string(), out.getOffset());
+ if (!rep) {
+ return false;
+ }
+
+ args.rval().setString(rep);
+ return true;
+}
+
+#endif
+
+static bool ParseCompileOptionsForModule(JSContext* cx,
+ JS::CompileOptions& options,
+ JS::Handle<JSObject*> opts,
+ bool& isModule) {
+ JS::Rooted<JS::Value> v(cx);
+
+ if (!JS_GetProperty(cx, opts, "module", &v)) {
+ return false;
+ }
+ if (!v.isUndefined() && JS::ToBoolean(v)) {
+ options.setModule();
+ isModule = true;
+ } else {
+ isModule = false;
+ }
+
+ return true;
+}
+
+static bool ParseCompileOptionsForInstantiate(JSContext* cx,
+ JS::CompileOptions& options,
+ JS::Handle<JSObject*> opts,
+ bool& prepareForInstantiate) {
+ JS::Rooted<JS::Value> v(cx);
+
+ if (!JS_GetProperty(cx, opts, "prepareForInstantiate", &v)) {
+ return false;
+ }
+ if (!v.isUndefined()) {
+ prepareForInstantiate = JS::ToBoolean(v);
+ } else {
+ prepareForInstantiate = false;
+ }
+
+ return true;
+}
+
+static bool CompileToStencil(JSContext* cx, uint32_t argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "compileToStencil", 1)) {
+ return false;
+ }
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
+ return false;
+ }
+
+ RootedString src(cx, ToString<CanGC>(cx, args[0]));
+ if (!src) {
+ return false;
+ }
+
+ /* Linearize the string to obtain a char16_t* range. */
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, src)) {
+ return false;
+ }
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
+ return false;
+ }
+
+ CompileOptions options(cx);
+ RootedString displayURL(cx);
+ RootedString sourceMapURL(cx);
+ UniqueChars fileNameBytes;
+ bool isModule = false;
+ bool prepareForInstantiate = false;
+ if (args.length() == 2) {
+ if (!args[1].isObject()) {
+ JS_ReportErrorASCII(
+ cx, "compileToStencil: The 2nd argument must be an object");
+ return false;
+ }
+
+ RootedObject opts(cx, &args[1].toObject());
+
+ if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
+ return false;
+ }
+ if (!ParseCompileOptionsForModule(cx, options, opts, isModule)) {
+ return false;
+ }
+ if (!ParseCompileOptionsForInstantiate(cx, options, opts,
+ prepareForInstantiate)) {
+ return false;
+ }
+ if (!js::ParseSourceOptions(cx, opts, &displayURL, &sourceMapURL)) {
+ return false;
+ }
+ }
+
+ AutoReportFrontendContext fc(cx);
+ RefPtr<JS::Stencil> stencil;
+ JS::CompilationStorage compileStorage;
+ if (isModule) {
+ stencil =
+ JS::CompileModuleScriptToStencil(&fc, options, srcBuf, compileStorage);
+ } else {
+ stencil =
+ JS::CompileGlobalScriptToStencil(&fc, options, srcBuf, compileStorage);
+ }
+ if (!stencil) {
+ return false;
+ }
+
+ if (!SetSourceOptions(cx, &fc, stencil->source, displayURL, sourceMapURL)) {
+ return false;
+ }
+
+ JS::InstantiationStorage storage;
+ if (prepareForInstantiate) {
+ if (!JS::PrepareForInstantiate(&fc, compileStorage, *stencil, storage)) {
+ return false;
+ }
+ }
+
+ Rooted<js::StencilObject*> stencilObj(
+ cx, js::StencilObject::create(cx, std::move(stencil)));
+ if (!stencilObj) {
+ return false;
+ }
+
+ args.rval().setObject(*stencilObj);
+ return true;
+}
+
+static bool EvalStencil(JSContext* cx, uint32_t argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "evalStencil", 1)) {
+ return false;
+ }
+
+ /* Prepare the input byte array. */
+ if (!args[0].isObject() || !args[0].toObject().is<js::StencilObject>()) {
+ JS_ReportErrorASCII(cx, "evalStencil: Stencil object expected");
+ return false;
+ }
+ Rooted<js::StencilObject*> stencilObj(
+ cx, &args[0].toObject().as<js::StencilObject>());
+
+ if (stencilObj->stencil()->isModule()) {
+ JS_ReportErrorASCII(cx,
+ "evalStencil: Module stencil cannot be evaluated. Use "
+ "instantiateModuleStencil instead");
+ return false;
+ }
+
+ CompileOptions options(cx);
+ UniqueChars fileNameBytes;
+ Rooted<JS::Value> privateValue(cx);
+ Rooted<JSString*> elementAttributeName(cx);
+ if (args.length() == 2) {
+ if (!args[1].isObject()) {
+ JS_ReportErrorASCII(cx,
+ "evalStencil: The 2nd argument must be an object");
+ return false;
+ }
+
+ RootedObject opts(cx, &args[1].toObject());
+
+ if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
+ return false;
+ }
+ if (!js::ParseDebugMetadata(cx, opts, &privateValue,
+ &elementAttributeName)) {
+ return false;
+ }
+ }
+
+ bool useDebugMetadata = !privateValue.isUndefined() || elementAttributeName;
+
+ JS::InstantiateOptions instantiateOptions(options);
+ if (useDebugMetadata) {
+ instantiateOptions.hideScriptFromDebugger = true;
+ }
+ RootedScript script(cx, JS::InstantiateGlobalStencil(cx, instantiateOptions,
+ stencilObj->stencil()));
+ if (!script) {
+ return false;
+ }
+
+ if (useDebugMetadata) {
+ instantiateOptions.hideScriptFromDebugger = false;
+ if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, privateValue,
+ elementAttributeName, nullptr, nullptr)) {
+ return false;
+ }
+ }
+
+ RootedValue retVal(cx);
+ if (!JS_ExecuteScript(cx, script, &retVal)) {
+ return false;
+ }
+
+ args.rval().set(retVal);
+ return true;
+}
+
+static bool CompileToStencilXDR(JSContext* cx, uint32_t argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "compileToStencilXDR", 1)) {
+ return false;
+ }
+
+ RootedString src(cx, ToString<CanGC>(cx, args[0]));
+ if (!src) {
+ return false;
+ }
+
+ /* Linearize the string to obtain a char16_t* range. */
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, src)) {
+ return false;
+ }
+ JS::SourceText<char16_t> srcBuf;
+ if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
+ return false;
+ }
+
+ CompileOptions options(cx);
+ RootedString displayURL(cx);
+ RootedString sourceMapURL(cx);
+ UniqueChars fileNameBytes;
+ bool isModule = false;
+ if (args.length() == 2) {
+ if (!args[1].isObject()) {
+ JS_ReportErrorASCII(
+ cx, "compileToStencilXDR: The 2nd argument must be an object");
+ return false;
+ }
+
+ RootedObject opts(cx, &args[1].toObject());
+
+ if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
+ return false;
+ }
+ if (!ParseCompileOptionsForModule(cx, options, opts, isModule)) {
+ return false;
+ }
+ if (!js::ParseSourceOptions(cx, opts, &displayURL, &sourceMapURL)) {
+ return false;
+ }
+ }
+
+ /* Compile the script text to stencil. */
+ AutoReportFrontendContext fc(cx);
+ frontend::NoScopeBindingCache scopeCache;
+ Rooted<frontend::CompilationInput> input(cx,
+ frontend::CompilationInput(options));
+ UniquePtr<frontend::ExtensibleCompilationStencil> stencil;
+ if (isModule) {
+ stencil = frontend::ParseModuleToExtensibleStencil(
+ cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf);
+ } else {
+ stencil = frontend::CompileGlobalScriptToExtensibleStencil(
+ cx, &fc, input.get(), &scopeCache, srcBuf, ScopeKind::Global);
+ }
+ if (!stencil) {
+ return false;
+ }
+
+ if (!SetSourceOptions(cx, &fc, stencil->source, displayURL, sourceMapURL)) {
+ return false;
+ }
+
+ /* Serialize the stencil to XDR. */
+ JS::TranscodeBuffer xdrBytes;
+ {
+ frontend::BorrowingCompilationStencil borrowingStencil(*stencil);
+ bool succeeded = false;
+ if (!borrowingStencil.serializeStencils(cx, input.get(), xdrBytes,
+ &succeeded)) {
+ return false;
+ }
+ if (!succeeded) {
+ fc.clearAutoReport();
+ JS_ReportErrorASCII(cx, "Encoding failure");
+ return false;
+ }
+ }
+
+ Rooted<StencilXDRBufferObject*> xdrObj(
+ cx,
+ StencilXDRBufferObject::create(cx, xdrBytes.begin(), xdrBytes.length()));
+ if (!xdrObj) {
+ return false;
+ }
+
+ args.rval().setObject(*xdrObj);
+ return true;
+}
+
+static bool EvalStencilXDR(JSContext* cx, uint32_t argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "evalStencilXDR", 1)) {
+ return false;
+ }
+
+ /* Prepare the input byte array. */
+ if (!args[0].isObject() || !args[0].toObject().is<StencilXDRBufferObject>()) {
+ JS_ReportErrorASCII(cx, "evalStencilXDR: stencil XDR object expected");
+ return false;
+ }
+ Rooted<StencilXDRBufferObject*> xdrObj(
+ cx, &args[0].toObject().as<StencilXDRBufferObject>());
+ MOZ_ASSERT(xdrObj->hasBuffer());
+
+ CompileOptions options(cx);
+ UniqueChars fileNameBytes;
+ Rooted<JS::Value> privateValue(cx);
+ Rooted<JSString*> elementAttributeName(cx);
+ if (args.length() == 2) {
+ if (!args[1].isObject()) {
+ JS_ReportErrorASCII(cx,
+ "evalStencilXDR: The 2nd argument must be an object");
+ return false;
+ }
+
+ RootedObject opts(cx, &args[1].toObject());
+
+ if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
+ return false;
+ }
+ if (!js::ParseDebugMetadata(cx, opts, &privateValue,
+ &elementAttributeName)) {
+ return false;
+ }
+ }
+
+ /* Prepare the CompilationStencil for decoding. */
+ AutoReportFrontendContext fc(cx);
+ frontend::CompilationStencil stencil(nullptr);
+
+ /* Deserialize the stencil from XDR. */
+ JS::TranscodeRange xdrRange(xdrObj->buffer(), xdrObj->bufferLength());
+ bool succeeded = false;
+ if (!stencil.deserializeStencils(&fc, options, xdrRange, &succeeded)) {
+ return false;
+ }
+ if (!succeeded) {
+ fc.clearAutoReport();
+ JS_ReportErrorASCII(cx, "Decoding failure");
+ return false;
+ }
+
+ if (stencil.isModule()) {
+ fc.clearAutoReport();
+ JS_ReportErrorASCII(cx,
+ "evalStencilXDR: Module stencil cannot be evaluated. "
+ "Use instantiateModuleStencilXDR instead");
+ return false;
+ }
+
+ bool useDebugMetadata = !privateValue.isUndefined() || elementAttributeName;
+
+ JS::InstantiateOptions instantiateOptions(options);
+ if (useDebugMetadata) {
+ instantiateOptions.hideScriptFromDebugger = true;
+ }
+ RootedScript script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, &stencil));
+ if (!script) {
+ return false;
+ }
+
+ if (useDebugMetadata) {
+ instantiateOptions.hideScriptFromDebugger = false;
+ if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, privateValue,
+ elementAttributeName, nullptr, nullptr)) {
+ return false;
+ }
+ }
+
+ RootedValue retVal(cx);
+ if (!JS_ExecuteScript(cx, script, &retVal)) {
+ return false;
+ }
+
+ args.rval().set(retVal);
+ return true;
+}
+
+static bool GetExceptionInfo(JSContext* cx, uint32_t argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "getExceptionInfo", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "getExceptionInfo: expected function argument");
+ return false;
+ }
+
+ RootedValue rval(cx);
+ if (JS_CallFunctionValue(cx, nullptr, args[0], JS::HandleValueArray::empty(),
+ &rval)) {
+ // Function didn't throw.
+ args.rval().setNull();
+ return true;
+ }
+
+ // We currently don't support interrupts or forced returns.
+ if (!cx->isExceptionPending()) {
+ JS_ReportErrorASCII(cx, "getExceptionInfo: unsupported exception status");
+ return false;
+ }
+
+ RootedValue excVal(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!GetAndClearExceptionAndStack(cx, &excVal, &stack)) {
+ return false;
+ }
+
+ RootedValue stackVal(cx);
+ if (stack) {
+ RootedString stackString(cx);
+ if (!BuildStackString(cx, cx->realm()->principals(), stack, &stackString)) {
+ return false;
+ }
+ stackVal.setString(stackString);
+ } else {
+ stackVal.setNull();
+ }
+
+ RootedObject obj(cx, NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "exception", excVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "stack", stackVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+class AllocationMarkerObject : public NativeObject {
+ public:
+ static const JSClass class_;
+};
+
+const JSClass AllocationMarkerObject::class_ = {"AllocationMarker"};
+
+static bool AllocationMarker(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool allocateInsideNursery = true;
+ if (args.length() > 0 && args[0].isObject()) {
+ RootedObject options(cx, &args[0].toObject());
+
+ RootedValue nurseryVal(cx);
+ if (!JS_GetProperty(cx, options, "nursery", &nurseryVal)) {
+ return false;
+ }
+ allocateInsideNursery = ToBoolean(nurseryVal);
+ }
+
+ JSObject* obj =
+ allocateInsideNursery
+ ? NewObjectWithGivenProto<AllocationMarkerObject>(cx, nullptr)
+ : NewTenuredObjectWithGivenProto<AllocationMarkerObject>(cx, nullptr);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+namespace gcCallback {
+
+struct MajorGC {
+ int32_t depth;
+ int32_t phases;
+};
+
+static void majorGC(JSContext* cx, JSGCStatus status, JS::GCReason reason,
+ void* data) {
+ auto info = static_cast<MajorGC*>(data);
+ if (!(info->phases & (1 << status))) {
+ return;
+ }
+
+ if (info->depth > 0) {
+ info->depth--;
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
+ info->depth++;
+ }
+}
+
+struct MinorGC {
+ int32_t phases;
+ bool active;
+};
+
+static void minorGC(JSContext* cx, JSGCStatus status, JS::GCReason reason,
+ void* data) {
+ auto info = static_cast<MinorGC*>(data);
+ if (!(info->phases & (1 << status))) {
+ return;
+ }
+
+ if (info->active) {
+ info->active = false;
+ if (cx->zone() && !cx->zone()->isAtomsZone()) {
+ cx->runtime()->gc.evictNursery(JS::GCReason::DEBUG_GC);
+ }
+ info->active = true;
+ }
+}
+
+// Process global, should really be runtime-local.
+static MajorGC majorGCInfo;
+static MinorGC minorGCInfo;
+
+static void enterNullRealm(JSContext* cx, JSGCStatus status,
+ JS::GCReason reason, void* data) {
+ JSAutoNullableRealm enterRealm(cx, nullptr);
+}
+
+} /* namespace gcCallback */
+
+static bool SetGCCallback(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedObject opts(cx, ToObject(cx, args[0]));
+ if (!opts) {
+ return false;
+ }
+
+ RootedValue v(cx);
+ if (!JS_GetProperty(cx, opts, "action", &v)) {
+ return false;
+ }
+
+ JSString* str = JS::ToString(cx, v);
+ if (!str) {
+ return false;
+ }
+ Rooted<JSLinearString*> action(cx, str->ensureLinear(cx));
+ if (!action) {
+ return false;
+ }
+
+ int32_t phases = 0;
+ if (StringEqualsLiteral(action, "minorGC") ||
+ StringEqualsLiteral(action, "majorGC")) {
+ if (!JS_GetProperty(cx, opts, "phases", &v)) {
+ return false;
+ }
+ if (v.isUndefined()) {
+ phases = (1 << JSGC_END);
+ } else {
+ JSString* str = JS::ToString(cx, v);
+ if (!str) {
+ return false;
+ }
+ JSLinearString* phasesStr = str->ensureLinear(cx);
+ if (!phasesStr) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(phasesStr, "begin")) {
+ phases = (1 << JSGC_BEGIN);
+ } else if (StringEqualsLiteral(phasesStr, "end")) {
+ phases = (1 << JSGC_END);
+ } else if (StringEqualsLiteral(phasesStr, "both")) {
+ phases = (1 << JSGC_BEGIN) | (1 << JSGC_END);
+ } else {
+ JS_ReportErrorASCII(cx, "Invalid callback phase");
+ return false;
+ }
+ }
+ }
+
+ if (StringEqualsLiteral(action, "minorGC")) {
+ gcCallback::minorGCInfo.phases = phases;
+ gcCallback::minorGCInfo.active = true;
+ JS_SetGCCallback(cx, gcCallback::minorGC, &gcCallback::minorGCInfo);
+ } else if (StringEqualsLiteral(action, "majorGC")) {
+ if (!JS_GetProperty(cx, opts, "depth", &v)) {
+ return false;
+ }
+ int32_t depth = 1;
+ if (!v.isUndefined()) {
+ if (!ToInt32(cx, v, &depth)) {
+ return false;
+ }
+ }
+ if (depth < 0) {
+ JS_ReportErrorASCII(cx, "Nesting depth cannot be negative");
+ return false;
+ }
+ if (depth + gcstats::MAX_PHASE_NESTING >
+ gcstats::Statistics::MAX_SUSPENDED_PHASES) {
+ JS_ReportErrorASCII(cx, "Nesting depth too large, would overflow");
+ return false;
+ }
+
+ gcCallback::majorGCInfo.phases = phases;
+ gcCallback::majorGCInfo.depth = depth;
+ JS_SetGCCallback(cx, gcCallback::majorGC, &gcCallback::majorGCInfo);
+ } else if (StringEqualsLiteral(action, "enterNullRealm")) {
+ JS_SetGCCallback(cx, gcCallback::enterNullRealm, nullptr);
+ } else {
+ JS_ReportErrorASCII(cx, "Unknown GC callback action");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef DEBUG
+static bool EnqueueMark(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ gc::GCRuntime* gc = &cx->runtime()->gc;
+
+ if (args.get(0).isString()) {
+ RootedString val(cx, args[0].toString());
+ if (!val->ensureLinear(cx)) {
+ return false;
+ }
+ if (!gc->appendTestMarkQueue(StringValue(val))) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ } else if (args.get(0).isObject()) {
+ if (!gc->appendTestMarkQueue(args[0])) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ } else {
+ JS_ReportErrorASCII(cx, "Argument must be a string or object");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetMarkQueue(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ const auto& queue = cx->runtime()->gc.getTestMarkQueue();
+
+ RootedObject result(cx, JS::NewArrayObject(cx, queue.length()));
+ if (!result) {
+ return false;
+ }
+ for (size_t i = 0; i < queue.length(); i++) {
+ RootedValue val(cx, queue[i]);
+ if (!JS_WrapValue(cx, &val)) {
+ return false;
+ }
+ if (!JS_SetElement(cx, result, i, val)) {
+ return false;
+ }
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+static bool ClearMarkQueue(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ cx->runtime()->gc.clearTestMarkQueue();
+ args.rval().setUndefined();
+ return true;
+}
+#endif // DEBUG
+
+static bool NurseryStringsEnabled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(cx->zone()->allocNurseryStrings());
+ return true;
+}
+
+static bool IsNurseryAllocated(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isGCThing()) {
+ JS_ReportErrorASCII(
+ cx, "The function takes one argument, which must be a GC thing");
+ return false;
+ }
+
+ args.rval().setBoolean(IsInsideNursery(args[0].toGCThing()));
+ return true;
+}
+
+static bool GetLcovInfo(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() > 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!coverage::IsLCovEnabled()) {
+ JS_ReportErrorASCII(cx, "Coverage not enabled for process.");
+ return false;
+ }
+
+ RootedObject global(cx);
+ if (args.hasDefined(0)) {
+ global = ToObject(cx, args[0]);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "Permission denied to access global");
+ return false;
+ }
+ global = CheckedUnwrapDynamic(global, cx, /* stopAtWindowProxy = */ false);
+ if (!global) {
+ ReportAccessDenied(cx);
+ return false;
+ }
+ if (!global->is<GlobalObject>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a global object");
+ return false;
+ }
+ } else {
+ global = JS::CurrentGlobalOrNull(cx);
+ }
+
+ size_t length = 0;
+ UniqueChars content;
+ {
+ AutoRealm ar(cx, global);
+ content = js::GetCodeCoverageSummary(cx, &length);
+ }
+
+ if (!content) {
+ return false;
+ }
+
+ JSString* str =
+ JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(content.get(), length));
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+#ifdef DEBUG
+static bool SetRNGState(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "SetRNGState", 2)) {
+ return false;
+ }
+
+ double d0;
+ if (!ToNumber(cx, args[0], &d0)) {
+ return false;
+ }
+
+ double d1;
+ if (!ToNumber(cx, args[1], &d1)) {
+ return false;
+ }
+
+ uint64_t seed0 = static_cast<uint64_t>(d0);
+ uint64_t seed1 = static_cast<uint64_t>(d1);
+
+ if (seed0 == 0 && seed1 == 0) {
+ JS_ReportErrorASCII(cx, "RNG requires non-zero seed");
+ return false;
+ }
+
+ cx->realm()->getOrCreateRandomNumberGenerator().setState(seed0, seed1);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool GetTimeZone(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 0) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+#ifndef __wasi__
+ auto getTimeZone = [](std::time_t* now) -> const char* {
+ std::tm local{};
+# if defined(_WIN32)
+ _tzset();
+ if (localtime_s(&local, now) == 0) {
+ return _tzname[local.tm_isdst > 0];
+ }
+# else
+ tzset();
+# if defined(HAVE_LOCALTIME_R)
+ if (localtime_r(now, &local)) {
+# else
+ std::tm* localtm = std::localtime(now);
+ if (localtm) {
+ *local = *localtm;
+# endif /* HAVE_LOCALTIME_R */
+
+# if defined(HAVE_TM_ZONE_TM_GMTOFF)
+ return local.tm_zone;
+# else
+ return tzname[local.tm_isdst > 0];
+# endif /* HAVE_TM_ZONE_TM_GMTOFF */
+ }
+# endif /* _WIN32 */
+ return nullptr;
+ };
+
+ std::time_t now = std::time(nullptr);
+ if (now != static_cast<std::time_t>(-1)) {
+ if (const char* tz = getTimeZone(&now)) {
+ return ReturnStringCopy(cx, args, tz);
+ }
+ }
+#endif /* __wasi__ */
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool SetTimeZone(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 1) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isString() && !args[0].isUndefined()) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument should be a string or undefined");
+ return false;
+ }
+
+#ifndef __wasi__
+ auto setTimeZone = [](const char* value) {
+# if defined(_WIN32)
+ return _putenv_s("TZ", value) == 0;
+# else
+ return setenv("TZ", value, true) == 0;
+# endif /* _WIN32 */
+ };
+
+ auto unsetTimeZone = []() {
+# if defined(_WIN32)
+ return _putenv_s("TZ", "") == 0;
+# else
+ return unsetenv("TZ") == 0;
+# endif /* _WIN32 */
+ };
+
+ if (args[0].isString() && !args[0].toString()->empty()) {
+ Rooted<JSLinearString*> str(cx, args[0].toString()->ensureLinear(cx));
+ if (!str) {
+ return false;
+ }
+
+ if (!StringIsAscii(str)) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument contains non-ASCII characters");
+ return false;
+ }
+
+ UniqueChars timeZone = JS_EncodeStringToASCII(cx, str);
+ if (!timeZone) {
+ return false;
+ }
+
+ if (!setTimeZone(timeZone.get())) {
+ JS_ReportErrorASCII(cx, "Failed to set 'TZ' environment variable");
+ return false;
+ }
+ } else {
+ if (!unsetTimeZone()) {
+ JS_ReportErrorASCII(cx, "Failed to unset 'TZ' environment variable");
+ return false;
+ }
+ }
+
+# if defined(_WIN32)
+ _tzset();
+# else
+ tzset();
+# endif /* _WIN32 */
+
+ JS::ResetTimeZone();
+
+#endif /* __wasi__ */
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetCoreCount(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 0) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ args.rval().setInt32(GetCPUCount());
+ return true;
+}
+
+static bool GetDefaultLocale(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 0) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ UniqueChars locale = JS_GetDefaultLocale(cx);
+ if (!locale) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_DEFAULT_LOCALE_ERROR);
+ return false;
+ }
+
+ return ReturnStringCopy(cx, args, locale.get());
+}
+
+static bool SetDefaultLocale(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 1) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isString() && !args[0].isUndefined()) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument should be a string or undefined");
+ return false;
+ }
+
+ if (args[0].isString() && !args[0].toString()->empty()) {
+ Rooted<JSLinearString*> str(cx, args[0].toString()->ensureLinear(cx));
+ if (!str) {
+ return false;
+ }
+
+ if (!StringIsAscii(str)) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument contains non-ASCII characters");
+ return false;
+ }
+
+ UniqueChars locale = JS_EncodeStringToASCII(cx, str);
+ if (!locale) {
+ return false;
+ }
+
+ bool containsOnlyValidBCP47Characters =
+ mozilla::IsAsciiAlpha(locale[0]) &&
+ std::all_of(locale.get(), locale.get() + str->length(), [](auto c) {
+ return mozilla::IsAsciiAlphanumeric(c) || c == '-';
+ });
+
+ if (!containsOnlyValidBCP47Characters) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument should be a BCP47 language tag");
+ return false;
+ }
+
+ if (!JS_SetDefaultLocale(cx->runtime(), locale.get())) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ } else {
+ JS_ResetDefaultLocale(cx->runtime());
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef AFLFUZZ
+static bool AflLoop(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ uint32_t max_cnt;
+ if (!ToUint32(cx, args.get(0), &max_cnt)) {
+ return false;
+ }
+
+ args.rval().setBoolean(!!__AFL_LOOP(max_cnt));
+ return true;
+}
+#endif
+
+static bool MonotonicNow(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ double now;
+
+// The std::chrono symbols are too new to be present in STL on all platforms we
+// care about, so use raw POSIX clock APIs when it might be necessary.
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
+ auto ComputeNow = [](const timespec& ts) {
+ return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+ };
+
+ timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+ // Use a monotonic clock if available.
+ now = ComputeNow(ts);
+ } else {
+ // Use a realtime clock as fallback.
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
+ // Fail if no clock is available.
+ JS_ReportErrorASCII(cx, "can't retrieve system clock");
+ return false;
+ }
+
+ now = ComputeNow(ts);
+
+ // Manually enforce atomicity on a non-monotonic clock.
+ {
+ static mozilla::Atomic<bool, mozilla::ReleaseAcquire> spinLock;
+ while (!spinLock.compareExchange(false, true)) {
+ continue;
+ }
+
+ static double lastNow = -FLT_MAX;
+ now = lastNow = std::max(now, lastNow);
+
+ spinLock = false;
+ }
+ }
+#else
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ using std::chrono::steady_clock;
+ now = duration_cast<milliseconds>(steady_clock::now().time_since_epoch())
+ .count();
+#endif // XP_UNIX && !XP_DARWIN
+
+ args.rval().setNumber(now);
+ return true;
+}
+
+static bool TimeSinceCreation(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ double when =
+ (mozilla::TimeStamp::Now() - mozilla::TimeStamp::ProcessCreation())
+ .ToMilliseconds();
+ args.rval().setNumber(when);
+ return true;
+}
+
+static bool GetInnerMostEnvironmentObject(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ FrameIter iter(cx);
+ if (iter.done()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ args.rval().setObjectOrNull(iter.environmentChain(cx));
+ return true;
+}
+
+static bool GetEnclosingEnvironmentObject(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getEnclosingEnvironmentObject", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ JSObject* envObj = &args[0].toObject();
+
+ if (envObj->is<EnvironmentObject>()) {
+ EnvironmentObject* env = &envObj->as<EnvironmentObject>();
+ args.rval().setObject(env->enclosingEnvironment());
+ return true;
+ }
+
+ if (envObj->is<DebugEnvironmentProxy>()) {
+ DebugEnvironmentProxy* envProxy = &envObj->as<DebugEnvironmentProxy>();
+ args.rval().setObject(envProxy->enclosingEnvironment());
+ return true;
+ }
+
+ args.rval().setNull();
+ return true;
+}
+
+static bool GetEnvironmentObjectType(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getEnvironmentObjectType", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ JSObject* envObj = &args[0].toObject();
+
+ if (envObj->is<EnvironmentObject>()) {
+ EnvironmentObject* env = &envObj->as<EnvironmentObject>();
+ JSString* str = JS_NewStringCopyZ(cx, env->typeString());
+ args.rval().setString(str);
+ return true;
+ }
+ if (envObj->is<DebugEnvironmentProxy>()) {
+ DebugEnvironmentProxy* envProxy = &envObj->as<DebugEnvironmentProxy>();
+ EnvironmentObject* env = &envProxy->environment();
+ char buf[256] = {'\0'};
+ SprintfLiteral(buf, "[DebugProxy] %s", env->typeString());
+ JSString* str = JS_NewStringCopyZ(cx, buf);
+ args.rval().setString(str);
+ return true;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GetErrorNotes(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getErrorNotes", 1)) {
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<ErrorObject>()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ JSErrorReport* report = args[0].toObject().as<ErrorObject>().getErrorReport();
+ if (!report) {
+ args.rval().setNull();
+ return true;
+ }
+
+ RootedObject notesArray(cx, CreateErrorNotesArray(cx, report));
+ if (!notesArray) {
+ return false;
+ }
+
+ args.rval().setObject(*notesArray);
+ return true;
+}
+
+static bool IsConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1) {
+ args.rval().setBoolean(false);
+ } else {
+ args.rval().setBoolean(IsConstructor(args[0]));
+ }
+ return true;
+}
+
+static bool SetTimeResolution(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.requireAtLeast(cx, "setTimeResolution", 2)) {
+ return false;
+ }
+
+ if (!args[0].isInt32()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be an Int32.");
+ return false;
+ }
+ int32_t resolution = args[0].toInt32();
+
+ if (!args[1].isBoolean()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument must be a Boolean");
+ return false;
+ }
+ bool jitter = args[1].toBoolean();
+
+ JS::SetTimeResolutionUsec(resolution, jitter);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool ScriptedCallerGlobal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, JS::GetScriptedCallerGlobal(cx));
+ if (!obj) {
+ args.rval().setNull();
+ return true;
+ }
+
+ obj = ToWindowProxyIfWindow(obj);
+
+ if (!cx->compartment()->wrap(cx, &obj)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool ObjectGlobal(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.get(0).isObject()) {
+ ReportUsageErrorASCII(cx, callee, "Argument must be an object");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ if (IsCrossCompartmentWrapper(obj)) {
+ args.rval().setNull();
+ return true;
+ }
+
+ obj = ToWindowProxyIfWindow(&obj->nonCCWGlobal());
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool IsSameCompartment(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.get(0).isObject() || !args.get(1).isObject()) {
+ ReportUsageErrorASCII(cx, callee, "Both arguments must be objects");
+ return false;
+ }
+
+ RootedObject obj1(cx, UncheckedUnwrap(&args[0].toObject()));
+ RootedObject obj2(cx, UncheckedUnwrap(&args[1].toObject()));
+
+ args.rval().setBoolean(obj1->compartment() == obj2->compartment());
+ return true;
+}
+
+static bool FirstGlobalInCompartment(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.get(0).isObject()) {
+ ReportUsageErrorASCII(cx, callee, "Argument must be an object");
+ return false;
+ }
+
+ RootedObject obj(cx, UncheckedUnwrap(&args[0].toObject()));
+ obj = ToWindowProxyIfWindow(GetFirstGlobalInCompartment(obj->compartment()));
+
+ if (!cx->compartment()->wrap(cx, &obj)) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool AssertCorrectRealm(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_RELEASE_ASSERT(cx->realm() == args.callee().as<JSFunction>().realm());
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool GlobalLexicals(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<GlobalLexicalEnvironmentObject*> globalLexical(
+ cx, &cx->global()->lexicalEnvironment());
+
+ RootedIdVector props(cx);
+ if (!GetPropertyKeys(cx, globalLexical, JSITER_HIDDEN, &props)) {
+ return false;
+ }
+
+ RootedObject res(cx, JS_NewPlainObject(cx));
+ if (!res) {
+ return false;
+ }
+
+ RootedValue val(cx);
+ for (size_t i = 0; i < props.length(); i++) {
+ HandleId id = props[i];
+ if (!JS_GetPropertyById(cx, globalLexical, id, &val)) {
+ return false;
+ }
+ if (val.isMagic(JS_UNINITIALIZED_LEXICAL)) {
+ continue;
+ }
+ if (!JS_DefinePropertyById(cx, res, id, val, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ args.rval().setObject(*res);
+ return true;
+}
+
+static bool EncodeAsUtf8InBuffer(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "encodeAsUtf8InBuffer", 2)) {
+ return false;
+ }
+
+ RootedObject callee(cx, &args.callee());
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ // Create the amounts array early so that the raw pointer into Uint8Array
+ // data has as short a lifetime as possible
+ Rooted<ArrayObject*> array(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!array) {
+ return false;
+ }
+ array->ensureDenseInitializedLength(0, 2);
+
+ JSObject* obj = args[1].isObject() ? &args[1].toObject() : nullptr;
+ Rooted<JS::Uint8Array> view(cx, JS::Uint8Array::unwrap(obj));
+ if (!view) {
+ ReportUsageErrorASCII(cx, callee, "Second argument must be a Uint8Array");
+ return false;
+ }
+
+ size_t length;
+ bool isSharedMemory = false;
+ uint8_t* data = nullptr;
+ {
+ // The hazard analysis does not track the data pointer, so it can neither
+ // tell that `data` is dead if ReportUsageErrorASCII is called, nor that
+ // its live range ends at the call to AsWritableChars(). Construct a
+ // temporary scope to hide from the analysis. This should really be replaced
+ // with a safer mechanism.
+ JS::AutoCheckCannotGC nogc(cx);
+ if (!view.isDetached()) {
+ data = view.get().getLengthAndData(&length, &isSharedMemory, nogc);
+ }
+ }
+
+ if (isSharedMemory || // exclude views of SharedArrayBuffers
+ !data) { // exclude views of detached ArrayBuffers
+ ReportUsageErrorASCII(
+ cx, callee,
+ "Second argument must be an unshared, non-detached Uint8Array");
+ return false;
+ }
+
+ Maybe<std::tuple<size_t, size_t>> amounts =
+ JS_EncodeStringToUTF8BufferPartial(cx, args[0].toString(),
+ AsWritableChars(Span(data, length)));
+ if (!amounts) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ auto [unitsRead, bytesWritten] = *amounts;
+
+ array->initDenseElement(0, Int32Value(AssertedCast<int32_t>(unitsRead)));
+ array->initDenseElement(1, Int32Value(AssertedCast<int32_t>(bytesWritten)));
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+JSScript* js::TestingFunctionArgumentToScript(
+ JSContext* cx, HandleValue v, JSFunction** funp /* = nullptr */) {
+ if (v.isString()) {
+ // To convert a string to a script, compile it. Parse it as an ES6 Program.
+ Rooted<JSString*> str(cx, v.toString());
+ AutoStableStringChars linearChars(cx);
+ if (!linearChars.initTwoByte(cx, str)) {
+ return nullptr;
+ }
+ SourceText<char16_t> source;
+ if (!source.initMaybeBorrowed(cx, linearChars)) {
+ return nullptr;
+ }
+
+ CompileOptions options(cx);
+ return JS::Compile(cx, options, source);
+ }
+
+ RootedFunction fun(cx, JS_ValueToFunction(cx, v));
+ if (!fun) {
+ return nullptr;
+ }
+
+ if (!fun->isInterpreted()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TESTING_SCRIPTS_ONLY);
+ return nullptr;
+ }
+
+ JSScript* script = JSFunction::getOrCreateScript(cx, fun);
+ if (!script) {
+ return nullptr;
+ }
+
+ if (funp) {
+ *funp = fun;
+ }
+
+ return script;
+}
+
+static bool BaselineCompile(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ RootedScript script(cx);
+ if (args.length() == 0) {
+ NonBuiltinScriptFrameIter iter(cx);
+ if (iter.done()) {
+ ReportUsageErrorASCII(cx, callee,
+ "no script argument and no script caller");
+ return false;
+ }
+ script = iter.script();
+ } else {
+ script = TestingFunctionArgumentToScript(cx, args[0]);
+ if (!script) {
+ return false;
+ }
+ }
+
+ bool forceDebug = false;
+ if (args.length() > 1) {
+ if (args.length() > 2) {
+ ReportUsageErrorASCII(cx, callee, "too many arguments");
+ return false;
+ }
+ if (!args[1].isBoolean() && !args[1].isUndefined()) {
+ ReportUsageErrorASCII(
+ cx, callee, "forceDebugInstrumentation argument should be boolean");
+ return false;
+ }
+ forceDebug = ToBoolean(args[1]);
+ }
+
+ const char* returnedStr = nullptr;
+ do {
+ // In order to check for differential behaviour, baselineCompile should have
+ // the same output whether --no-baseline is used or not.
+ if (js::SupportDifferentialTesting()) {
+ returnedStr = "skipped (differential testing)";
+ break;
+ }
+
+ AutoRealm ar(cx, script);
+ if (script->hasBaselineScript()) {
+ if (forceDebug && !script->baselineScript()->hasDebugInstrumentation()) {
+ // There isn't an easy way to do this for a script that might be on
+ // stack right now. See
+ // js::jit::RecompileOnStackBaselineScriptsForDebugMode.
+ ReportUsageErrorASCII(
+ cx, callee, "unsupported case: recompiling script for debug mode");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+ }
+
+ if (!jit::IsBaselineJitEnabled(cx)) {
+ returnedStr = "baseline disabled";
+ break;
+ }
+ if (!script->canBaselineCompile()) {
+ returnedStr = "can't compile";
+ break;
+ }
+ if (!cx->realm()->ensureJitRealmExists(cx)) {
+ return false;
+ }
+
+ jit::MethodStatus status = jit::BaselineCompile(cx, script, forceDebug);
+ switch (status) {
+ case jit::Method_Error:
+ return false;
+ case jit::Method_CantCompile:
+ returnedStr = "can't compile";
+ break;
+ case jit::Method_Skipped:
+ returnedStr = "skipped";
+ break;
+ case jit::Method_Compiled:
+ args.rval().setUndefined();
+ }
+ } while (false);
+
+ if (returnedStr) {
+ return ReturnStringCopy(cx, args, returnedStr);
+ }
+
+ return true;
+}
+
+static bool ClearKeptObjects(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JS::ClearKeptObjects(cx);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool NumberToDouble(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "numberToDouble", 1)) {
+ return false;
+ }
+
+ if (!args[0].isNumber()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "argument must be a number");
+ return false;
+ }
+
+ args.rval().setDouble(args[0].toNumber());
+ return true;
+}
+
+static bool GetICUOptions(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info) {
+ return false;
+ }
+
+#ifdef JS_HAS_INTL_API
+ RootedString str(cx);
+
+ str = NewStringCopy<CanGC>(cx, mozilla::intl::ICU4CLibrary::GetVersion());
+ if (!str || !JS_DefineProperty(cx, info, "version", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ str = NewStringCopy<CanGC>(cx, mozilla::intl::String::GetUnicodeVersion());
+ if (!str || !JS_DefineProperty(cx, info, "unicode", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ str = NewStringCopyZ<CanGC>(cx, mozilla::intl::Locale::GetDefaultLocale());
+ if (!str || !JS_DefineProperty(cx, info, "locale", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ auto tzdataVersion = mozilla::intl::TimeZone::GetTZDataVersion();
+ if (tzdataVersion.isErr()) {
+ intl::ReportInternalError(cx, tzdataVersion.unwrapErr());
+ return false;
+ }
+
+ str = NewStringCopy<CanGC>(cx, tzdataVersion.unwrap());
+ if (!str || !JS_DefineProperty(cx, info, "tzdata", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buf(cx);
+
+ if (auto ok = DateTimeInfo::timeZoneId(DateTimeInfo::ShouldRFP::No, buf);
+ ok.isErr()) {
+ intl::ReportInternalError(cx, ok.unwrapErr());
+ return false;
+ }
+
+ str = buf.toString(cx);
+ if (!str || !JS_DefineProperty(cx, info, "timezone", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (auto ok = mozilla::intl::TimeZone::GetHostTimeZone(buf); ok.isErr()) {
+ intl::ReportInternalError(cx, ok.unwrapErr());
+ return false;
+ }
+
+ str = buf.toString(cx);
+ if (!str ||
+ !JS_DefineProperty(cx, info, "host-timezone", str, JSPROP_ENUMERATE)) {
+ return false;
+ }
+#endif
+
+ args.rval().setObject(*info);
+ return true;
+}
+
+static bool GetAvailableLocalesOf(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.requireAtLeast(cx, "getAvailableLocalesOf", 1)) {
+ return false;
+ }
+
+ HandleValue arg = args[0];
+ if (!arg.isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a string");
+ return false;
+ }
+
+ ArrayObject* result;
+#ifdef JS_HAS_INTL_API
+ using SupportedLocaleKind = js::intl::SharedIntlData::SupportedLocaleKind;
+
+ SupportedLocaleKind kind;
+ {
+ JSLinearString* typeStr = arg.toString()->ensureLinear(cx);
+ if (!typeStr) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(typeStr, "Collator")) {
+ kind = SupportedLocaleKind::Collator;
+ } else if (StringEqualsLiteral(typeStr, "DateTimeFormat")) {
+ kind = SupportedLocaleKind::DateTimeFormat;
+ } else if (StringEqualsLiteral(typeStr, "DisplayNames")) {
+ kind = SupportedLocaleKind::DisplayNames;
+ } else if (StringEqualsLiteral(typeStr, "ListFormat")) {
+ kind = SupportedLocaleKind::ListFormat;
+ } else if (StringEqualsLiteral(typeStr, "NumberFormat")) {
+ kind = SupportedLocaleKind::NumberFormat;
+ } else if (StringEqualsLiteral(typeStr, "PluralRules")) {
+ kind = SupportedLocaleKind::PluralRules;
+ } else if (StringEqualsLiteral(typeStr, "RelativeTimeFormat")) {
+ kind = SupportedLocaleKind::RelativeTimeFormat;
+ } else {
+ ReportUsageErrorASCII(cx, callee, "Unsupported Intl constructor name");
+ return false;
+ }
+ }
+
+ intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
+ result = sharedIntlData.availableLocalesOf(cx, kind);
+#else
+ result = NewDenseEmptyArray(cx);
+#endif
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+static bool IsSmallFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.requireAtLeast(cx, "IsSmallFunction", 1)) {
+ return false;
+ }
+
+ HandleValue arg = args[0];
+ if (!arg.isObject() || !arg.toObject().is<JSFunction>()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a function");
+ return false;
+ }
+
+ RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+ if (!fun->isInterpreted()) {
+ ReportUsageErrorASCII(cx, callee,
+ "First argument must be an interpreted function");
+ return false;
+ }
+
+ JSScript* script = JSFunction::getOrCreateScript(cx, fun);
+ if (!script) {
+ return false;
+ }
+
+ args.rval().setBoolean(jit::JitOptions.isSmallFunction(script));
+ return true;
+}
+
+static bool PCCountProfiling_Start(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::StartPCCountProfiling(cx);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool PCCountProfiling_Stop(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::StopPCCountProfiling(cx);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool PCCountProfiling_Purge(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::PurgePCCounts(cx);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool PCCountProfiling_ScriptCount(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ size_t length = JS::GetPCCountScriptCount(cx);
+
+ args.rval().setNumber(double(length));
+ return true;
+}
+
+static bool PCCountProfiling_ScriptSummary(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "summary", 1)) {
+ return false;
+ }
+
+ uint32_t index;
+ if (!JS::ToUint32(cx, args[0], &index)) {
+ return false;
+ }
+
+ JSString* str = JS::GetPCCountScriptSummary(cx, index);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool PCCountProfiling_ScriptContents(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "contents", 1)) {
+ return false;
+ }
+
+ uint32_t index;
+ if (!JS::ToUint32(cx, args[0], &index)) {
+ return false;
+ }
+
+ JSString* str = JS::GetPCCountScriptContents(cx, index);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool NukeCCW(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isObject() ||
+ !IsCrossCompartmentWrapper(&args[0].toObject())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARGS,
+ "nukeCCW");
+ return false;
+ }
+
+ NukeCrossCompartmentWrapper(cx, &args[0].toObject());
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool FdLibM_Pow(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ double x;
+ if (!JS::ToNumber(cx, args.get(0), &x)) {
+ return false;
+ }
+
+ double y;
+ if (!JS::ToNumber(cx, args.get(1), &y)) {
+ return false;
+ }
+
+ // Because C99 and ECMA specify different behavior for pow(), we need to wrap
+ // the fdlibm call to make it ECMA compliant.
+ if (!std::isfinite(y) && (x == 1.0 || x == -1.0)) {
+ args.rval().setNaN();
+ } else {
+ args.rval().setDouble(fdlibm::pow(x, y));
+ }
+ return true;
+}
+
+// clang-format off
+static const JSFunctionSpecWithHelp TestingFunctions[] = {
+ JS_FN_HELP("gc", ::GC, 0, 0,
+"gc([obj] | 'zone' [, ('shrinking' | 'last-ditch') ])",
+" Run the garbage collector.\n"
+" The first parameter describes which zones to collect: if an object is\n"
+" given, GC only its zone. If 'zone' is given, GC any zones that were\n"
+" scheduled via schedulegc.\n"
+" The second parameter is optional and may be 'shrinking' to perform a\n"
+" shrinking GC or 'last-ditch' for a shrinking, last-ditch GC."),
+
+ JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
+"minorgc([aboutToOverflow])",
+" Run a minor collector on the Nursery. When aboutToOverflow is true, marks\n"
+" the store buffer as about-to-overflow before collecting."),
+
+ JS_FN_HELP("maybegc", ::MaybeGC, 0, 0,
+"maybegc()",
+" Hint to the engine that now is an ok time to run the garbage collector.\n"),
+
+ JS_FN_HELP("gcparam", GCParameter, 2, 0,
+"gcparam(name [, value])",
+" Wrapper for JS_[GS]etGCParameter. The name is one of:" GC_PARAMETER_ARGS_LIST),
+
+ JS_FN_HELP("hasDisassembler", HasDisassembler, 0, 0,
+"hasDisassembler()",
+" Return true if a disassembler is present (for disnative and wasmDis)."),
+
+ JS_FN_HELP("disnative", DisassembleNative, 2, 0,
+"disnative(fun,[path])",
+" Disassemble a function into its native code. Optionally write the native code bytes to a file on disk.\n"),
+
+ JS_FN_HELP("relazifyFunctions", RelazifyFunctions, 0, 0,
+"relazifyFunctions(...)",
+" Perform a GC and allow relazification of functions. Accepts the same\n"
+" arguments as gc()."),
+
+ JS_FN_HELP("getBuildConfiguration", GetBuildConfiguration, 0, 0,
+"getBuildConfiguration()",
+" Return an object describing some of the configuration options SpiderMonkey\n"
+" was built with."),
+
+ JS_FN_HELP("getRealmConfiguration", GetRealmConfiguration, 0, 0,
+"getRealmConfiguration()",
+" Return an object describing some of the runtime options SpiderMonkey\n"
+" is running with."),
+
+ JS_FN_HELP("isLcovEnabled", ::IsLCovEnabled, 0, 0,
+"isLcovEnabled()",
+" Return true if JS LCov support is enabled."),
+
+ JS_FN_HELP("trialInline", TrialInline, 0, 0,
+"trialInline()",
+" Perform trial-inlining for the caller's frame if it's a BaselineFrame."),
+
+ JS_FN_HELP("hasChild", HasChild, 0, 0,
+"hasChild(parent, child)",
+" Return true if |child| is a child of |parent|, as determined by a call to\n"
+" TraceChildren"),
+
+ JS_FN_HELP("setSavedStacksRNGState", SetSavedStacksRNGState, 1, 0,
+"setSavedStacksRNGState(seed)",
+" Set this compartment's SavedStacks' RNG state.\n"),
+
+ JS_FN_HELP("getSavedFrameCount", GetSavedFrameCount, 0, 0,
+"getSavedFrameCount()",
+" Return the number of SavedFrame instances stored in this compartment's\n"
+" SavedStacks cache."),
+
+ JS_FN_HELP("clearSavedFrames", ClearSavedFrames, 0, 0,
+"clearSavedFrames()",
+" Empty the current compartment's cache of SavedFrame objects, so that\n"
+" subsequent stack captures allocate fresh objects to represent frames.\n"
+" Clear the current stack's LiveSavedFrameCaches."),
+
+ JS_FN_HELP("saveStack", SaveStack, 0, 0,
+"saveStack([maxDepth [, compartment]])",
+" Capture a stack. If 'maxDepth' is given, capture at most 'maxDepth' number\n"
+" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
+" with the given object's compartment."),
+
+ JS_FN_HELP("captureFirstSubsumedFrame", CaptureFirstSubsumedFrame, 1, 0,
+"saveStack(object [, shouldIgnoreSelfHosted = true]])",
+" Capture a stack back to the first frame whose principals are subsumed by the\n"
+" object's compartment's principals. If 'shouldIgnoreSelfHosted' is given,\n"
+" control whether self-hosted frames are considered when checking principals."),
+
+ JS_FN_HELP("callFunctionFromNativeFrame", CallFunctionFromNativeFrame, 1, 0,
+"callFunctionFromNativeFrame(function)",
+" Call 'function' with a (C++-)native frame on stack.\n"
+" Required for testing that SaveStack properly handles native frames."),
+
+ JS_FN_HELP("callFunctionWithAsyncStack", CallFunctionWithAsyncStack, 0, 0,
+"callFunctionWithAsyncStack(function, stack, asyncCause)",
+" Call 'function', using the provided stack as the async stack responsible\n"
+" for the call, and propagate its return value or the exception it throws.\n"
+" The function is called with no arguments, and 'this' is 'undefined'. The\n"
+" specified |asyncCause| is attached to the provided stack frame."),
+
+ JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
+"enableTrackAllocations()",
+" Start capturing the JS stack at every allocation. Note that this sets an\n"
+" object metadata callback that will override any other object metadata\n"
+" callback that may be set."),
+
+ JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,
+"disableTrackAllocations()",
+" Stop capturing the JS stack at every allocation."),
+
+ JS_FN_HELP("setTestFilenameValidationCallback", SetTestFilenameValidationCallback, 0, 0,
+"setTestFilenameValidationCallback()",
+" Set the filename validation callback to a callback that accepts only\n"
+" filenames starting with 'safe' or (only in system realms) 'system'."),
+
+ JS_FN_HELP("newObjectWithAddPropertyHook", NewObjectWithAddPropertyHook, 0, 0,
+"newObjectWithAddPropertyHook()",
+" Returns a new object with an addProperty JSClass hook. This hook\n"
+" increments the value of the _propertiesAdded data property on the object\n"
+" when a new property is added."),
+
+ JS_FN_HELP("newObjectWithCallHook", NewObjectWithCallHook, 0, 0,
+"newObjectWithCallHook()",
+" Returns a new object with call/construct JSClass hooks. These hooks return\n"
+" a new object that contains the Values supplied by the caller."),
+
+ JS_FN_HELP("newObjectWithManyReservedSlots", NewObjectWithManyReservedSlots, 0, 0,
+"newObjectWithManyReservedSlots()",
+" Returns a new object with many reserved slots. The slots are initialized to int32\n"
+" values. checkObjectWithManyReservedSlots can be used to check the slots still\n"
+" hold these values."),
+
+ JS_FN_HELP("checkObjectWithManyReservedSlots", CheckObjectWithManyReservedSlots, 1, 0,
+"checkObjectWithManyReservedSlots(obj)",
+" Checks the reserved slots set by newObjectWithManyReservedSlots still hold the expected\n"
+" values."),
+
+ JS_FN_HELP("getWatchtowerLog", GetWatchtowerLog, 0, 0,
+"getWatchtowerLog()",
+" Returns the Watchtower log recording object changes for objects for which\n"
+" addWatchtowerTarget was called. The internal log is cleared. The return\n"
+" value is an array of plain objects with the following properties:\n"
+" - kind: a string describing the kind of mutation, for example \"add-prop\"\n"
+" - object: the object being mutated\n"
+" - extra: an extra value, for example the name of the property being added"),
+
+ JS_FN_HELP("addWatchtowerTarget", AddWatchtowerTarget, 1, 0,
+"addWatchtowerTarget(object)",
+" Invoke the watchtower callback for changes to this object."),
+
+ JS_FN_HELP("newString", NewString, 2, 0,
+"newString(str[, options])",
+" Copies str's chars and returns a new string. Valid options:\n"
+" \n"
+" - tenured: allocate directly into the tenured heap.\n"
+" \n"
+" - twoByte: create a \"two byte\" string, not a latin1 string, regardless of the\n"
+" input string's characters. Latin1 will be used by default if possible\n"
+" (again regardless of the input string.)\n"
+" \n"
+" - external: create an external string. External strings are always twoByte and\n"
+" tenured.\n"
+" \n"
+" - maybeExternal: create an external string, unless the data fits within an\n"
+" inline string. Inline strings may be nursery-allocated."),
+
+ JS_FN_HELP("ensureLinearString", EnsureLinearString, 1, 0,
+"ensureLinearString(str)",
+" Ensures str is a linear (non-rope) string and returns it."),
+
+ JS_FN_HELP("representativeStringArray", RepresentativeStringArray, 0, 0,
+"representativeStringArray()",
+" Returns an array of strings that represent the various internal string\n"
+" types and character encodings."),
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
+ JS_FN_HELP("oomThreadTypes", OOMThreadTypes, 0, 0,
+"oomThreadTypes()",
+" Get the number of thread types that can be used as an argument for\n"
+" oomAfterAllocations() and oomAtAllocation()."),
+
+ JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0,
+"oomAfterAllocations(count [,threadType])",
+" After 'count' js_malloc memory allocations, fail every following allocation\n"
+" (return nullptr). The optional thread type limits the effect to the\n"
+" specified type of helper thread."),
+
+ JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 2, 0,
+"oomAtAllocation(count [,threadType])",
+" After 'count' js_malloc memory allocations, fail the next allocation\n"
+" (return nullptr). The optional thread type limits the effect to the\n"
+" specified type of helper thread."),
+
+ JS_FN_HELP("resetOOMFailure", ResetOOMFailure, 0, 0,
+"resetOOMFailure()",
+" Remove the allocation failure scheduled by either oomAfterAllocations() or\n"
+" oomAtAllocation() and return whether any allocation had been caused to fail."),
+
+ JS_FN_HELP("oomTest", OOMTest, 0, 0,
+"oomTest(function, [expectExceptionOnFailure = true | options])",
+" Test that the passed function behaves correctly under OOM conditions by\n"
+" repeatedly executing it and simulating allocation failure at successive\n"
+" allocations until the function completes without seeing a failure.\n"
+" By default this tests that an exception is raised if execution fails, but\n"
+" this can be disabled by passing false as the optional second parameter.\n"
+" This is also disabled when --fuzzing-safe is specified.\n"
+" Alternatively an object can be passed to set the following options:\n"
+" expectExceptionOnFailure: bool - as described above.\n"
+" keepFailing: bool - continue to fail after first simulated failure.\n"
+"\n"
+" WARNING: By design, oomTest assumes the test-function follows the same\n"
+" code path each time it is called, right up to the point where OOM occurs.\n"
+" If on iteration 70 it finishes and caches a unit of work that saves 65\n"
+" allocations the next time we run, then the subsequent 65 allocation\n"
+" points will go untested.\n"
+"\n"
+" Things in this category include lazy parsing and baseline compilation,\n"
+" so it is very easy to accidentally write an oomTest that only tests one\n"
+" or the other of those, and not the functionality you meant to test!\n"
+" To avoid lazy parsing, call the test function once first before passing\n"
+" it to oomTest. The jits can be disabled via the test harness.\n"),
+
+ JS_FN_HELP("stackTest", StackTest, 0, 0,
+"stackTest(function, [expectExceptionOnFailure = true])",
+" This function behaves exactly like oomTest with the difference that\n"
+" instead of simulating regular OOM conditions, it simulates the engine\n"
+" running out of stack space (failing recursion check).\n"
+"\n"
+" See the WARNING in help('oomTest').\n"),
+
+ JS_FN_HELP("interruptTest", InterruptTest, 0, 0,
+"interruptTest(function)",
+" This function simulates interrupts similar to how oomTest simulates OOM conditions."
+"\n"
+" See the WARNING in help('oomTest').\n"),
+
+#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
+ JS_FN_HELP("newRope", NewRope, 3, 0,
+"newRope(left, right[, options])",
+" Creates a rope with the given left/right strings.\n"
+" Available options:\n"
+" nursery: bool - force the string to be created in/out of the nursery, if possible.\n"),
+
+ JS_FN_HELP("isRope", IsRope, 1, 0,
+"isRope(str)",
+" Returns true if the parameter is a rope"),
+
+ JS_FN_HELP("settlePromiseNow", SettlePromiseNow, 1, 0,
+"settlePromiseNow(promise)",
+" 'Settle' a 'promise' immediately. This just marks the promise as resolved\n"
+" with a value of `undefined` and causes the firing of any onPromiseSettled\n"
+" hooks set on Debugger instances that are observing the given promise's\n"
+" global as a debuggee."),
+ JS_FN_HELP("getWaitForAllPromise", GetWaitForAllPromise, 1, 0,
+"getWaitForAllPromise(densePromisesArray)",
+" Calls the 'GetWaitForAllPromise' JSAPI function and returns the result\n"
+" Promise."),
+JS_FN_HELP("resolvePromise", ResolvePromise, 2, 0,
+"resolvePromise(promise, resolution)",
+" Resolve a Promise by calling the JSAPI function JS::ResolvePromise."),
+JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
+"rejectPromise(promise, reason)",
+" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
+
+ JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
+"makeFinalizeObserver()",
+" Get a special object whose finalization increases the counter returned\n"
+" by the finalizeCount function."),
+
+ JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0,
+"finalizeCount()",
+" Return the current value of the finalization counter that is incremented\n"
+" each time an object returned by the makeFinalizeObserver is finalized."),
+
+ JS_FN_HELP("resetFinalizeCount", ResetFinalizeCount, 0, 0,
+"resetFinalizeCount()",
+" Reset the value returned by finalizeCount()."),
+
+ JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0,
+"gcPreserveCode()",
+" Preserve JIT code during garbage collections."),
+
+#ifdef JS_GC_ZEAL
+ JS_FN_HELP("gczeal", GCZeal, 2, 0,
+"gczeal(mode, [frequency])",
+gc::ZealModeHelpText),
+
+ JS_FN_HELP("unsetgczeal", UnsetGCZeal, 2, 0,
+"unsetgczeal(mode)",
+" Turn off a single zeal mode set with gczeal() and don't finish any ongoing\n"
+" collection that may be happening."),
+
+ JS_FN_HELP("schedulegc", ScheduleGC, 1, 0,
+"schedulegc([num])",
+" If num is given, schedule a GC after num allocations.\n"
+" Returns the number of allocations before the next trigger."),
+
+ JS_FN_HELP("selectforgc", SelectForGC, 0, 0,
+"selectforgc(obj1, obj2, ...)",
+" Schedule the given objects to be marked in the next GC slice."),
+
+ JS_FN_HELP("verifyprebarriers", VerifyPreBarriers, 0, 0,
+"verifyprebarriers()",
+" Start or end a run of the pre-write barrier verifier."),
+
+ JS_FN_HELP("verifypostbarriers", VerifyPostBarriers, 0, 0,
+"verifypostbarriers()",
+" Does nothing (the post-write barrier verifier has been remove)."),
+
+ JS_FN_HELP("currentgc", CurrentGC, 0, 0,
+"currentgc()",
+" Report various information about the currently running incremental GC,\n"
+" if one is running."),
+
+ JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
+"deterministicgc(true|false)",
+" If true, only allow determinstic GCs to run."),
+
+ JS_FN_HELP("dumpGCArenaInfo", DumpGCArenaInfo, 0, 0,
+"dumpGCArenaInfo()",
+" Prints information about the different GC things and how they are arranged\n"
+" in arenas.\n"),
+
+ JS_FN_HELP("setMarkStackLimit", SetMarkStackLimit, 1, 0,
+"markStackLimit(limit)",
+" Sets a limit on the number of words used for the mark stack. Used to test OOM"
+" handling during marking.\n"),
+
+#endif
+
+ JS_FN_HELP("gcstate", GCState, 0, 0,
+"gcstate([obj])",
+" Report the global GC state, or the GC state for the zone containing |obj|."),
+
+ JS_FN_HELP("schedulezone", ScheduleZoneForGC, 1, 0,
+"schedulezone([obj | string])",
+" If obj is given, schedule a GC of obj's zone.\n"
+" If string is given, schedule a GC of the string's zone if possible."),
+
+ JS_FN_HELP("startgc", StartGC, 1, 0,
+"startgc([n [, 'shrinking']])",
+" Start an incremental GC and run a slice that processes about n objects.\n"
+" If 'shrinking' is passesd as the optional second argument, perform a\n"
+" shrinking GC rather than a normal GC. If no zones have been selected with\n"
+" schedulezone(), a full GC will be performed."),
+
+ JS_FN_HELP("finishgc", FinishGC, 0, 0,
+"finishgc()",
+" Finish an in-progress incremental GC, if none is running then do nothing."),
+
+ JS_FN_HELP("gcslice", GCSlice, 1, 0,
+"gcslice([n [, options]])",
+" Start or continue an an incremental GC, running a slice that processes\n"
+" about n objects. Takes an optional options object, which may contain the\n"
+" following properties:\n"
+" dontStart: do not start a new incremental GC if one is not already\n"
+" running"),
+
+ JS_FN_HELP("abortgc", AbortGC, 1, 0,
+"abortgc()",
+" Abort the current incremental GC."),
+
+ JS_FN_HELP("setMallocMaxDirtyPageModifier", SetMallocMaxDirtyPageModifier, 1, 0,
+"setMallocMaxDirtyPageModifier(value)",
+" Change the maximum size of jemalloc's page cache. The value should be between\n"
+" -5 and 16 (inclusive). See moz_set_max_dirty_page_modifier.\n"),
+
+ JS_FN_HELP("fullcompartmentchecks", FullCompartmentChecks, 1, 0,
+"fullcompartmentchecks(true|false)",
+" If true, check for compartment mismatches before every GC."),
+
+ JS_FN_HELP("nondeterministicGetWeakMapKeys", NondeterministicGetWeakMapKeys, 1, 0,
+"nondeterministicGetWeakMapKeys(weakmap)",
+" Return an array of the keys in the given WeakMap."),
+
+ JS_FN_HELP("internalConst", InternalConst, 1, 0,
+"internalConst(name)",
+" Query an internal constant for the engine. See InternalConst source for\n"
+" the list of constant names."),
+
+ JS_FN_HELP("isProxy", IsProxy, 1, 0,
+"isProxy(obj)",
+" If true, obj is a proxy of some sort"),
+
+ JS_FN_HELP("dumpHeap", DumpHeap, 1, 0,
+"dumpHeap([filename])",
+" Dump reachable and unreachable objects to the named file, or to stdout. Objects\n"
+" in the nursery are ignored, so if you wish to include them, consider calling\n"
+" minorgc() first."),
+
+ JS_FN_HELP("terminate", Terminate, 0, 0,
+"terminate()",
+" Terminate JavaScript execution, as if we had run out of\n"
+" memory or been terminated by the slow script dialog."),
+
+ JS_FN_HELP("readGeckoProfilingStack", ReadGeckoProfilingStack, 0, 0,
+"readGeckoProfilingStack()",
+" Reads the JIT/Wasm stack using ProfilingFrameIterator. Skips non-JIT/Wasm frames."),
+
+ JS_FN_HELP("readGeckoInterpProfilingStack", ReadGeckoInterpProfilingStack, 0, 0,
+"readGeckoInterpProfilingStack()",
+" Reads the C++ interpreter profiling stack. Skips JIT/Wasm frames."),
+
+ JS_FN_HELP("enableOsiPointRegisterChecks", EnableOsiPointRegisterChecks, 0, 0,
+"enableOsiPointRegisterChecks()",
+" Emit extra code to verify live regs at the start of a VM call are not\n"
+" modified before its OsiPoint."),
+
+ JS_FN_HELP("displayName", DisplayName, 1, 0,
+"displayName(fn)",
+" Gets the display name for a function, which can possibly be a guessed or\n"
+" inferred name based on where the function was defined. This can be\n"
+" different from the 'name' property on the function."),
+
+ JS_FN_HELP("isAsmJSCompilationAvailable", IsAsmJSCompilationAvailable, 0, 0,
+"isAsmJSCompilationAvailable",
+" Returns whether asm.js compilation is currently available or whether it is disabled\n"
+" (e.g., by the debugger)."),
+
+ JS_FN_HELP("getJitCompilerOptions", GetJitCompilerOptions, 0, 0,
+"getJitCompilerOptions()",
+" Return an object describing some of the JIT compiler options.\n"),
+
+ JS_FN_HELP("isAsmJSModule", IsAsmJSModule, 1, 0,
+"isAsmJSModule(fn)",
+" Returns whether the given value is a function containing \"use asm\" that has been\n"
+" validated according to the asm.js spec."),
+
+ JS_FN_HELP("isAsmJSFunction", IsAsmJSFunction, 1, 0,
+"isAsmJSFunction(fn)",
+" Returns whether the given value is a nested function in an asm.js module that has been\n"
+" both compile- and link-time validated."),
+
+ JS_FN_HELP("isAvxPresent", IsAvxPresent, 0, 0,
+"isAvxPresent([minVersion])",
+" Returns whether AVX is present and enabled. If minVersion specified,\n"
+" use 1 - to check if AVX is enabled (default), 2 - if AVX2 is enabled."),
+
+ JS_FN_HELP("wasmIsSupported", WasmIsSupported, 0, 0,
+"wasmIsSupported()",
+" Returns a boolean indicating whether WebAssembly is supported on the current device."),
+
+ JS_FN_HELP("wasmIsSupportedByHardware", WasmIsSupportedByHardware, 0, 0,
+"wasmIsSupportedByHardware()",
+" Returns a boolean indicating whether WebAssembly is supported on the current hardware (regardless of whether we've enabled support)."),
+
+ JS_FN_HELP("wasmDebuggingEnabled", WasmDebuggingEnabled, 0, 0,
+"wasmDebuggingEnabled()",
+" Returns a boolean indicating whether WebAssembly debugging is supported on the current device;\n"
+" returns false also if WebAssembly is not supported"),
+
+ JS_FN_HELP("wasmStreamingEnabled", WasmStreamingEnabled, 0, 0,
+"wasmStreamingEnabled()",
+" Returns a boolean indicating whether WebAssembly caching is supported by the runtime."),
+
+ JS_FN_HELP("wasmCachingEnabled", WasmCachingEnabled, 0, 0,
+"wasmCachingEnabled()",
+" Returns a boolean indicating whether WebAssembly caching is supported by the runtime."),
+
+ JS_FN_HELP("wasmHugeMemorySupported", WasmHugeMemorySupported, 0, 0,
+"wasmHugeMemorySupported()",
+" Returns a boolean indicating whether WebAssembly supports using a large"
+" virtual memory reservation in order to elide bounds checks on this platform."),
+
+ JS_FN_HELP("wasmMaxMemoryPages", WasmMaxMemoryPages, 1, 0,
+"wasmMaxMemoryPages(indexType)",
+" Returns an int with the maximum number of pages that can be allocated to a memory."
+" This is an implementation artifact that does depend on the index type, the hardware,"
+" the operating system, the build configuration, and flags. The result is constant for"
+" a given combination of those; there is no guarantee that that size allocation will"
+" always succeed, only that it can succeed in principle. The indexType is a string,"
+" 'i32' or 'i64'."),
+
+#define WASM_FEATURE(NAME, ...) \
+ JS_FN_HELP("wasm" #NAME "Enabled", Wasm##NAME##Enabled, 0, 0, \
+"wasm" #NAME "Enabled()", \
+" Returns a boolean indicating whether the WebAssembly " #NAME " proposal is enabled."),
+JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE)
+#undef WASM_FEATURE
+
+ JS_FN_HELP("wasmThreadsEnabled", WasmThreadsEnabled, 0, 0,
+"wasmThreadsEnabled()",
+" Returns a boolean indicating whether the WebAssembly threads proposal is\n"
+" supported on the current device."),
+
+ JS_FN_HELP("wasmSimdEnabled", WasmSimdEnabled, 0, 0,
+"wasmSimdEnabled()",
+" Returns a boolean indicating whether WebAssembly SIMD proposal is\n"
+" supported by the current device."),
+
+#if defined(ENABLE_WASM_SIMD) && defined(DEBUG)
+ JS_FN_HELP("wasmSimdAnalysis", WasmSimdAnalysis, 1, 0,
+"wasmSimdAnalysis(...)",
+" Unstable API for white-box testing.\n"),
+#endif
+
+ JS_FN_HELP("wasmGlobalFromArrayBuffer", WasmGlobalFromArrayBuffer, 2, 0,
+"wasmGlobalFromArrayBuffer(type, arrayBuffer)",
+" Create a WebAssembly.Global object from a provided ArrayBuffer. The type\n"
+" must be POD (i32, i64, f32, f64, v128). The buffer must be the same\n"
+" size as the type in bytes.\n"),
+ JS_FN_HELP("wasmGlobalExtractLane", WasmGlobalExtractLane, 3, 0,
+"wasmGlobalExtractLane(global, laneInterp, laneIndex)",
+" Extract a lane from a WebAssembly.Global object that contains a v128 value\n"
+" and return it as a new WebAssembly.Global object of the appropriate type.\n"
+" The supported laneInterp values are i32x4, i64x2, f32x4, and\n"
+" f64x2.\n"),
+ JS_FN_HELP("wasmGlobalsEqual", WasmGlobalsEqual, 2, 0,
+"wasmGlobalsEqual(globalA, globalB)",
+" Compares two WebAssembly.Global objects for if their types and values are\n"
+" equal. Mutability is not compared. Floating point values are compared for\n"
+" bitwise equality, not IEEE 754 equality.\n"),
+ JS_FN_HELP("wasmGlobalIsNaN", WasmGlobalIsNaN, 2, 0,
+"wasmGlobalIsNaN(global, flavor)",
+" Compares a floating point WebAssembly.Global object for if its value is a\n"
+" specific NaN flavor. Valid flavors are `arithmetic_nan` and `canonical_nan`.\n"),
+ JS_FN_HELP("wasmGlobalToString", WasmGlobalToString, 1, 0,
+"wasmGlobalToString(global)",
+" Returns a debug representation of the contents of a WebAssembly.Global\n"
+" object.\n"),
+ JS_FN_HELP("wasmLosslessInvoke", WasmLosslessInvoke, 1, 0,
+"wasmLosslessInvoke(wasmFunc, args...)",
+" Invokes the provided WebAssembly function using a modified conversion\n"
+" function that allows providing a param as a WebAssembly.Global and\n"
+" returning a result as a WebAssembly.Global.\n"),
+
+ JS_FN_HELP("wasmCompilersPresent", WasmCompilersPresent, 0, 0,
+"wasmCompilersPresent()",
+" Returns a string indicating the present wasm compilers: a comma-separated list\n"
+" of 'baseline', 'ion'. A compiler is present in the executable if it is compiled\n"
+" in and can generate code for the current architecture."),
+
+ JS_FN_HELP("wasmCompileMode", WasmCompileMode, 0, 0,
+"wasmCompileMode()",
+" Returns a string indicating the available wasm compilers: 'baseline', 'ion',\n"
+" 'baseline+ion', or 'none'. A compiler is available if it is present in the\n"
+" executable and not disabled by switches or runtime conditions. At most one\n"
+" baseline and one optimizing compiler can be available."),
+
+ JS_FN_HELP("wasmBaselineDisabledByFeatures", WasmBaselineDisabledByFeatures, 0, 0,
+"wasmBaselineDisabledByFeatures()",
+" If some feature is enabled at compile-time or run-time that prevents baseline\n"
+" from being used then this returns a truthy string describing the features that\n."
+" are disabling it. Otherwise it returns false."),
+
+ JS_FN_HELP("wasmIonDisabledByFeatures", WasmIonDisabledByFeatures, 0, 0,
+"wasmIonDisabledByFeatures()",
+" If some feature is enabled at compile-time or run-time that prevents Ion\n"
+" from being used then this returns a truthy string describing the features that\n."
+" are disabling it. Otherwise it returns false."),
+
+ JS_FN_HELP("wasmExtractCode", WasmExtractCode, 1, 0,
+"wasmExtractCode(module[, tier])",
+" Extracts generated machine code from WebAssembly.Module. The tier is a string,\n"
+" 'stable', 'best', 'baseline', or 'ion'; the default is 'stable'. If the request\n"
+" cannot be satisfied then null is returned. If the request is 'ion' then block\n"
+" until background compilation is complete."),
+
+ JS_FN_HELP("wasmDis", WasmDisassemble, 1, 0,
+"wasmDis(wasmObject[, options])\n",
+" Disassembles generated machine code from an exported WebAssembly function,\n"
+" or from all the functions defined in the module or instance, exported and not.\n"
+" The `options` is an object with the following optional keys:\n"
+" asString: boolean - if true, return a string rather than printing on stderr,\n"
+" the default is false.\n"
+" tier: string - one of 'stable', 'best', 'baseline', or 'ion'; the default is\n"
+" 'stable'.\n"
+" kinds: string - if set, and the wasmObject is a module or instance, a\n"
+" comma-separated list of the following keys, the default is `Function`:\n"
+" Function - functions defined in the module\n"
+" InterpEntry - C++-to-wasm stubs\n"
+" JitEntry - jitted-js-to-wasm stubs\n"
+" ImportInterpExit - wasm-to-C++ stubs\n"
+" ImportJitExit - wasm-to-jitted-JS stubs\n"
+" all - all kinds, including obscure ones\n"),
+
+ JS_FN_HELP("wasmHasTier2CompilationCompleted", WasmHasTier2CompilationCompleted, 1, 0,
+"wasmHasTier2CompilationCompleted(module)",
+" Returns a boolean indicating whether a given module has finished compiled code for tier2. \n"
+"This will return true early if compilation isn't two-tiered. "),
+
+ JS_FN_HELP("wasmLoadedFromCache", WasmLoadedFromCache, 1, 0,
+"wasmLoadedFromCache(module)",
+" Returns a boolean indicating whether a given module was deserialized directly from a\n"
+" cache (as opposed to compiled from bytecode)."),
+
+ JS_FN_HELP("wasmIntrinsicI8VecMul", WasmIntrinsicI8VecMul, 0, 0,
+"wasmIntrinsicI8VecMul()",
+" Returns a module that implements an i8 vector pairwise multiplication intrinsic."),
+
+ JS_FN_HELP("largeArrayBufferSupported", LargeArrayBufferSupported, 0, 0,
+"largeArrayBufferSupported()",
+" Returns true if array buffers larger than 2GB can be allocated."),
+
+ JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
+"isLazyFunction(fun)",
+" True if fun is a lazy JSFunction."),
+
+ JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
+"isRelazifiableFunction(fun)",
+" True if fun is a JSFunction with a relazifiable JSScript."),
+
+ JS_FN_HELP("hasSameBytecodeData", HasSameBytecodeData, 2, 0,
+"hasSameBytecodeData(fun1, fun2)",
+" True if fun1 and fun2 share the same copy of bytecode data. This will\n"
+" delazify the function if necessary."),
+
+ JS_FN_HELP("enableShellAllocationMetadataBuilder", EnableShellAllocationMetadataBuilder, 0, 0,
+"enableShellAllocationMetadataBuilder()",
+" Use ShellAllocationMetadataBuilder to supply metadata for all newly created objects."),
+
+ JS_FN_HELP("getAllocationMetadata", GetAllocationMetadata, 1, 0,
+"getAllocationMetadata(obj)",
+" Get the metadata for an object."),
+
+ JS_INLINABLE_FN_HELP("bailout", testingFunc_bailout, 0, 0, TestBailout,
+"bailout()",
+" Force a bailout out of ionmonkey (if running in ionmonkey)."),
+
+ JS_FN_HELP("bailAfter", testingFunc_bailAfter, 1, 0,
+"bailAfter(number)",
+" Start a counter to bail once after passing the given amount of possible bailout positions in\n"
+" ionmonkey.\n"),
+
+ JS_FN_HELP("invalidate", testingFunc_invalidate, 0, 0,
+"invalidate()",
+" Force an immediate invalidation (if running in Warp)."),
+
+ JS_FN_HELP("inJit", testingFunc_inJit, 0, 0,
+"inJit()",
+" Returns true when called within (jit-)compiled code. When jit compilation is disabled this\n"
+" function returns an error string. This function returns false in all other cases.\n"
+" Depending on truthiness, you should continue to wait for compilation to happen or stop execution.\n"),
+
+ JS_FN_HELP("inIon", testingFunc_inIon, 0, 0,
+"inIon()",
+" Returns true when called within ion. When ion is disabled or when compilation is abnormally\n"
+" slow to start, this function returns an error string. Otherwise, this function returns false.\n"
+" This behaviour ensures that a falsy value means that we are not in ion, but expect a\n"
+" compilation to occur in the future. Conversely, a truthy value means that we are either in\n"
+" ion or that there is litle or no chance of ion ever compiling the current script."),
+
+ JS_FN_HELP("assertJitStackInvariants", TestingFunc_assertJitStackInvariants, 0, 0,
+"assertJitStackInvariants()",
+" Iterates the Jit stack and check that stack invariants hold."),
+
+ JS_FN_HELP("setIonCheckGraphCoherency", SetIonCheckGraphCoherency, 1, 0,
+"setIonCheckGraphCoherency(bool)",
+" Set whether Ion should perform graph consistency (DEBUG-only) assertions. These assertions\n"
+" are valuable and should be generally enabled, however they can be very expensive for large\n"
+" (wasm) programs."),
+
+ JS_FN_HELP("serialize", testingFunc_serialize, 1, 0,
+"serialize(data, [transferables, [policy]])",
+" Serialize 'data' using JS_WriteStructuredClone. Returns a structured\n"
+" clone buffer object. 'policy' may be an options hash. Valid keys:\n"
+" 'SharedArrayBuffer' - either 'allow' or 'deny' (the default)\n"
+" to specify whether SharedArrayBuffers may be serialized.\n"
+" 'scope' - SameProcess, DifferentProcess, or\n"
+" DifferentProcessForIndexedDB. Determines how some values will be\n"
+" serialized. Clone buffers may only be deserialized with a compatible\n"
+" scope. NOTE - For DifferentProcess/DifferentProcessForIndexedDB,\n"
+" must also set SharedArrayBuffer:'deny' if data contains any shared memory\n"
+" object."),
+
+ JS_FN_HELP("deserialize", Deserialize, 1, 0,
+"deserialize(clonebuffer[, opts])",
+" Deserialize data generated by serialize. 'opts' may be an options hash.\n"
+" Valid keys:\n"
+" 'SharedArrayBuffer' - either 'allow' or 'deny' (the default)\n"
+" to specify whether SharedArrayBuffers may be serialized.\n"
+" 'scope', which limits the clone buffers that are considered\n"
+" valid. Allowed values: ''SameProcess', 'DifferentProcess',\n"
+" and 'DifferentProcessForIndexedDB'. So for example, a\n"
+" DifferentProcessForIndexedDB clone buffer may be deserialized in any scope, but\n"
+" a SameProcess clone buffer cannot be deserialized in a\n"
+" DifferentProcess scope."),
+
+ JS_FN_HELP("detachArrayBuffer", DetachArrayBuffer, 1, 0,
+"detachArrayBuffer(buffer)",
+" Detach the given ArrayBuffer object from its memory, i.e. as if it\n"
+" had been transferred to a WebWorker."),
+
+ JS_FN_HELP("makeSerializable", MakeSerializable, 1, 0,
+"makeSerializable(numeric id, [behavior])",
+" Make a custom serializable, transferable object. It will have a single accessor\n"
+" obj.log that will give a history of all operations on all such objects in the\n"
+" current thread as an array [id, action, id, action, ...] where the id\n"
+" is the number passed into this function, and the action is one of:\n"
+" ? - the canTransfer() hook was called.\n"
+" w - the write() hook was called.\n"
+" W - the writeTransfer() hook was called.\n"
+" R - the readTransfer() hook was called.\n"
+" r - the read() hook was called.\n"
+" F - the freeTransfer() hook was called.\n"
+" The `behavior` parameter can be used to force a failure during processing:\n"
+" 1 - fail during readTransfer() hook\n"
+" 2 - fail during read() hook\n"
+" Set the log to null to clear it."),
+
+ JS_FN_HELP("helperThreadCount", HelperThreadCount, 0, 0,
+"helperThreadCount()",
+" Returns the number of helper threads available for off-thread tasks."),
+
+ JS_FN_HELP("createShapeSnapshot", CreateShapeSnapshot, 1, 0,
+"createShapeSnapshot(obj)",
+" Returns an object containing a shape snapshot for use with\n"
+" checkShapeSnapshot.\n"),
+
+ JS_FN_HELP("checkShapeSnapshot", CheckShapeSnapshot, 2, 0,
+"checkShapeSnapshot(snapshot, [obj])",
+" Check shape invariants based on the given snapshot and optional object.\n"
+" If there's no object argument, the snapshot's object is used.\n"),
+
+ JS_FN_HELP("enableShapeConsistencyChecks", EnableShapeConsistencyChecks, 0, 0,
+"enableShapeConsistencyChecks()",
+" Enable some slow Shape assertions.\n"),
+
+ JS_FN_HELP("reportOutOfMemory", ReportOutOfMemory, 0, 0,
+"reportOutOfMemory()",
+" Report OOM, then clear the exception and return undefined. For crash testing."),
+
+ JS_FN_HELP("throwOutOfMemory", ThrowOutOfMemory, 0, 0,
+"throwOutOfMemory()",
+" Throw out of memory exception, for OOM handling testing."),
+
+ JS_FN_HELP("reportLargeAllocationFailure", ReportLargeAllocationFailure, 0, 0,
+"reportLargeAllocationFailure([bytes])",
+" Call the large allocation failure callback, as though a large malloc call failed,\n"
+" then return undefined. In Gecko, this sends a memory pressure notification, which\n"
+" can free up some memory."),
+
+ JS_FN_HELP("findPath", FindPath, 2, 0,
+"findPath(start, target)",
+" Return an array describing one of the shortest paths of GC heap edges from\n"
+" |start| to |target|, or |undefined| if |target| is unreachable from |start|.\n"
+" Each element of the array is either of the form:\n"
+" { node: <object or string>, edge: <string describing edge from node> }\n"
+" if the node is a JavaScript object or value; or of the form:\n"
+" { type: <string describing node>, edge: <string describing edge> }\n"
+" if the node is some internal thing that is not a proper JavaScript value\n"
+" (like a shape or a scope chain element). The destination of the i'th array\n"
+" element's edge is the node of the i+1'th array element; the destination of\n"
+" the last array element is implicitly |target|.\n"),
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ JS_FN_HELP("dumpObject", DumpObject, 1, 0,
+"dumpObject()",
+" Dump an internal representation of an object."),
+#endif
+
+ JS_FN_HELP("sharedMemoryEnabled", SharedMemoryEnabled, 0, 0,
+"sharedMemoryEnabled()",
+" Return true if SharedArrayBuffer and Atomics are enabled"),
+
+ JS_FN_HELP("sharedArrayRawBufferRefcount", SharedArrayRawBufferRefcount, 0, 0,
+"sharedArrayRawBufferRefcount(sab)",
+" Return the reference count of the SharedArrayRawBuffer object held by sab"),
+
+#ifdef NIGHTLY_BUILD
+ JS_FN_HELP("objectAddress", ObjectAddress, 1, 0,
+"objectAddress(obj)",
+" Return the current address of the object. For debugging only--this\n"
+" address may change during a moving GC."),
+
+ JS_FN_HELP("sharedAddress", SharedAddress, 1, 0,
+"sharedAddress(obj)",
+" Return the address of the shared storage of a SharedArrayBuffer."),
+#endif
+
+ JS_FN_HELP("hasInvalidatedTeleporting", HasInvalidatedTeleporting, 1, 0,
+"hasInvalidatedTeleporting(obj)",
+" Return true if the shape teleporting optimization has been disabled for |obj|."),
+
+ JS_FN_HELP("evalReturningScope", EvalReturningScope, 1, 0,
+"evalReturningScope(scriptStr, [global])",
+" Evaluate the script in a new scope and return the scope.\n"
+" If |global| is present, clone the script to |global| before executing."),
+
+ JS_FN_HELP("backtrace", DumpBacktrace, 1, 0,
+"backtrace()",
+" Dump out a brief backtrace."),
+
+ JS_FN_HELP("getBacktrace", GetBacktrace, 1, 0,
+"getBacktrace([options])",
+" Return the current stack as a string. Takes an optional options object,\n"
+" which may contain any or all of the boolean properties:\n"
+" options.args - show arguments to each function\n"
+" options.locals - show local variables in each frame\n"
+" options.thisprops - show the properties of the 'this' object of each frame\n"),
+
+ JS_FN_HELP("byteSize", ByteSize, 1, 0,
+"byteSize(value)",
+" Return the size in bytes occupied by |value|, or |undefined| if value\n"
+" is not allocated in memory.\n"),
+
+ JS_FN_HELP("byteSizeOfScript", ByteSizeOfScript, 1, 0,
+"byteSizeOfScript(f)",
+" Return the size in bytes occupied by the function |f|'s JSScript.\n"),
+
+ JS_FN_HELP("setImmutablePrototype", SetImmutablePrototype, 1, 0,
+"setImmutablePrototype(obj)",
+" Try to make obj's [[Prototype]] immutable, such that subsequent attempts to\n"
+" change it will fail. Return true if obj's [[Prototype]] was successfully made\n"
+" immutable (or if it already was immutable), false otherwise. Throws in case\n"
+" of internal error, or if the operation doesn't even make sense (for example,\n"
+" because the object is a revoked proxy)."),
+
+#ifdef DEBUG
+ JS_FN_HELP("dumpStringRepresentation", DumpStringRepresentation, 1, 0,
+"dumpStringRepresentation(str)",
+" Print a human-readable description of how the string |str| is represented.\n"),
+
+ JS_FN_HELP("stringRepresentation", GetStringRepresentation, 1, 0,
+"stringRepresentation(str)",
+" Return a human-readable description of how the string |str| is represented.\n"),
+
+#endif
+
+ JS_FN_HELP("allocationMarker", AllocationMarker, 0, 0,
+"allocationMarker([options])",
+" Return a freshly allocated object whose [[Class]] name is\n"
+" \"AllocationMarker\". Such objects are allocated only by calls\n"
+" to this function, never implicitly by the system, making them\n"
+" suitable for use in allocation tooling tests. Takes an optional\n"
+" options object which may contain the following properties:\n"
+" * nursery: bool, whether to allocate the object in the nursery\n"),
+
+ JS_FN_HELP("setGCCallback", SetGCCallback, 1, 0,
+"setGCCallback({action:\"...\", options...})",
+" Set the GC callback. action may be:\n"
+" 'minorGC' - run a nursery collection\n"
+" 'majorGC' - run a major collection, nesting up to a given 'depth'\n"),
+
+#ifdef DEBUG
+ JS_FN_HELP("enqueueMark", EnqueueMark, 1, 0,
+"enqueueMark(obj|string)",
+" Add an object to the queue of objects to mark at the beginning every GC. (Note\n"
+" that the objects will actually be marked at the beginning of every slice, but\n"
+" after the first slice they will already be marked so nothing will happen.)\n"
+" \n"
+" Instead of an object, a few magic strings may be used:\n"
+" 'yield' - cause the current marking slice to end, as if the mark budget were\n"
+" exceeded.\n"
+" 'enter-weak-marking-mode' - divide the list into two segments. The items after\n"
+" this string will not be marked until we enter weak marking mode. Note that weak\n"
+" marking mode may be entered zero or multiple times for one GC.\n"
+" 'abort-weak-marking-mode' - same as above, but then abort weak marking to fall back\n"
+" on the old iterative marking code path.\n"
+" 'drain' - fully drain the mark stack before continuing.\n"
+" 'set-color-black' - force everything following in the mark queue to be marked black.\n"
+" 'set-color-gray' - continue with the regular GC until gray marking is possible, then force\n"
+" everything following in the mark queue to be marked gray.\n"
+" 'unset-color' - stop forcing the mark color."),
+
+ JS_FN_HELP("clearMarkQueue", ClearMarkQueue, 0, 0,
+"clearMarkQueue()",
+" Cancel the special marking of all objects enqueue with enqueueMark()."),
+
+ JS_FN_HELP("getMarkQueue", GetMarkQueue, 0, 0,
+"getMarkQueue()",
+" Return the current mark queue set up via enqueueMark calls. Note that all\n"
+" returned values will be wrapped into the current compartment, so this loses\n"
+" some fidelity."),
+#endif // DEBUG
+
+ JS_FN_HELP("nurseryStringsEnabled", NurseryStringsEnabled, 0, 0,
+"nurseryStringsEnabled()",
+" Return whether strings are currently allocated in the nursery for current\n"
+" global\n"),
+
+ JS_FN_HELP("isNurseryAllocated", IsNurseryAllocated, 1, 0,
+"isNurseryAllocated(thing)",
+" Return whether a GC thing is nursery allocated.\n"),
+
+ JS_FN_HELP("getLcovInfo", GetLcovInfo, 1, 0,
+"getLcovInfo(global)",
+" Generate LCOV tracefile for the given compartment. If no global are provided then\n"
+" the current global is used as the default one.\n"),
+
+#ifdef DEBUG
+ JS_FN_HELP("setRNGState", SetRNGState, 2, 0,
+"setRNGState(seed0, seed1)",
+" Set this compartment's RNG state.\n"),
+#endif
+
+#ifdef AFLFUZZ
+ JS_FN_HELP("aflloop", AflLoop, 1, 0,
+"aflloop(max_cnt)",
+" Call the __AFL_LOOP() runtime function (see AFL docs)\n"),
+#endif
+
+ JS_FN_HELP("monotonicNow", MonotonicNow, 0, 0,
+"monotonicNow()",
+" Return a timestamp reflecting the current elapsed system time.\n"
+" This is monotonically increasing.\n"),
+
+ JS_FN_HELP("timeSinceCreation", TimeSinceCreation, 0, 0,
+"TimeSinceCreation()",
+" Returns the time in milliseconds since process creation.\n"
+" This uses a clock compatible with the profiler.\n"),
+
+ JS_FN_HELP("isConstructor", IsConstructor, 1, 0,
+"isConstructor(value)",
+" Returns whether the value is considered IsConstructor.\n"),
+
+ JS_FN_HELP("getTimeZone", GetTimeZone, 0, 0,
+"getTimeZone()",
+" Get the current time zone.\n"),
+
+ JS_FN_HELP("getDefaultLocale", GetDefaultLocale, 0, 0,
+"getDefaultLocale()",
+" Get the current default locale.\n"),
+
+ JS_FN_HELP("getCoreCount", GetCoreCount, 0, 0,
+"getCoreCount()",
+" Get the number of CPU cores from the platform layer. Typically this\n"
+" means the number of hyperthreads on systems where that makes sense.\n"),
+
+ JS_FN_HELP("setTimeResolution", SetTimeResolution, 2, 0,
+"setTimeResolution(resolution, jitter)",
+" Enables time clamping and jittering. Specify a time resolution in\n"
+" microseconds and whether or not to jitter\n"),
+
+ JS_FN_HELP("scriptedCallerGlobal", ScriptedCallerGlobal, 0, 0,
+"scriptedCallerGlobal()",
+" Get the caller's global (or null). See JS::GetScriptedCallerGlobal.\n"),
+
+ JS_FN_HELP("objectGlobal", ObjectGlobal, 1, 0,
+"objectGlobal(obj)",
+" Returns the object's global object or null if the object is a wrapper.\n"),
+
+ JS_FN_HELP("isSameCompartment", IsSameCompartment, 2, 0,
+"isSameCompartment(obj1, obj2)",
+" Unwraps obj1 and obj2 and returns whether the unwrapped objects are\n"
+" same-compartment.\n"),
+
+ JS_FN_HELP("firstGlobalInCompartment", FirstGlobalInCompartment, 1, 0,
+"firstGlobalInCompartment(obj)",
+" Returns the first global in obj's compartment.\n"),
+
+ JS_FN_HELP("assertCorrectRealm", AssertCorrectRealm, 0, 0,
+"assertCorrectRealm()",
+" Asserts cx->realm matches callee->realm.\n"),
+
+ JS_FN_HELP("globalLexicals", GlobalLexicals, 0, 0,
+"globalLexicals()",
+" Returns an object containing a copy of all global lexical bindings.\n"
+" Example use: let x = 1; assertEq(globalLexicals().x, 1);\n"),
+
+ JS_FN_HELP("baselineCompile", BaselineCompile, 2, 0,
+"baselineCompile([fun/code], forceDebugInstrumentation=false)",
+" Baseline-compiles the given JS function or script.\n"
+" Without arguments, baseline-compiles the caller's script; but note\n"
+" that extra boilerplate is needed afterwards to cause the VM to start\n"
+" running the jitcode rather than staying in the interpreter:\n"
+" baselineCompile(); for (var i=0; i<1; i++) {} ...\n"
+" The interpreter will enter the new jitcode at the loop header unless\n"
+" baselineCompile returned a string or threw an error.\n"),
+
+ JS_FN_HELP("encodeAsUtf8InBuffer", EncodeAsUtf8InBuffer, 2, 0,
+"encodeAsUtf8InBuffer(str, uint8Array)",
+" Encode as many whole code points from the string str into the provided\n"
+" Uint8Array as will completely fit in it, converting lone surrogates to\n"
+" REPLACEMENT CHARACTER. Return an array [r, w] where |r| is the\n"
+" number of 16-bit units read and |w| is the number of bytes of UTF-8\n"
+" written."),
+
+ JS_FN_HELP("clearKeptObjects", ClearKeptObjects, 0, 0,
+"clearKeptObjects()",
+"Perform the ECMAScript ClearKeptObjects operation, clearing the list of\n"
+"observed WeakRef targets that are kept alive until the next synchronous\n"
+"sequence of ECMAScript execution completes. This is used for testing\n"
+"WeakRefs.\n"),
+
+ JS_FN_HELP("numberToDouble", NumberToDouble, 1, 0,
+"numberToDouble(number)",
+" Return the input number as double-typed number."),
+
+JS_FN_HELP("getICUOptions", GetICUOptions, 0, 0,
+"getICUOptions()",
+" Return an object describing the following ICU options.\n\n"
+" version: a string containing the ICU version number, e.g. '67.1'\n"
+" unicode: a string containing the Unicode version number, e.g. '13.0'\n"
+" locale: the ICU default locale, e.g. 'en_US'\n"
+" tzdata: a string containing the tzdata version number, e.g. '2020a'\n"
+" timezone: the ICU default time zone, e.g. 'America/Los_Angeles'\n"
+" host-timezone: the host time zone, e.g. 'America/Los_Angeles'"),
+
+JS_FN_HELP("getAvailableLocalesOf", GetAvailableLocalesOf, 0, 0,
+"getAvailableLocalesOf(name)",
+" Return an array of all available locales for the given Intl constuctor."),
+
+JS_FN_HELP("isSmallFunction", IsSmallFunction, 1, 0,
+"isSmallFunction(fun)",
+" Returns true if a scripted function is small enough to be inlinable."),
+
+ JS_FN_HELP("compileToStencil", CompileToStencil, 1, 0,
+"compileToStencil(string, [options])",
+" Parses the given string argument as js script, returns the stencil"
+" for it."),
+
+ JS_FN_HELP("evalStencil", EvalStencil, 1, 0,
+"evalStencil(stencil, [options])",
+" Instantiates the given stencil, and evaluates the top-level script it"
+" defines."),
+
+ JS_FN_HELP("compileToStencilXDR", CompileToStencilXDR, 1, 0,
+"compileToStencilXDR(string, [options])",
+" Parses the given string argument as js script, produces the stencil"
+" for it, XDR-encodes the stencil, and returns an object that contains the"
+" XDR buffer."),
+
+ JS_FN_HELP("evalStencilXDR", EvalStencilXDR, 1, 0,
+"evalStencilXDR(stencilXDR, [options])",
+" Reads the given stencil XDR object, and evaluates the top-level script it"
+" defines."),
+
+ JS_FN_HELP("getExceptionInfo", GetExceptionInfo, 1, 0,
+"getExceptionInfo(fun)",
+" Calls the given function and returns information about the exception it"
+" throws. Returns null if the function didn't throw an exception."),
+
+ JS_FN_HELP("nukeCCW", NukeCCW, 1, 0,
+"nukeCCW(wrapper)",
+" Nuke a CrossCompartmentWrapper, which turns it into a DeadProxyObject."),
+
+ JS_FS_HELP_END
+};
+// clang-format on
+
+// clang-format off
+static const JSFunctionSpecWithHelp FuzzingUnsafeTestingFunctions[] = {
+ JS_FN_HELP("getErrorNotes", GetErrorNotes, 1, 0,
+"getErrorNotes(error)",
+" Returns an array of error notes."),
+
+ JS_FN_HELP("setTimeZone", SetTimeZone, 1, 0,
+"setTimeZone(tzname)",
+" Set the 'TZ' environment variable to the given time zone and applies the new time zone.\n"
+" An empty string or undefined resets the time zone to its default value.\n"
+" NOTE: The input string is not validated and will be passed verbatim to setenv()."),
+
+JS_FN_HELP("setDefaultLocale", SetDefaultLocale, 1, 0,
+"setDefaultLocale(locale)",
+" Set the runtime default locale to the given value.\n"
+" An empty string or undefined resets the runtime locale to its default value.\n"
+" NOTE: The input string is not fully validated, it must be a valid BCP-47 language tag."),
+
+JS_FN_HELP("isInStencilCache", IsInStencilCache, 1, 0,
+"isInStencilCache(fun)",
+" True if fun is available in the stencil cache."),
+
+JS_FN_HELP("waitForStencilCache", WaitForStencilCache, 1, 0,
+"waitForStencilCache(fun)",
+" Block main thread execution until the function is made available in the cache."),
+
+JS_FN_HELP("getInnerMostEnvironmentObject", GetInnerMostEnvironmentObject, 0, 0,
+"getInnerMostEnvironmentObject()",
+" Return the inner-most environment object for current execution."),
+
+JS_FN_HELP("getEnclosingEnvironmentObject", GetEnclosingEnvironmentObject, 1, 0,
+"getEnclosingEnvironmentObject(env)",
+" Return the enclosing environment object for given environment object."),
+
+JS_FN_HELP("getEnvironmentObjectType", GetEnvironmentObjectType, 1, 0,
+"getEnvironmentObjectType(env)",
+" Return a string represents the type of given environment object."),
+
+ JS_FN_HELP("shortestPaths", ShortestPaths, 3, 0,
+"shortestPaths(targets, options)",
+" Return an array of arrays of shortest retaining paths. There is an array of\n"
+ " shortest retaining paths for each object in |targets|. Each element in a path\n"
+ " is of the form |{ predecessor, edge }|. |options| may contain:\n"
+ " \n"
+ " maxNumPaths: The maximum number of paths returned in each of those arrays\n"
+ " (default 3).\n"
+ " start: The object to start all paths from. If not given, then\n"
+ " the starting point will be the set of GC roots."),
+
+
+ JS_FS_HELP_END
+};
+// clang-format on
+
+// clang-format off
+static const JSFunctionSpecWithHelp PCCountProfilingTestingFunctions[] = {
+ JS_FN_HELP("start", PCCountProfiling_Start, 0, 0,
+ "start()",
+ " Start PC count profiling."),
+
+ JS_FN_HELP("stop", PCCountProfiling_Stop, 0, 0,
+ "stop()",
+ " Stop PC count profiling."),
+
+ JS_FN_HELP("purge", PCCountProfiling_Purge, 0, 0,
+ "purge()",
+ " Purge the collected PC count profiling data."),
+
+ JS_FN_HELP("count", PCCountProfiling_ScriptCount, 0, 0,
+ "count()",
+ " Return the number of profiled scripts."),
+
+ JS_FN_HELP("summary", PCCountProfiling_ScriptSummary, 1, 0,
+ "summary(index)",
+ " Return the PC count profiling summary for the given script index.\n"
+ " The script index must be in the range [0, pc.count())."),
+
+ JS_FN_HELP("contents", PCCountProfiling_ScriptContents, 1, 0,
+ "contents(index)",
+ " Return the complete profiling contents for the given script index.\n"
+ " The script index must be in the range [0, pc.count())."),
+
+ JS_FS_HELP_END
+};
+// clang-format on
+
+// clang-format off
+static const JSFunctionSpecWithHelp FdLibMTestingFunctions[] = {
+ JS_FN_HELP("pow", FdLibM_Pow, 2, 0,
+ "pow(x, y)",
+ " Return x ** y."),
+
+ JS_FS_HELP_END
+};
+// clang-format on
+
+bool js::InitTestingFunctions() { return disasmBuf.init(); }
+
+bool js::DefineTestingFunctions(JSContext* cx, HandleObject obj,
+ bool fuzzingSafe_, bool disableOOMFunctions_) {
+ fuzzingSafe = fuzzingSafe_;
+ if (EnvVarIsDefined("MOZ_FUZZING_SAFE")) {
+ fuzzingSafe = true;
+ }
+
+ disableOOMFunctions = disableOOMFunctions_;
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, obj, FuzzingUnsafeTestingFunctions)) {
+ return false;
+ }
+
+ RootedObject pccount(cx, JS_NewPlainObject(cx));
+ if (!pccount) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "pccount", pccount, 0)) {
+ return false;
+ }
+
+ if (!JS_DefineFunctionsWithHelp(cx, pccount,
+ PCCountProfilingTestingFunctions)) {
+ return false;
+ }
+ }
+
+ RootedObject fdlibm(cx, JS_NewPlainObject(cx));
+ if (!fdlibm) {
+ return false;
+ }
+
+ if (!JS_DefineProperty(cx, obj, "fdlibm", fdlibm, 0)) {
+ return false;
+ }
+
+ if (!JS_DefineFunctionsWithHelp(cx, fdlibm, FdLibMTestingFunctions)) {
+ return false;
+ }
+
+ return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
+}
+
+#ifdef FUZZING_JS_FUZZILLI
+uint32_t js::FuzzilliHashDouble(double value) {
+ // We shouldn't GC here as this is called directly from IC code.
+ AutoUnsafeCallWithABI unsafe;
+ uint64_t v = mozilla::BitwiseCast<uint64_t>(value);
+ return static_cast<uint32_t>(v) + static_cast<uint32_t>(v >> 32);
+}
+
+uint32_t js::FuzzilliHashBigInt(BigInt* bigInt) {
+ // We shouldn't GC here as this is called directly from IC code.
+ AutoUnsafeCallWithABI unsafe;
+ return bigInt->hash();
+}
+
+void js::FuzzilliHashObject(JSContext* cx, JSObject* obj) {
+ // called from IC and baseline/interpreter
+ uint32_t hash;
+ FuzzilliHashObjectInl(cx, obj, &hash);
+
+ cx->executionHashInputs += 1;
+ cx->executionHash = mozilla::RotateLeft(cx->executionHash + hash, 1);
+}
+
+void js::FuzzilliHashObjectInl(JSContext* cx, JSObject* obj, uint32_t* out) {
+ *out = 0;
+ if (!js::SupportDifferentialTesting()) {
+ return;
+ }
+
+ RootedValue v(cx);
+ v.setObject(*obj);
+
+ JSAutoStructuredCloneBuffer JSCloner(
+ JS::StructuredCloneScope::DifferentProcess, nullptr, nullptr);
+ if (JSCloner.write(cx, v)) {
+ JSStructuredCloneData& data = JSCloner.data();
+ data.ForEachDataChunk([&](const char* aData, size_t aSize) {
+ uint32_t h = mozilla::HashBytes(aData, aSize);
+ h = (h << 1) | 1;
+ *out ^= h;
+ *out *= h;
+ return true;
+ });
+ } else if (JS_IsExceptionPending(cx)) {
+ JS_ClearPendingException(cx);
+ }
+}
+#endif