summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/TestingFunctions.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/builtin/TestingFunctions.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/builtin/TestingFunctions.cpp')
-rw-r--r--js/src/builtin/TestingFunctions.cpp4814
1 files changed, 4814 insertions, 0 deletions
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
new file mode 100644
index 000000000..5bc69a346
--- /dev/null
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -0,0 +1,4814 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/FloatingPoint.h"
+#include "mozilla/Move.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+
+#include <cmath>
+
+#include "jsapi.h"
+#include "jscntxt.h"
+#include "jsfriendapi.h"
+#include "jsgc.h"
+#include "jsobj.h"
+#include "jsprf.h"
+#include "jswrapper.h"
+
+#include "builtin/Promise.h"
+#include "builtin/SelfHostingDefines.h"
+#ifdef DEBUG
+#include "frontend/TokenStream.h"
+#include "irregexp/RegExpAST.h"
+#include "irregexp/RegExpEngine.h"
+#include "irregexp/RegExpParser.h"
+#endif
+#include "jit/InlinableNatives.h"
+#include "jit/JitFrameIterator.h"
+#include "js/Debug.h"
+#include "js/HashTable.h"
+#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 "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/ProxyObject.h"
+#include "vm/SavedStacks.h"
+#include "vm/Stack.h"
+#include "vm/StringBuffer.h"
+#include "vm/TraceLogging.h"
+#include "wasm/AsmJS.h"
+#include "wasm/WasmBinaryToExperimentalText.h"
+#include "wasm/WasmBinaryToText.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmModule.h"
+#include "wasm/WasmSignalHandlers.h"
+#include "wasm/WasmTextToBinary.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+using mozilla::Move;
+
+// If fuzzingSafe is set, remove functionality that could cause problems with
+// fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
+static bool fuzzingSafe = false;
+
+// If disableOOMFunctions is set, disable functionality that causes artificial
+// OOM conditions.
+static 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
+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;
+
+ 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 JS_HAS_CTYPES
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-ctypes", value))
+ return false;
+
+#ifdef JS_CPU_X86
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x86", value))
+ return false;
+
+#ifdef JS_CPU_X64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "x64", 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 JS_SIMULATOR_ARM64
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "arm64-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 JS_GC_ZEAL
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "has-gczeal", value))
+ return false;
+
+#ifdef JS_MORE_DETERMINISTIC
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "more-deterministic", 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_OOM_DO_BACKTRACES
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "oom-backtraces", value))
+ return false;
+
+#ifdef ENABLE_BINARYDATA
+ value = BooleanValue(true);
+#else
+ value = BooleanValue(false);
+#endif
+ if (!JS_SetProperty(cx, info, "binary-data", value))
+ return false;
+
+#ifdef EXPOSE_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;
+
+ args.rval().setObject(*info);
+ 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_StringEqualsAscii(cx, arg.toString(), "zone", &zone))
+ return false;
+ } else if (arg.isObject()) {
+ PrepareZoneForGC(UncheckedUnwrap(&arg.toObject())->zone());
+ zone = true;
+ }
+ }
+
+ bool shrinking = false;
+ if (args.length() >= 2) {
+ Value arg = args[1];
+ if (arg.isString()) {
+ if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
+ return false;
+ }
+ }
+
+#ifndef JS_MORE_DETERMINISTIC
+ size_t preBytes = cx->runtime()->gc.usage.gcBytes();
+#endif
+
+ if (zone)
+ PrepareForDebugGC(cx->runtime());
+ else
+ JS::PrepareForFullGC(cx);
+
+ JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
+ JS::GCForReason(cx, gckind, JS::gcreason::API);
+
+ char buf[256] = { '\0' };
+#ifndef JS_MORE_DETERMINISTIC
+ SprintfLiteral(buf, "before %" PRIuSIZE ", after %" PRIuSIZE "\n",
+ preBytes, cx->runtime()->gc.usage.gcBytes());
+#endif
+ JSString* str = JS_NewStringCopyZ(cx, buf);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+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();
+
+ cx->minorGC(JS::gcreason::API);
+ args.rval().setUndefined();
+ return true;
+}
+
+#define FOR_EACH_GC_PARAM(_) \
+ _("maxBytes", JSGC_MAX_BYTES, true) \
+ _("maxMallocBytes", JSGC_MAX_MALLOC_BYTES, true) \
+ _("gcBytes", JSGC_BYTES, false) \
+ _("gcNumber", JSGC_NUMBER, false) \
+ _("mode", JSGC_MODE, true) \
+ _("unusedChunks", JSGC_UNUSED_CHUNKS, false) \
+ _("totalChunks", JSGC_TOTAL_CHUNKS, false) \
+ _("sliceTimeBudget", JSGC_SLICE_TIME_BUDGET, true) \
+ _("markStackLimit", JSGC_MARK_STACK_LIMIT, true) \
+ _("highFrequencyTimeLimit", JSGC_HIGH_FREQUENCY_TIME_LIMIT, true) \
+ _("highFrequencyLowLimit", JSGC_HIGH_FREQUENCY_LOW_LIMIT, true) \
+ _("highFrequencyHighLimit", JSGC_HIGH_FREQUENCY_HIGH_LIMIT, true) \
+ _("highFrequencyHeapGrowthMax", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, true) \
+ _("highFrequencyHeapGrowthMin", JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, true) \
+ _("lowFrequencyHeapGrowth", JSGC_LOW_FREQUENCY_HEAP_GROWTH, true) \
+ _("dynamicHeapGrowth", JSGC_DYNAMIC_HEAP_GROWTH, true) \
+ _("dynamicMarkSlice", JSGC_DYNAMIC_MARK_SLICE, true) \
+ _("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true) \
+ _("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true) \
+ _("maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT, true) \
+ _("compactingEnabled", JSGC_COMPACTING_ENABLED, true) \
+ _("refreshFrameSlicesEnabled", JSGC_REFRESH_FRAME_SLICES_ENABLED, true)
+
+static const struct ParamInfo {
+ const char* name;
+ JSGCParamKey param;
+ bool writable;
+} paramMap[] = {
+#define DEFINE_PARAM_INFO(name, key, writable) \
+ {name, key, writable},
+FOR_EACH_GC_PARAM(DEFINE_PARAM_INFO)
+#undef DEFINE_PARAM_INFO
+};
+
+#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;
+
+ JSFlatString* flatStr = JS_FlattenString(cx, str);
+ if (!flatStr)
+ return false;
+
+ size_t paramIndex = 0;
+ for (;; paramIndex++) {
+ if (paramIndex == ArrayLength(paramMap)) {
+ JS_ReportErrorASCII(cx,
+ "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
+ return false;
+ }
+ if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name))
+ break;
+ }
+ const ParamInfo& info = paramMap[paramIndex];
+ JSGCParamKey param = info.param;
+
+ // Request mode.
+ if (args.length() == 1) {
+ uint32_t value = JS_GetGCParameter(cx, param);
+ args.rval().setNumber(value);
+ return true;
+ }
+
+ if (!info.writable) {
+ JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s", info.name);
+ return false;
+ }
+
+ if (disableOOMFunctions && (param == JSGC_MAX_BYTES || param == JSGC_MAX_MALLOC_BYTES)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ 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);
+ if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx)) {
+ JS_ReportErrorASCII(cx, "attempt to set markStackLimit while a GC is in progress");
+ return false;
+ }
+
+ if (param == JSGC_MAX_BYTES) {
+ uint32_t gcBytes = JS_GetGCParameter(cx, JSGC_BYTES);
+ if (value < gcBytes) {
+ JS_ReportErrorASCII(cx,
+ "attempt to set maxBytes to the value less than the current "
+ "gcBytes (%u)",
+ gcBytes);
+ return false;
+ }
+ }
+
+ bool ok;
+ {
+ JSRuntime* rt = cx->runtime();
+ AutoLockGC lock(rt);
+ ok = rt->gc.setParameter(param, value, lock);
+ }
+
+ if (!ok) {
+ JS_ReportErrorASCII(cx, "Parameter value out of range");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static void
+SetAllowRelazification(JSContext* cx, bool allow)
+{
+ JSRuntime* rt = cx->runtime();
+ MOZ_ASSERT(rt->allowRelazificationForTesting != allow);
+ rt->allowRelazificationForTesting = allow;
+
+ for (AllScriptFramesIter i(cx); !i.done(); ++i)
+ i.script()->setDoNotRelazify(allow);
+}
+
+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);
+ SetAllowRelazification(cx, true);
+
+ JS::PrepareForFullGC(cx);
+ JS::GCForReason(cx, GC_SHRINK, JS::gcreason::API);
+
+ SetAllowRelazification(cx, 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));
+ return true;
+}
+
+static bool
+WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (!args.requireAtLeast(cx, "wasmTextToBinary", 1))
+ return false;
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ AutoStableStringChars twoByteChars(cx);
+ if (!twoByteChars.initTwoByte(cx, args[0].toString()))
+ return false;
+
+ if (args.hasDefined(1)) {
+ if (!args[1].isString()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a String");
+ return false;
+ }
+ }
+
+ wasm::Bytes bytes;
+ UniqueChars error;
+ if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
+ error.get() ? error.get() : "out of memory");
+ return false;
+ }
+
+ RootedObject obj(cx, JS_NewUint8Array(cx, bytes.length()));
+ if (!obj)
+ return false;
+
+ memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+WasmBinaryToText(JSContext* cx, unsigned argc, Value* vp)
+{
+ MOZ_ASSERT(cx->options().wasm());
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject() || !args.get(0).toObject().is<TypedArrayObject>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+ return false;
+ }
+
+ Rooted<TypedArrayObject*> code(cx, &args[0].toObject().as<TypedArrayObject>());
+
+ if (!TypedArrayObject::ensureHasBuffer(cx, code))
+ return false;
+
+ if (code->isSharedMemory()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+ return false;
+ }
+
+ const uint8_t* bufferStart = code->bufferUnshared()->dataPointer();
+ const uint8_t* bytes = bufferStart + code->byteOffset();
+ uint32_t length = code->byteLength();
+
+ Vector<uint8_t> copy(cx);
+ if (code->bufferUnshared()->hasInlineData()) {
+ if (!copy.append(bytes, length))
+ return false;
+ bytes = copy.begin();
+ }
+
+ bool experimental = false;
+ if (args.length() > 1) {
+ JSString* opt = JS::ToString(cx, args[1]);
+ if (!opt)
+ return false;
+ bool match;
+ if (!JS_StringEqualsAscii(cx, opt, "experimental", &match))
+ return false;
+ experimental = match;
+ }
+
+ StringBuffer buffer(cx);
+ bool ok;
+ if (experimental)
+ ok = wasm::BinaryToExperimentalText(cx, bytes, length, buffer, wasm::ExperimentalTextFormatting());
+ else
+ ok = wasm::BinaryToText(cx, bytes, length, buffer);
+ if (!ok) {
+ if (!cx->isExceptionPending())
+ JS_ReportErrorASCII(cx, "wasm binary to text print error");
+ return false;
+ }
+
+ JSString* result = buffer.finishString();
+ if (!result)
+ return false;
+
+ args.rval().setString(result);
+ return true;
+}
+
+static bool
+WasmExtractCode(JSContext* cx, unsigned argc, Value* vp)
+{
+ MOZ_ASSERT(cx->options().wasm());
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "argument is not an object");
+ return false;
+ }
+
+ JSObject* unwrapped = CheckedUnwrap(&args.get(0).toObject());
+ if (!unwrapped || !unwrapped->is<WasmModuleObject>()) {
+ JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
+ return false;
+ }
+
+ Rooted<WasmModuleObject*> module(cx, &unwrapped->as<WasmModuleObject>());
+ RootedValue result(cx);
+ if (!module->module().extractCode(cx, &result))
+ return false;
+
+ args.rval().set(result);
+ 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;
+ }
+ args.rval().setBoolean(args[0].toObject().as<JSFunction>().isInterpretedLazy());
+ 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->hasScript() && fun->nonLazyScript()->isRelazifiable());
+ 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;
+ JSFlatString* flat = JS_FlattenString(cx, str);
+ if (!flat)
+ return false;
+
+ if (JS_FlatStringEqualsAscii(flat, "INCREMENTAL_MARK_STACK_BASE_CAPACITY")) {
+ args.rval().setNumber(uint32_t(js::INCREMENTAL_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
+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;
+ }
+
+ uint32_t zeal;
+ if (!ToUint32(cx, args.get(0), &zeal))
+ return false;
+
+ if (zeal > uint32_t(gc::ZealMode::Limit)) {
+ JS_ReportErrorASCII(cx, "gczeal argument out of range");
+ 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, (uint8_t)zeal, frequency);
+ 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].isInt32()) {
+ /* Schedule a GC to happen after |arg| allocations. */
+ JS_ScheduleGC(cx, args[0].toInt32());
+ } else if (args[0].isObject()) {
+ /* Ensure that |zone| is collected during the next GC. */
+ Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone();
+ PrepareZoneForGC(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(zone);
+ } else {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Bad argument - expecting integer, object or string");
+ 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.
+ */
+ JSRuntime* rt = cx->runtime();
+ rt->gc.evictNursery();
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ if (args[i].isObject()) {
+ if (!rt->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
+GCState(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;
+ }
+
+ const char* state = StateName(cx->runtime()->gc.state());
+ JSString* str = JS_NewStringCopyZ(cx, state);
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ 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;
+}
+#endif /* JS_GC_ZEAL */
+
+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_StringEqualsAscii(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;
+ }
+
+ JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
+ rt->gc.startDebugGC(gckind, budget);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GCSlice(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;
+ }
+
+ auto budget = SliceBudget::unlimited();
+ if (args.length() == 1) {
+ uint32_t work = 0;
+ if (!ToUint32(cx, args[0], &work))
+ return false;
+ budget = SliceBudget(WorkBudget(work));
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (!rt->gc.isIncrementalGCInProgress())
+ rt->gc.startDebugGC(GC_NORMAL, budget);
+ else
+ rt->gc.debugGCSlice(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;
+ }
+
+ cx->runtime()->gc.abortGC();
+ 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 : public JS::CallbackTracer
+{
+ RootedValue child_;
+ bool found_;
+
+ void onChild(const JS::GCCellPtr& thing) override {
+ if (thing.asCell() == child_.toGCThing())
+ found_ = true;
+ }
+
+ public:
+ HasChildTracer(JSContext* cx, HandleValue child)
+ : JS::CallbackTracer(cx, TraceWeakMapKeysValues), 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.isMarkable() || !child.isMarkable()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ HasChildTracer trc(cx, child);
+ TraceChildren(&trc, 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->compartment()->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->compartment()->savedStacks().count());
+ 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 (mozilla::IsNaN(maxDouble) || maxDouble < 0 || maxDouble > UINT32_MAX) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not a valid maximum frame count", NULL);
+ return false;
+ }
+ uint32_t max = uint32_t(maxDouble);
+ if (max > 0)
+ capture = JS::StackCapture(JS::MaxFrames(max));
+ }
+
+ JSCompartment* targetCompartment = cx->compartment();
+ if (args.length() >= 2) {
+ if (!args[1].isObject()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object", NULL);
+ return false;
+ }
+ RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject()));
+ if (!obj)
+ return false;
+ targetCompartment = obj->compartment();
+ }
+
+ RootedObject stack(cx);
+ {
+ AutoCompartment ac(cx, targetCompartment);
+ if (!JS::CaptureCurrentStack(cx, &stack, mozilla::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 = CheckedUnwrap(obj);
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Denied permission to object.");
+ return false;
+ }
+
+ JS::StackCapture capture(JS::FirstSubsumedFrame(cx, obj->compartment()->principals()));
+ if (args.length() > 1)
+ capture.as<JS::FirstSubsumedFrame>().ignoreSelfHosted = JS::ToBoolean(args[1]);
+
+ JS::RootedObject capturedStack(cx);
+ if (!JS::CaptureCurrentStack(cx, &capturedStack, mozilla::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());
+ JSAutoByteString utf8Cause;
+ if (!utf8Cause.encodeUtf8(cx, asyncCause)) {
+ MOZ_ASSERT(cx->isExceptionPending());
+ return false;
+ }
+
+ JS::AutoSetAsyncStackForNewCalls sas(cx, stack, utf8Cause.ptr(),
+ 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 void
+FinalizeExternalString(Zone* zone, const JSStringFinalizer* fin, char16_t* chars);
+
+static const JSStringFinalizer ExternalStringFinalizer =
+ { FinalizeExternalString };
+
+static void
+FinalizeExternalString(Zone* zone, const JSStringFinalizer* fin, char16_t* chars)
+{
+ MOZ_ASSERT(fin == &ExternalStringFinalizer);
+ js_free(chars);
+}
+
+static bool
+NewExternalString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorASCII(cx, "newExternalString takes exactly one string argument.");
+ return false;
+ }
+
+ RootedString str(cx, args[0].toString());
+ size_t len = str->length();
+
+ UniqueTwoByteChars buf(cx->pod_malloc<char16_t>(len));
+ if (!buf)
+ return false;
+
+ if (!JS_CopyStringChars(cx, mozilla::Range<char16_t>(buf.get(), len), str))
+ return false;
+
+ JSString* res = JS_NewExternalString(cx, buf.get(), len, &ExternalStringFinalizer);
+ if (!res)
+ return false;
+
+ mozilla::Unused << buf.release();
+ args.rval().setString(res);
+ return true;
+}
+
+static bool
+EnsureFlatString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorASCII(cx, "ensureFlatString takes exactly one string argument.");
+ return false;
+ }
+
+ JSFlatString* flat = args[0].toString()->ensureFlat(cx);
+ if (!flat)
+ return false;
+
+ args.rval().setString(flat);
+ 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::oom::THREAD_TYPE_MAX);
+ 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::oom::THREAD_TYPE_MAIN;
+ if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread))
+ return false;
+
+ if (targetThread == js::oom::THREAD_TYPE_NONE || targetThread >= js::oom::THREAD_TYPE_MAX) {
+ JS_ReportErrorASCII(cx, "Invalid thread type specified");
+ return false;
+ }
+
+ HelperThreadState().waitForAllThreads();
+ js::oom::SimulateOOMAfter(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);
+ args.rval().setBoolean(js::oom::HadSimulatedOOM());
+ js::oom::ResetSimulatedOOM();
+ return true;
+}
+
+static bool
+OOMTest(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1 || args.length() > 2) {
+ JS_ReportErrorASCII(cx, "oomTest() takes between 1 and 2 arguments.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "The first argument to oomTest() must be a function.");
+ return false;
+ }
+
+ if (args.length() == 2 && !args[1].isBoolean()) {
+ JS_ReportErrorASCII(cx, "The optional second argument to oomTest() must be a boolean.");
+ return false;
+ }
+
+ bool expectExceptionOnFailure = true;
+ if (args.length() == 2)
+ expectExceptionOnFailure = args[1].toBoolean();
+
+ // 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;
+
+ if (disableOOMFunctions) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
+
+ bool verbose = EnvVarIsDefined("OOM_VERBOSE");
+
+ unsigned threadStart = oom::THREAD_TYPE_MAIN;
+ unsigned threadEnd = oom::THREAD_TYPE_MAX;
+
+ // Test a single thread type if specified by the OOM_THREAD environment variable.
+ int threadOption = 0;
+ if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
+ if (threadOption < oom::THREAD_TYPE_MAIN || threadOption > oom::THREAD_TYPE_MAX) {
+ JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
+ return false;
+ }
+
+ threadStart = threadOption;
+ threadEnd = threadOption + 1;
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (rt->runningOOMTest) {
+ JS_ReportErrorASCII(cx, "Nested call to oomTest() is not allowed.");
+ return false;
+ }
+ rt->runningOOMTest = true;
+
+ MOZ_ASSERT(!cx->isExceptionPending());
+ rt->hadOutOfMemory = false;
+
+ JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
+
+ for (unsigned thread = threadStart; thread < threadEnd; thread++) {
+ if (verbose)
+ fprintf(stderr, "thread %d\n", thread);
+
+ HelperThreadState().waitForAllThreads();
+ js::oom::targetThread = thread;
+
+ unsigned allocation = 1;
+ bool handledOOM;
+ do {
+ if (verbose)
+ fprintf(stderr, " allocation %d\n", allocation);
+
+ MOZ_ASSERT(!cx->isExceptionPending());
+ MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
+
+ js::oom::SimulateOOMAfter(allocation, thread, false);
+
+ RootedValue result(cx);
+ bool ok = JS_CallFunction(cx, cx->global(), function,
+ HandleValueArray::empty(), &result);
+
+ handledOOM = js::oom::HadSimulatedOOM();
+ js::oom::ResetSimulatedOOM();
+
+ MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
+
+ if (ok) {
+ MOZ_ASSERT(!cx->isExceptionPending(),
+ "Thunk execution succeeded but an exception was raised - "
+ "missing error check?");
+ } else if (expectExceptionOnFailure) {
+ MOZ_ASSERT(cx->isExceptionPending(),
+ "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 OOM, 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.
+
+ cx->clearPendingException();
+ cx->runtime()->hadOutOfMemory = false;
+
+#ifdef JS_TRACE_LOGGING
+ // Reset the TraceLogger state if enabled.
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ if (logger->enabled()) {
+ while (logger->enabled())
+ logger->disable();
+ logger->enable(cx);
+ }
+#endif
+
+ allocation++;
+ } while (handledOOM);
+
+ if (verbose) {
+ fprintf(stderr, " finished after %d allocations\n", allocation - 2);
+ }
+ }
+
+ rt->runningOOMTest = false;
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+#ifdef SPIDERMONKEY_PROMISE
+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;
+ }
+
+ RootedNativeObject promise(cx, &args[0].toObject().as<NativeObject>());
+ int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32();
+ promise->setFixedSlot(PromiseSlot_Flags,
+ Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
+ promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
+
+ JS::dbg::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() || !IsPackedArray(&args[0].toObject())) {
+ JS_ReportErrorASCII(cx, "first argument must be a dense Array of Promise objects");
+ return false;
+ }
+ RootedNativeObject list(cx, &args[0].toObject().as<NativeObject>());
+ AutoObjectVector 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<AutoCompartment> ac;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ac.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &resolution))
+ 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<AutoCompartment> ac;
+ if (IsWrapper(promise)) {
+ promise = UncheckedUnwrap(promise);
+ ac.emplace(cx, promise);
+ if (!cx->compartment()->wrap(cx, &reason))
+ return false;
+ }
+
+ bool result = JS::RejectPromise(cx, promise, reason);
+ if (result)
+ args.rval().setUndefined();
+ return result;
+}
+
+#else
+
+static const js::Class FakePromiseClass = {
+ "Promise", JSCLASS_IS_ANONYMOUS
+};
+
+static bool
+MakeFakePromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &FakePromiseClass, nullptr));
+ if (!obj)
+ return false;
+
+ JS::dbg::onNewPromise(cx, obj);
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+SettleFakePromise(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "settleFakePromise", 1))
+ return false;
+ if (!args[0].isObject() || args[0].toObject().getClass() != &FakePromiseClass) {
+ JS_ReportErrorASCII(cx, "first argument must be a (fake) Promise object");
+ return false;
+ }
+
+ RootedObject promise(cx, &args[0].toObject());
+ JS::dbg::onPromiseSettled(cx, promise);
+ return true;
+}
+#endif // SPIDERMONKEY_PROMISE
+
+static unsigned finalizeCount = 0;
+
+static void
+finalize_counter_finalize(JSFreeOp* fop, JSObject* obj)
+{
+ ++finalizeCount;
+}
+
+static const JSClassOps FinalizeCounterClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ finalize_counter_finalize
+};
+
+static const JSClass FinalizeCounterClass = {
+ "FinalizeCounter",
+ JSCLASS_IS_ANONYMOUS |
+ 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);
+
+ DumpHeapNurseryBehaviour nurseryBehaviour = js::IgnoreNurseryObjects;
+ FILE* dumpFile = nullptr;
+
+ unsigned i = 0;
+ if (args.length() > i) {
+ Value v = args[i];
+ if (v.isString()) {
+ JSString* str = v.toString();
+ bool same = false;
+ if (!JS_StringEqualsAscii(cx, str, "collectNurseryBeforeDump", &same))
+ return false;
+ if (same) {
+ nurseryBehaviour = js::CollectNurseryBeforeDump;
+ ++i;
+ }
+ }
+ }
+
+ if (args.length() > i) {
+ Value v = args[i];
+ if (v.isString()) {
+ if (!fuzzingSafe) {
+ RootedString str(cx, v.toString());
+ JSAutoByteString fileNameBytes;
+ if (!fileNameBytes.encodeLatin1(cx, str))
+ return false;
+ const char* fileName = fileNameBytes.ptr();
+ dumpFile = fopen(fileName, "w");
+ if (!dumpFile) {
+ fileNameBytes.clear();
+ if (!fileNameBytes.encodeUtf8(cx, str))
+ return false;
+ JS_ReportErrorUTF8(cx, "can't open %s", fileNameBytes.ptr());
+ return false;
+ }
+ }
+ ++i;
+ }
+ }
+
+ if (i != args.length()) {
+ JS_ReportErrorASCII(cx, "bad arguments passed to dumpHeap");
+ if (dumpFile)
+ fclose(dumpFile);
+ return false;
+ }
+
+ js::DumpHeap(cx, dumpFile ? dumpFile : stdout, nurseryBehaviour);
+
+ if (dumpFile)
+ fclose(dumpFile);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+Terminate(JSContext* cx, unsigned arg, Value* vp)
+{
+#ifdef JS_MORE_DETERMINISTIC
+ // Print a message to stderr in more-deterministic builds to help jsfunfuzz
+ // find uncatchable-exception bugs.
+ fprintf(stderr, "terminate called\n");
+#endif
+
+ JS_ClearPendingException(cx);
+ return false;
+}
+
+static bool
+ReadSPSProfilingStack(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()->spsProfiler.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->runtime()->isProfilerSamplingEnabled()) {
+ args.rval().setObject(*stack);
+ return true;
+ }
+
+ struct InlineFrameInfo
+ {
+ InlineFrameInfo(const char* kind, UniqueChars&& label)
+ : kind(kind), label(mozilla::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_Baseline:
+ frameKindStr = "baseline";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Ion:
+ frameKindStr = "ion";
+ break;
+ case JS::ProfilingFrameIterator::Frame_Wasm:
+ frameKindStr = "wasm";
+ break;
+ default:
+ frameKindStr = "unknown";
+ }
+
+ if (!frameInfo.back().emplaceBack(frameKindStr, mozilla::Move(frames[i].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, NewBuiltinClassInstance<PlainObject>(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;
+
+ auto chars = inlineFrame.label.release();
+ frameLabel = NewString<CanGC>(cx, reinterpret_cast<Latin1Char*>(chars), strlen(chars));
+ if (!frameLabel)
+ return false;
+
+ if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel, propAttrs))
+ return false;
+
+ idx = INT_TO_JSID(inlineFrameNo);
+ if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0))
+ return false;
+
+ ++inlineFrameNo;
+ }
+
+ // Push inline array into main array.
+ idx = INT_TO_JSID(physicalFrameNo);
+ if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0))
+ return false;
+
+ ++physicalFrameNo;
+ }
+
+ 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);
+ 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, NewBuiltinClassInstance<PlainObject>(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,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
+ }
+
+ if (!JS_DefineProperty(cx, obj, "stack", stack, 0,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ 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 = INT_TO_JSID(stackIndex);
+ RootedObject callee(cx, iter.callee(cx));
+ if (!JS_DefinePropertyById(cx, stack, id, callee, 0,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ 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
+ cx->runtime()->setIonBailAfter(args[0].toInt32());
+#endif
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!jit::IsBaselineEnabled(cx)) {
+ JSString* error = JS_NewStringCopyZ(cx, "Baseline is disabled.");
+ if(!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ JSScript* script = cx->currentScript();
+ if (script && script->getWarmUpResetCount() >= 20) {
+ JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ 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)) {
+ JSString* error = JS_NewStringCopyZ(cx, "Ion is disabled.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+
+ ScriptFrameIter iter(cx);
+ if (iter.isIon()) {
+ // Reset the counter of the IonScript's script.
+ jit::JitFrameIterator jitIter(cx);
+ ++jitIter;
+ jitIter.script()->resetWarmUpResetCounter();
+ } else {
+ // Check if we missed multiple attempts at compiling the innermost script.
+ JSScript* script = cx->currentScript();
+ if (script && script->getWarmUpResetCount() >= 20) {
+ JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+ if (!error)
+ return false;
+
+ args.rval().setString(error);
+ return true;
+ }
+ }
+
+ 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
+SetJitCompilerOption(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 2) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String.");
+ return false;
+ }
+
+ if (!args[1].isInt32()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument must be an Int32.");
+ return false;
+ }
+
+ JSFlatString* strArg = JS_FlattenString(cx, args[0].toString());
+ if (!strArg)
+ return false;
+
+#define JIT_COMPILER_MATCH(key, string) \
+ else if (JS_FlatStringEqualsAscii(strArg, string)) \
+ opt = JSJITCOMPILER_ ## key;
+
+ JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
+ if (false) {}
+ JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
+#undef JIT_COMPILER_MATCH
+
+ if (opt == JSJITCOMPILER_NOT_AN_OPTION) {
+ ReportUsageErrorASCII(cx, callee, "First argument does not name a valid option (see jsapi.h).");
+ return false;
+ }
+
+ int32_t number = args[1].toInt32();
+ if (number < 0)
+ number = -1;
+
+ // Throw if disabling the JITs and there's JIT code on the stack, to avoid
+ // assertion failures.
+ if ((opt == JSJITCOMPILER_BASELINE_ENABLE || opt == JSJITCOMPILER_ION_ENABLE) &&
+ number == 0)
+ {
+ js::jit::JitActivationIterator iter(cx->runtime());
+ if (!iter.done()) {
+ JS_ReportErrorASCII(cx, "Can't turn off JITs with JIT code on the stack.");
+ return false;
+ }
+ }
+
+ JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));
+
+ 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;
+}
+
+class CloneBufferObject : public NativeObject {
+ static const JSPropertySpec props_[2];
+ static const size_t DATA_SLOT = 0;
+ static const size_t LENGTH_SLOT = 1;
+ static const size_t NUM_SLOTS = 2;
+
+ public:
+ static const Class class_;
+
+ static CloneBufferObject* Create(JSContext* cx) {
+ RootedObject obj(cx, JS_NewObject(cx, Jsvalify(&class_)));
+ if (!obj)
+ return nullptr;
+ obj->as<CloneBufferObject>().setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
+ obj->as<CloneBufferObject>().setReservedSlot(LENGTH_SLOT, Int32Value(0));
+
+ 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>();
+ if (!data) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ buffer->steal(data.get());
+ obj->setData(data.release());
+ return obj;
+ }
+
+ JSStructuredCloneData* data() const {
+ return static_cast<JSStructuredCloneData*>(getReservedSlot(DATA_SLOT).toPrivate());
+ }
+
+ void setData(JSStructuredCloneData* aData) {
+ MOZ_ASSERT(!data());
+ setReservedSlot(DATA_SLOT, PrivateValue(aData));
+ }
+
+ // Discard an owned clone buffer.
+ void discard() {
+ if (data()) {
+ JSAutoStructuredCloneBuffer clonebuf(JS::StructuredCloneScope::SameProcessSameThread, nullptr, nullptr);
+ clonebuf.adopt(Move(*data()));
+ }
+ setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
+ }
+
+ static bool
+ setCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "clonebuffer setter requires a single string argument");
+ return false;
+ }
+ if (!args[0].isString()) {
+ JS_ReportErrorASCII(cx, "clonebuffer value must be a string");
+ return false;
+ }
+
+ if (fuzzingSafe) {
+ // A manually-created clonebuffer could easily trigger a crash
+ args.rval().setUndefined();
+ return true;
+ }
+
+ Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+ obj->discard();
+
+ char* str = JS_EncodeString(cx, args[0].toString());
+ if (!str)
+ return false;
+ size_t nbytes = JS_GetStringLength(args[0].toString());
+ MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0);
+ auto buf = js::MakeUnique<JSStructuredCloneData>(nbytes, nbytes, nbytes);
+ js_memcpy(buf->Start(), str, nbytes);
+ JS_free(cx, str);
+ obj->setData(buf.release());
+
+ 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
+ getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+ Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+ MOZ_ASSERT(args.length() == 0);
+
+ if (!obj->data()) {
+ args.rval().setUndefined();
+ 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;
+ }
+
+ size_t size = obj->data()->Size();
+ UniqueChars buffer(static_cast<char*>(js_malloc(size)));
+ if (!buffer) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ auto iter = obj->data()->Iter();
+ obj->data()->ReadBytes(iter, buffer.get(), size);
+ 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 void Finalize(FreeOp* fop, JSObject* obj) {
+ obj->as<CloneBufferObject>().discard();
+ }
+};
+
+static const ClassOps CloneBufferObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ CloneBufferObject::Finalize
+};
+
+const Class 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_PS_END
+};
+
+static mozilla::Maybe<JS::StructuredCloneScope>
+ParseCloneScope(JSContext* cx, HandleString str)
+{
+ mozilla::Maybe<JS::StructuredCloneScope> scope;
+
+ JSAutoByteString scopeStr(cx, str);
+ if (!scopeStr)
+ return scope;
+
+ if (strcmp(scopeStr.ptr(), "SameProcessSameThread") == 0)
+ scope.emplace(JS::StructuredCloneScope::SameProcessSameThread);
+ else if (strcmp(scopeStr.ptr(), "SameProcessDifferentThread") == 0)
+ scope.emplace(JS::StructuredCloneScope::SameProcessDifferentThread);
+ else if (strcmp(scopeStr.ptr(), "DifferentProcess") == 0)
+ scope.emplace(JS::StructuredCloneScope::DifferentProcess);
+
+ return scope;
+}
+
+static bool
+Serialize(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ 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;
+ JSAutoByteString poli(cx, str);
+ if (!poli)
+ return false;
+
+ if (strcmp(poli.ptr(), "allow") == 0) {
+ // default
+ } else if (strcmp(poli.ptr(), "deny") == 0) {
+ policy.denySharedArrayBuffer();
+ } 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, nullptr, nullptr);
+ }
+ }
+
+ if (!clonebuf)
+ clonebuf.emplace(JS::StructuredCloneScope::SameProcessSameThread, nullptr, 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 (!args.get(0).isObject() || !args[0].toObject().is<CloneBufferObject>()) {
+ JS_ReportErrorASCII(cx, "deserialize requires a clonebuffer argument");
+ return false;
+ }
+
+ JS::StructuredCloneScope scope = JS::StructuredCloneScope::SameProcessSameThread;
+ if (args.get(1).isObject()) {
+ RootedObject opts(cx, &args[1].toObject());
+ if (!opts)
+ return false;
+
+ RootedValue v(cx);
+ 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;
+ }
+
+ scope = *maybeScope;
+ }
+ }
+
+ Rooted<CloneBufferObject*> obj(cx, &args[0].toObject().as<CloneBufferObject>());
+
+ // 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, nullptr, nullptr))
+ {
+ return false;
+ }
+ args.rval().set(deserialized);
+
+ 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);
+#ifdef JS_MORE_DETERMINISTIC
+ // Always return 0 to get consistent output with and without --no-threads.
+ args.rval().setInt32(0);
+#else
+ if (CanUseExtraThreads())
+ args.rval().setInt32(HelperThreadState().threadCount);
+ else
+ args.rval().setInt32(0);
+#endif
+ return true;
+}
+
+static bool
+TimesAccessed(JSContext* cx, unsigned argc, Value* vp)
+{
+ static int32_t accessed = 0;
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(++accessed);
+ return true;
+}
+
+#ifdef JS_TRACE_LOGGING
+static bool
+EnableTraceLogger(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ if (!TraceLoggerEnable(logger, cx))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DisableTraceLogger(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ args.rval().setBoolean(TraceLoggerDisable(logger));
+
+ return true;
+}
+#endif
+
+#ifdef DEBUG
+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->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
+ return true;
+}
+
+#ifdef NIGHTLY_BUILD
+static bool
+ObjectAddress(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()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+#ifdef JS_MORE_DETERMINISTIC
+ args.rval().setInt32(0);
+#else
+ void* ptr = js::UncheckedUnwrap(&args[0].toObject(), true);
+ char buffer[64];
+ SprintfLiteral(buffer, "%p", ptr);
+
+ JSString* str = JS_NewStringCopyZ(cx, buffer);
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+#endif
+
+ return true;
+}
+
+static bool
+SharedAddress(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()) {
+ RootedObject callee(cx, &args.callee());
+ ReportUsageErrorASCII(cx, callee, "Expected object");
+ return false;
+ }
+
+#ifdef JS_MORE_DETERMINISTIC
+ args.rval().setString(cx->staticStrings().getUint(0));
+#else
+ RootedObject obj(cx, CheckedUnwrap(&args[0].toObject()));
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "Permission denied to access object");
+ 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);
+#endif
+
+ return true;
+}
+#endif
+
+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);
+ }
+
+ char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
+ if (!buf)
+ return false;
+
+ RootedString str(cx);
+ if (!(str = JS_NewStringCopyZ(cx, buf)))
+ return false;
+ JS_smprintf_free(buf);
+
+ 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);
+ void* buf = cx->runtime()->onOutOfMemoryCanGC(AllocFunction::Malloc, JSRuntime::LARGE_ALLOCATION);
+ js_free(buf);
+ args.rval().setUndefined();
+ return true;
+}
+
+namespace heaptools {
+
+typedef UniqueTwoByteChars EdgeName;
+
+// 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_(Move(name)) { }
+ BackEdge(BackEdge&& rhs) : predecessor_(rhs.predecessor_), name_(Move(rhs.name_)) { }
+ BackEdge& operator=(BackEdge&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~BackEdge();
+ new(this) BackEdge(Move(rhs));
+ return *this;
+ }
+
+ EdgeName forgetName() { return 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 {
+ typedef BackEdge NodeData;
+ typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal;
+
+ 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 = DuplicateString(cx, edge.name.get());
+ if (!edgeName)
+ return false;
+ *backEdge = mozilla::Move(BackEdge(origin, 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))
+ 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.
+ bool recordPath(Traversal& traversal) {
+ JS::ubi::Node here = target;
+
+ do {
+ Traversal::NodeMap::Ptr p = traversal.visited.lookup(here);
+ MOZ_ASSERT(p);
+ JS::ubi::Node predecessor = p->value().predecessor();
+ if (!nodes.append(predecessor.exposeToJS()) ||
+ !edges.append(p->value().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 (argc < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
+ "findPath", "1", "");
+ 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()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", NULL);
+ return false;
+ }
+
+ if (!args[1].isObject() && !args[1].isString() && !args[1].isSymbol()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", NULL);
+ 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.init() || !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();
+ RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!result)
+ return false;
+ result->ensureDenseInitializedLength(cx, 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, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!obj)
+ return false;
+
+ RootedValue wrapped(cx, nodes[i]);
+ if (!cx->compartment()->wrap(cx, &wrapped))
+ return false;
+
+ if (!JS_DefineProperty(cx, obj, "node", wrapped,
+ JSPROP_ENUMERATE, nullptr, nullptr))
+ return false;
+
+ heaptools::EdgeName edgeName = Move(edges[i]);
+
+ RootedString edgeStr(cx, NewString<CanGC>(cx, edgeName.get(), js_strlen(edgeName.get())));
+ if (!edgeStr)
+ return false;
+ mozilla::Unused << edgeName.release(); // edgeStr acquired ownership
+
+ if (!JS_DefineProperty(cx, obj, "edge", edgeStr, JSPROP_ENUMERATE, nullptr, nullptr))
+ 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", 3))
+ 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()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[0], nullptr,
+ "not an object, string, or symbol", nullptr);
+ return false;
+ }
+
+ if (!args[1].isObject() || !args[1].toObject().is<ArrayObject>()) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[1], nullptr,
+ "not an array object", nullptr);
+ return false;
+ }
+
+ RootedArrayObject objs(cx, &args[1].toObject().as<ArrayObject>());
+ size_t length = objs->getDenseInitializedLength();
+ if (length == 0) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[1], nullptr,
+ "not a dense array object with one or more elements", nullptr);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ RootedValue el(cx, objs->getDenseElement(i));
+ if (!el.isObject() && !el.isString() && !el.isSymbol()) {
+ JS_ReportErrorASCII(cx, "Each target must be an object, string, or symbol");
+ return false;
+ }
+ }
+
+ int32_t maxNumPaths;
+ if (!JS::ToInt32(cx, args[2], &maxNumPaths))
+ return false;
+ if (maxNumPaths <= 0) {
+ ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+ JSDVG_SEARCH_STACK, args[2], nullptr,
+ "not greater than 0", nullptr);
+ 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::AutoCheckCannotGC noGC(cx);
+
+ JS::ubi::NodeSet targets;
+ if (!targets.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+ JS::ubi::Node root(args[0]);
+ auto maybeShortestPaths = JS::ubi::ShortestPaths::Create(cx, noGC, maxNumPaths,
+ root, mozilla::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(mozilla::Move(part->name())))
+ {
+ return false;
+ }
+ }
+
+ return values.back().append(mozilla::Move(pathVals.get())) &&
+ names.back().append(mozilla::Move(pathNames));
+ });
+
+ if (!ok)
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(values.length() == names.length());
+ MOZ_ASSERT(values.length() == length);
+
+ RootedArrayObject results(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!results)
+ return false;
+ results->ensureDenseInitializedLength(cx, 0, length);
+
+ for (size_t i = 0; i < length; i++) {
+ size_t numPaths = values[i].length();
+ MOZ_ASSERT(names[i].length() == numPaths);
+
+ RootedArrayObject pathsArray(cx, NewDenseFullyAllocatedArray(cx, numPaths));
+ if (!pathsArray)
+ return false;
+ pathsArray->ensureDenseInitializedLength(cx, 0, numPaths);
+
+ for (size_t j = 0; j < numPaths; j++) {
+ size_t pathLength = values[i][j].length();
+ MOZ_ASSERT(names[i][j].length() == pathLength);
+
+ RootedArrayObject path(cx, NewDenseFullyAllocatedArray(cx, pathLength));
+ if (!path)
+ return false;
+ path->ensureDenseInitializedLength(cx, 0, pathLength);
+
+ for (size_t k = 0; k < pathLength; k++) {
+ RootedPlainObject part(cx, NewBuiltinClassInstance<PlainObject>(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;
+ }
+
+ AutoStableStringChars strChars(cx);
+ if (!strChars.initTwoByte(cx, str))
+ return false;
+
+ mozilla::Range<const char16_t> chars = strChars.twoByteRange();
+ size_t srclen = chars.length();
+ const char16_t* src = chars.begin().get();
+
+ JS::AutoFilename filename;
+ unsigned lineno;
+
+ JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filename.get(), lineno);
+ options.setNoScriptRval(true);
+
+ JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+ RootedScript script(cx);
+ if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script))
+ return false;
+
+ if (global) {
+ global = CheckedUnwrap(global);
+ 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);
+ RootedObject lexicalScope(cx);
+
+ {
+ // If we're switching globals here, ExecuteInGlobalAndReturnScope will
+ // take care of cloning the script into that compartment before
+ // executing it.
+ AutoCompartment ac(cx, global);
+
+ if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope))
+ return false;
+
+ varObj = lexicalScope->enclosingEnvironment();
+ }
+
+ RootedObject rv(cx, JS_NewPlainObject(cx));
+ if (!rv)
+ return false;
+
+ RootedValue varObjVal(cx, ObjectValue(*varObj));
+ if (!cx->compartment()->wrap(cx, &varObjVal))
+ return false;
+ if (!JS_SetProperty(cx, rv, "vars", varObjVal))
+ return false;
+
+ RootedValue lexicalScopeVal(cx, ObjectValue(*lexicalScope));
+ if (!cx->compartment()->wrap(cx, &lexicalScopeVal))
+ return false;
+ if (!JS_SetProperty(cx, rv, "lexicals", lexicalScopeVal))
+ return false;
+
+ args.rval().setObject(*rv);
+ return true;
+}
+
+static bool
+ShellCloneAndExecuteScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "cloneAndExecuteScript", 2))
+ return false;
+
+ RootedString str(cx, ToString(cx, args[0]));
+ if (!str)
+ return false;
+
+ RootedObject global(cx, ToObject(cx, args[1]));
+ if (!global)
+ return false;
+
+ AutoStableStringChars strChars(cx);
+ if (!strChars.initTwoByte(cx, str))
+ return false;
+
+ mozilla::Range<const char16_t> chars = strChars.twoByteRange();
+ size_t srclen = chars.length();
+ const char16_t* src = chars.begin().get();
+
+ JS::AutoFilename filename;
+ unsigned lineno;
+
+ JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(filename.get(), lineno);
+ options.setNoScriptRval(true);
+
+ JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+ RootedScript script(cx);
+ if (!JS::Compile(cx, options, srcBuf, &script))
+ return false;
+
+ global = CheckedUnwrap(global);
+ 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;
+ }
+
+ AutoCompartment ac(cx, global);
+
+ JS::RootedValue rval(cx);
+ if (!JS::CloneAndExecuteScript(cx, script, &rval))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+IsSimdAvailable(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+#if defined(JS_CODEGEN_NONE) || !defined(ENABLE_SIMD)
+ bool available = false;
+#else
+ bool available = cx->jitSupportsSimd();
+#endif
+ args.rval().set(BooleanValue(available));
+ 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;
+ }
+
+ JSFunction* fun = &args[0].toObject().as<JSFunction>();
+ if (fun->isNative()) {
+ JS_ReportErrorASCII(cx, "Argument must be a scripted function");
+ return false;
+ }
+
+ RootedScript script(cx, fun->getOrCreateScript(cx));
+ 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;
+
+ str->dumpRepresentation(stderr, 0);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool
+SetLazyParsingDisabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool disable = !args.hasDefined(0) || ToBoolean(args[0]);
+ cx->compartment()->behaviors().setDisableLazyParsing(disable);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+SetDiscardSource(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ bool discard = !args.hasDefined(0) || ToBoolean(args[0]);
+ cx->compartment()->behaviors().setDiscardSource(discard);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetConstructorName(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "getConstructorName", 1))
+ return false;
+
+ if (!args[0].isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
+ "getConstructorName", "Object",
+ InformalValueTypeName(args[0]));
+ return false;
+ }
+
+ RootedAtom name(cx);
+ if (!args[0].toObject().constructorDisplayAtom(cx, &name))
+ return false;
+
+ if (name) {
+ args.rval().setString(name);
+ } else {
+ args.rval().setNull();
+ }
+ return true;
+}
+
+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);
+ }
+
+ static const Class cls = { "AllocationMarker" };
+
+ auto newKind = allocateInsideNursery ? GenericObject : TenuredObject;
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &cls, nullptr, newKind));
+ 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, void* data)
+{
+ auto info = static_cast<MajorGC*>(data);
+ if (!(info->phases & (1 << status)))
+ return;
+
+ if (info->depth > 0) {
+ info->depth--;
+ JS::PrepareForFullGC(cx);
+ JS::GCForReason(cx, GC_NORMAL, JS::gcreason::API);
+ info->depth++;
+ }
+}
+
+struct MinorGC {
+ int32_t phases;
+ bool active;
+};
+
+static void
+minorGC(JSContext* cx, JSGCStatus status, void* data)
+{
+ auto info = static_cast<MinorGC*>(data);
+ if (!(info->phases & (1 << status)))
+ return;
+
+ if (info->active) {
+ info->active = false;
+ cx->gc.evictNursery(JS::gcreason::DEBUG_GC);
+ info->active = true;
+ }
+}
+
+// Process global, should really be runtime-local. Also, the final one of these
+// is currently leaked, since they are only deleted when changing.
+MajorGC* prevMajorGC = nullptr;
+MinorGC* prevMinorGC = 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;
+ JSAutoByteString action(cx, str);
+ if (!action)
+ return false;
+
+ int32_t phases = 0;
+ if ((strcmp(action.ptr(), "minorGC") == 0) || (strcmp(action.ptr(), "majorGC") == 0)) {
+ 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;
+ JSAutoByteString phasesStr(cx, str);
+ if (!phasesStr)
+ return false;
+
+ if (strcmp(phasesStr.ptr(), "begin") == 0)
+ phases = (1 << JSGC_BEGIN);
+ else if (strcmp(phasesStr.ptr(), "end") == 0)
+ phases = (1 << JSGC_END);
+ else if (strcmp(phasesStr.ptr(), "both") == 0)
+ phases = (1 << JSGC_BEGIN) | (1 << JSGC_END);
+ else {
+ JS_ReportErrorASCII(cx, "Invalid callback phase");
+ return false;
+ }
+ }
+ }
+
+ if (gcCallback::prevMajorGC) {
+ JS_SetGCCallback(cx, nullptr, nullptr);
+ js_delete<gcCallback::MajorGC>(gcCallback::prevMajorGC);
+ gcCallback::prevMajorGC = nullptr;
+ }
+
+ if (gcCallback::prevMinorGC) {
+ JS_SetGCCallback(cx, nullptr, nullptr);
+ js_delete<gcCallback::MinorGC>(gcCallback::prevMinorGC);
+ gcCallback::prevMinorGC = nullptr;
+ }
+
+ if (strcmp(action.ptr(), "minorGC") == 0) {
+ auto info = js_new<gcCallback::MinorGC>();
+ if (!info) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ info->phases = phases;
+ info->active = true;
+ JS_SetGCCallback(cx, gcCallback::minorGC, info);
+ } else if (strcmp(action.ptr(), "majorGC") == 0) {
+ 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 > int32_t(gcstats::Statistics::MAX_NESTING - 4)) {
+ JS_ReportErrorASCII(cx, "Nesting depth too large, would overflow");
+ return false;
+ }
+
+ auto info = js_new<gcCallback::MajorGC>();
+ if (!info) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ info->phases = phases;
+ info->depth = depth;
+ JS_SetGCCallback(cx, gcCallback::majorGC, info);
+ } else {
+ JS_ReportErrorASCII(cx, "Unknown GC callback action");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ 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;
+ }
+
+ RootedObject global(cx);
+ if (args.hasDefined(0)) {
+ global = ToObject(cx, args[0]);
+ if (!global) {
+ JS_ReportErrorASCII(cx, "First argument should be an object");
+ return false;
+ }
+ global = CheckedUnwrap(global);
+ 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);
+ }
+
+ size_t length = 0;
+ char* content = nullptr;
+ {
+ AutoCompartment ac(cx, global);
+ content = js::GetCodeCoverageSummary(cx, &length);
+ }
+
+ if (!content)
+ return false;
+
+ JSString* str = JS_NewStringCopyN(cx, content, length);
+ free(content);
+
+ 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->compartment()->ensureRandomNumberGenerator();
+ cx->compartment()->randomNumberGenerator.ref().setState(seed0, seed1);
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static ModuleEnvironmentObject*
+GetModuleEnvironment(JSContext* cx, HandleValue moduleValue)
+{
+ RootedModuleObject module(cx, &moduleValue.toObject().as<ModuleObject>());
+
+ // Use the initial environment so that tests can check bindings exists
+ // before they have been instantiated.
+ RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
+ MOZ_ASSERT(env);
+ MOZ_ASSERT_IF(module->environment(), module->environment() == env);
+
+ return env;
+}
+
+static bool
+GetModuleEnvironmentNames(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 (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+ JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
+ return false;
+ }
+
+ RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+ Rooted<IdVector> ids(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, env, &ids))
+ return false;
+
+ uint32_t length = ids.length();
+ RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!array)
+ return false;
+
+ array->setDenseInitializedLength(length);
+ for (uint32_t i = 0; i < length; i++)
+ array->initDenseElement(i, StringValue(JSID_TO_STRING(ids[i])));
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+static bool
+GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 2) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+ JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
+ return false;
+ }
+
+ if (!args[1].isString()) {
+ JS_ReportErrorASCII(cx, "Second argument should be a string");
+ return false;
+ }
+
+ RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+ RootedString name(cx, args[1].toString());
+ RootedId id(cx);
+ if (!JS_StringToId(cx, name, &id))
+ return false;
+
+ return GetProperty(cx, env, env, id, args.rval());
+}
+
+#ifdef DEBUG
+static const char*
+AssertionTypeToString(irregexp::RegExpAssertion::AssertionType type)
+{
+ switch (type) {
+ case irregexp::RegExpAssertion::START_OF_LINE:
+ return "START_OF_LINE";
+ case irregexp::RegExpAssertion::START_OF_INPUT:
+ return "START_OF_INPUT";
+ case irregexp::RegExpAssertion::END_OF_LINE:
+ return "END_OF_LINE";
+ case irregexp::RegExpAssertion::END_OF_INPUT:
+ return "END_OF_INPUT";
+ case irregexp::RegExpAssertion::BOUNDARY:
+ return "BOUNDARY";
+ case irregexp::RegExpAssertion::NON_BOUNDARY:
+ return "NON_BOUNDARY";
+ case irregexp::RegExpAssertion::NOT_AFTER_LEAD_SURROGATE:
+ return "NOT_AFTER_LEAD_SURROGATE";
+ case irregexp::RegExpAssertion::NOT_IN_SURROGATE_PAIR:
+ return "NOT_IN_SURROGATE_PAIR";
+ }
+ MOZ_CRASH("unexpected AssertionType");
+}
+
+static JSObject*
+ConvertRegExpTreeToObject(JSContext* cx, irregexp::RegExpTree* tree)
+{
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return nullptr;
+
+ auto IntProp = [](JSContext* cx, HandleObject obj,
+ const char* name, int32_t value) {
+ RootedValue val(cx, Int32Value(value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto BooleanProp = [](JSContext* cx, HandleObject obj,
+ const char* name, bool value) {
+ RootedValue val(cx, BooleanValue(value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto StringProp = [](JSContext* cx, HandleObject obj,
+ const char* name, const char* value) {
+ RootedString valueStr(cx, JS_NewStringCopyZ(cx, value));
+ if (!valueStr)
+ return false;
+
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto ObjectProp = [](JSContext* cx, HandleObject obj,
+ const char* name, HandleObject value) {
+ RootedValue val(cx, ObjectValue(*value));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto CharVectorProp = [](JSContext* cx, HandleObject obj,
+ const char* name, const irregexp::CharacterVector& data) {
+ RootedString valueStr(cx, JS_NewUCStringCopyN(cx, data.begin(), data.length()));
+ if (!valueStr)
+ return false;
+
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ auto TreeProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name, irregexp::RegExpTree* tree) {
+ RootedObject treeObj(cx, ConvertRegExpTreeToObject(cx, tree));
+ if (!treeObj)
+ return false;
+ return ObjectProp(cx, obj, name, treeObj);
+ };
+
+ auto TreeVectorProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name,
+ const irregexp::RegExpTreeVector& nodes) {
+ size_t len = nodes.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ RootedObject child(cx, ConvertRegExpTreeToObject(cx, nodes[i]));
+ if (!child)
+ return false;
+
+ RootedValue childVal(cx, ObjectValue(*child));
+ if (!JS_SetElement(cx, array, i, childVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ auto CharRangesProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name,
+ const irregexp::CharacterRangeVector& ranges) {
+ size_t len = ranges.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ const irregexp::CharacterRange& range = ranges[i];
+ RootedObject rangeObj(cx, JS_NewPlainObject(cx));
+ if (!rangeObj)
+ return false;
+
+ auto CharProp = [](JSContext* cx, HandleObject obj,
+ const char* name, char16_t c) {
+ RootedString valueStr(cx, JS_NewUCStringCopyN(cx, &c, 1));
+ if (!valueStr)
+ return false;
+ RootedValue val(cx, StringValue(valueStr));
+ return JS_SetProperty(cx, obj, name, val);
+ };
+
+ if (!CharProp(cx, rangeObj, "from", range.from()))
+ return false;
+ if (!CharProp(cx, rangeObj, "to", range.to()))
+ return false;
+
+ RootedValue rangeVal(cx, ObjectValue(*rangeObj));
+ if (!JS_SetElement(cx, array, i, rangeVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ auto ElemProp = [&ObjectProp](JSContext* cx, HandleObject obj,
+ const char* name, const irregexp::TextElementVector& elements) {
+ size_t len = elements.length();
+ RootedObject array(cx, JS_NewArrayObject(cx, len));
+ if (!array)
+ return false;
+
+ for (size_t i = 0; i < len; i++) {
+ const irregexp::TextElement& element = elements[i];
+ RootedObject elemTree(cx, ConvertRegExpTreeToObject(cx, element.tree()));
+ if (!elemTree)
+ return false;
+
+ RootedValue elemTreeVal(cx, ObjectValue(*elemTree));
+ if (!JS_SetElement(cx, array, i, elemTreeVal))
+ return false;
+ }
+ return ObjectProp(cx, obj, name, array);
+ };
+
+ if (tree->IsDisjunction()) {
+ if (!StringProp(cx, obj, "type", "Disjunction"))
+ return nullptr;
+ irregexp::RegExpDisjunction* t = tree->AsDisjunction();
+ if (!TreeVectorProp(cx, obj, "alternatives", t->alternatives()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAlternative()) {
+ if (!StringProp(cx, obj, "type", "Alternative"))
+ return nullptr;
+ irregexp::RegExpAlternative* t = tree->AsAlternative();
+ if (!TreeVectorProp(cx, obj, "nodes", t->nodes()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAssertion()) {
+ if (!StringProp(cx, obj, "type", "Assertion"))
+ return nullptr;
+ irregexp::RegExpAssertion* t = tree->AsAssertion();
+ if (!StringProp(cx, obj, "assertion_type", AssertionTypeToString(t->assertion_type())))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsCharacterClass()) {
+ if (!StringProp(cx, obj, "type", "CharacterClass"))
+ return nullptr;
+ irregexp::RegExpCharacterClass* t = tree->AsCharacterClass();
+ if (!BooleanProp(cx, obj, "is_negated", t->is_negated()))
+ return nullptr;
+ LifoAlloc* alloc = &cx->tempLifoAlloc();
+ if (!CharRangesProp(cx, obj, "ranges", t->ranges(alloc)))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsAtom()) {
+ if (!StringProp(cx, obj, "type", "Atom"))
+ return nullptr;
+ irregexp::RegExpAtom* t = tree->AsAtom();
+ if (!CharVectorProp(cx, obj, "data", t->data()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsText()) {
+ if (!StringProp(cx, obj, "type", "Text"))
+ return nullptr;
+ irregexp::RegExpText* t = tree->AsText();
+ if (!ElemProp(cx, obj, "elements", t->elements()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsQuantifier()) {
+ if (!StringProp(cx, obj, "type", "Quantifier"))
+ return nullptr;
+ irregexp::RegExpQuantifier* t = tree->AsQuantifier();
+ if (!IntProp(cx, obj, "min", t->min()))
+ return nullptr;
+ if (!IntProp(cx, obj, "max", t->max()))
+ return nullptr;
+ if (!StringProp(cx, obj, "quantifier_type",
+ t->is_possessive() ? "POSSESSIVE"
+ : t->is_non_greedy() ? "NON_GREEDY"
+ : "GREEDY"))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsCapture()) {
+ if (!StringProp(cx, obj, "type", "Capture"))
+ return nullptr;
+ irregexp::RegExpCapture* t = tree->AsCapture();
+ if (!IntProp(cx, obj, "index", t->index()))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsLookahead()) {
+ if (!StringProp(cx, obj, "type", "Lookahead"))
+ return nullptr;
+ irregexp::RegExpLookahead* t = tree->AsLookahead();
+ if (!BooleanProp(cx, obj, "is_positive", t->is_positive()))
+ return nullptr;
+ if (!TreeProp(cx, obj, "body", t->body()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsBackReference()) {
+ if (!StringProp(cx, obj, "type", "BackReference"))
+ return nullptr;
+ irregexp::RegExpBackReference* t = tree->AsBackReference();
+ if (!IntProp(cx, obj, "index", t->index()))
+ return nullptr;
+ return obj;
+ }
+ if (tree->IsEmpty()) {
+ if (!StringProp(cx, obj, "type", "Empty"))
+ return nullptr;
+ return obj;
+ }
+
+ MOZ_CRASH("unexpected RegExpTree type");
+}
+
+static bool
+ParseRegExp(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;
+ }
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ RegExpFlag flags = RegExpFlag(0);
+ if (!args.get(1).isUndefined()) {
+ if (!args.get(1).isString()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a String");
+ return false;
+ }
+ RootedString flagStr(cx, args[1].toString());
+ if (!ParseRegExpFlags(cx, flagStr, &flags))
+ return false;
+ }
+
+ bool match_only = false;
+ if (!args.get(2).isUndefined()) {
+ if (!args.get(2).isBoolean()) {
+ ReportUsageErrorASCII(cx, callee, "Third argument, if present, must be a Boolean");
+ return false;
+ }
+ match_only = args[2].toBoolean();
+ }
+
+ RootedAtom pattern(cx, AtomizeString(cx, args[0].toString()));
+ if (!pattern)
+ return false;
+
+ CompileOptions options(cx);
+ frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
+
+ irregexp::RegExpCompileData data;
+ if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern,
+ flags & MultilineFlag, match_only,
+ flags & UnicodeFlag, flags & IgnoreCaseFlag,
+ flags & GlobalFlag, flags & StickyFlag,
+ &data))
+ {
+ return false;
+ }
+
+ RootedObject obj(cx, ConvertRegExpTreeToObject(cx, data.tree));
+ if (!obj)
+ return false;
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+DisRegExp(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;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<RegExpObject>()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a RegExp");
+ return false;
+ }
+
+ Rooted<RegExpObject*> reobj(cx, &args[0].toObject().as<RegExpObject>());
+
+ bool match_only = false;
+ if (!args.get(1).isUndefined()) {
+ if (!args.get(1).isBoolean()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be a Boolean");
+ return false;
+ }
+ match_only = args[1].toBoolean();
+ }
+
+ RootedLinearString input(cx, cx->runtime()->emptyString);
+ if (!args.get(2).isUndefined()) {
+ if (!args.get(2).isString()) {
+ ReportUsageErrorASCII(cx, callee, "Third argument, if present, must be a String");
+ return false;
+ }
+ RootedString inputStr(cx, args[2].toString());
+ input = inputStr->ensureLinear(cx);
+ if (!input)
+ return false;
+ }
+
+ if (!reobj->dumpBytecode(cx, match_only, input))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif // DEBUG
+
+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 const JSFunctionSpecWithHelp TestingFunctions[] = {
+ JS_FN_HELP("gc", ::GC, 0, 0,
+"gc([obj] | 'zone' [, 'shrinking'])",
+" Run the garbage collector. When obj is given, GC only its zone.\n"
+" If 'zone' is given, GC any zones that were scheduled for\n"
+" GC via schedulegc.\n"
+" If 'shrinking' is passed as the optional second argument, perform a\n"
+" shrinking GC rather than a normal 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("gcparam", GCParameter, 2, 0,
+"gcparam(name [, value])",
+" Wrapper for JS_[GS]etGCParameter. The name is one of:" GC_PARAMETER_ARGS_LIST),
+
+ 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("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("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("newExternalString", NewExternalString, 1, 0,
+"newExternalString(str)",
+" Copies str's chars and returns a new external string."),
+
+ JS_FN_HELP("ensureFlatString", EnsureFlatString, 1, 0,
+"ensureFlatString(str)",
+" Ensures str is a flat (null-terminated) string and returns it."),
+
+#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])",
+" 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."),
+#endif
+
+#ifdef SPIDERMONKEY_PROMISE
+ 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."),
+#else
+ JS_FN_HELP("makeFakePromise", MakeFakePromise, 0, 0,
+"makeFakePromise()",
+" Create an object whose [[Class]] name is 'Promise' and call\n"
+" JS::dbg::onNewPromise on it before returning it. It doesn't actually have\n"
+" any of the other behavior associated with promises."),
+
+ JS_FN_HELP("settleFakePromise", SettleFakePromise, 1, 0,
+"settleFakePromise(promise)",
+" 'Settle' a 'promise' created by makeFakePromise(). This doesn't have any\n"
+" observable effects outside of firing any onPromiseSettled hooks set on\n"
+" Debugger instances that are observing the given promise's global as a\n"
+" debuggee."),
+#endif // SPIDERMONKEY_PROMISE
+
+ 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(level, [N])",
+gc::ZealModeHelpText),
+
+ JS_FN_HELP("schedulegc", ScheduleGC, 1, 0,
+"schedulegc([num | obj | string])",
+" If num is given, schedule a GC after num allocations.\n"
+" 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.\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("gcstate", GCState, 0, 0,
+"gcstate()",
+" Report the global GC state."),
+
+ JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
+"deterministicgc(true|false)",
+" If true, only allow determinstic GCs to run."),
+#endif
+
+ 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."),
+
+ JS_FN_HELP("gcslice", GCSlice, 1, 0,
+"gcslice([n])",
+" Start or continue an an incremental GC, running a slice that processes about n objects."),
+
+ JS_FN_HELP("abortgc", AbortGC, 1, 0,
+"abortgc()",
+" Abort the current incremental GC."),
+
+ 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(['collectNurseryBeforeDump'], [filename])",
+" Dump reachable and unreachable objects to the named file, or to stdout. If\n"
+" 'collectNurseryBeforeDump' is specified, a minor GC is performed first,\n"
+" otherwise objects in the nursery are ignored."),
+
+ 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("readSPSProfilingStack", ReadSPSProfilingStack, 0, 0,
+"readSPSProfilingStack()",
+" Reads the jit stack using ProfilingFrameIterator."),
+
+ 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("isSimdAvailable", IsSimdAvailable, 0, 0,
+"isSimdAvailable",
+" Returns true if SIMD extensions are supported on this platform."),
+
+ JS_FN_HELP("getJitCompilerOptions", GetJitCompilerOptions, 0, 0,
+"getCompilerOptions()",
+"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("isAsmJSModuleLoadedFromCache", IsAsmJSModuleLoadedFromCache, 1, 0,
+"isAsmJSModuleLoadedFromCache(fn)",
+" Return whether the given asm.js module function has been loaded directly\n"
+" from the cache. This function throws an error if fn is not a validated asm.js\n"
+" module."),
+
+ 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("wasmIsSupported", WasmIsSupported, 0, 0,
+"wasmIsSupported()",
+" Returns a boolean indicating whether WebAssembly is supported on the current device."),
+
+ JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
+"wasmTextToBinary(str)",
+" Translates the given text wasm module into its binary encoding."),
+
+ JS_FN_HELP("wasmBinaryToText", WasmBinaryToText, 1, 0,
+"wasmBinaryToText(bin)",
+" Translates binary encoding to text format"),
+
+ JS_FN_HELP("wasmExtractCode", WasmExtractCode, 1, 0,
+"wasmExtractCode(module)",
+" Extracts generated machine code from WebAssembly.Module."),
+
+ 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)",
+" Ture if fun is a JSFunction with a relazifiable JSScript."),
+
+ 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("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("setJitCompilerOption", SetJitCompilerOption, 2, 0,
+"setCompilerOption(<option>, <number>)",
+" Set a compiler option indexed in JSCompileOption enum to a number.\n"),
+
+ 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", 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' (the default) or 'deny'\n"
+" to specify whether SharedArrayBuffers may be serialized.\n"
+"\n"
+" 'scope' - SameProcessSameThread, SameProcessDifferentThread, or\n"
+" DifferentProcess. Determines how some values will be serialized.\n"
+" Clone buffers may only be deserialized with a compatible scope."),
+
+ JS_FN_HELP("deserialize", Deserialize, 1, 0,
+"deserialize(clonebuffer[, opts])",
+" Deserialize data generated by serialize. 'opts' is an options hash with one\n"
+" recognized key 'scope', which limits the clone buffers that are considered\n"
+" valid. Allowed values: 'SameProcessSameThread', 'SameProcessDifferentThread',\n"
+" and 'DifferentProcess'. So for example, a DifferentProcess clone buffer\n"
+" may be deserialized in any scope, but a SameProcessSameThread clone buffer\n"
+" cannot be deserialized in a 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("helperThreadCount", HelperThreadCount, 0, 0,
+"helperThreadCount()",
+" Returns the number of helper threads available for off-main-thread tasks."),
+
+#ifdef JS_TRACE_LOGGING
+ JS_FN_HELP("startTraceLogger", EnableTraceLogger, 0, 0,
+"startTraceLogger()",
+" Start logging the mainThread.\n"
+" Note: tracelogging starts automatically. Disable it by setting environment variable\n"
+" TLOPTIONS=disableMainThread"),
+
+ JS_FN_HELP("stopTraceLogger", DisableTraceLogger, 0, 0,
+"stopTraceLogger()",
+" Stop logging the mainThread."),
+#endif
+
+ 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()",
+" 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"),
+
+ JS_FN_HELP("shortestPaths", ShortestPaths, 3, 0,
+"shortestPaths(start, targets, maxNumPaths)",
+" Return an array of arrays of shortest retaining paths. There is an array of\n"
+" shortest retaining paths for each object in |targets|. The maximum number of\n"
+" paths in each of those arrays is bounded by |maxNumPaths|. Each element in a\n"
+" path is of the form |{ predecessor, edge }|."),
+
+#ifdef DEBUG
+ 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"),
+
+#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("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("cloneAndExecuteScript", ShellCloneAndExecuteScript, 2, 0,
+"cloneAndExecuteScript(source, global)",
+" Compile |source| in the current compartment, clone it into |global|'s\n"
+" compartment, and run it there."),
+
+ 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"),
+#endif
+
+ JS_FN_HELP("setLazyParsingDisabled", SetLazyParsingDisabled, 1, 0,
+"setLazyParsingDisabled(bool)",
+" Explicitly disable lazy parsing in the current compartment. The default is that lazy "
+" parsing is not explicitly disabled."),
+
+ JS_FN_HELP("setDiscardSource", SetDiscardSource, 1, 0,
+"setDiscardSource(bool)",
+" Explicitly enable source discarding in the current compartment. The default is that "
+" source discarding is not explicitly enabled."),
+
+ JS_FN_HELP("getConstructorName", GetConstructorName, 1, 0,
+"getConstructorName(object)",
+" If the given object was created with `new Ctor`, return the constructor's display name. "
+" Otherwise, return null."),
+
+ 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"),
+
+ 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
+
+ JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0,
+"getModuleEnvironmentNames(module)",
+" Get the list of a module environment's bound names for a specified module.\n"),
+
+ JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
+"getModuleEnvironmentValue(module, name)",
+" Get the value of a bound name in a module environment.\n"),
+
+ JS_FN_HELP("isConstructor", IsConstructor, 1, 0,
+"isConstructor(value)",
+" Returns whether the value is considered IsConstructor.\n"),
+
+ JS_FS_HELP_END
+};
+
+static const JSFunctionSpecWithHelp FuzzingUnsafeTestingFunctions[] = {
+#ifdef DEBUG
+ JS_FN_HELP("parseRegExp", ParseRegExp, 3, 0,
+"parseRegExp(pattern[, flags[, match_only])",
+" Parses a RegExp pattern and returns a tree, potentially throwing."),
+
+ JS_FN_HELP("disRegExp", DisRegExp, 3, 0,
+"disRegExp(regexp[, match_only[, input]])",
+" Dumps RegExp bytecode."),
+#endif
+
+ JS_FS_HELP_END
+};
+
+static const JSPropertySpec TestingProperties[] = {
+ JS_PSG("timesAccessed", TimesAccessed, 0),
+ JS_PS_END
+};
+
+bool
+js::DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe_,
+ bool disableOOMFunctions_)
+{
+ fuzzingSafe = fuzzingSafe_;
+ if (EnvVarIsDefined("MOZ_FUZZING_SAFE"))
+ fuzzingSafe = true;
+
+ disableOOMFunctions = disableOOMFunctions_;
+
+ if (!JS_DefineProperties(cx, obj, TestingProperties))
+ return false;
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, obj, FuzzingUnsafeTestingFunctions))
+ return false;
+ }
+
+ return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
+}