Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "builtin/TestingFunctions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#ifdef JS_HAS_INTL_API
# include "mozilla/intl/ICU4CLibrary.h"
# include "mozilla/intl/Locale.h"
# include "mozilla/intl/String.h"
# include "mozilla/intl/TimeZone.h"
#endif
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Span.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StringBuffer.h"
#include "mozilla/TextUtils.h"
#include "mozilla/ThreadLocal.h"
#include <algorithm>
#include <cfloat>
#include <cinttypes>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <utility>
#if defined(XP_UNIX) && !defined(XP_DARWIN)
# include <time.h>
#else
# include <chrono>
#endif
#include "fdlibm.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#ifdef JS_HAS_INTL_API
# include "builtin/intl/CommonFunctions.h"
# include "builtin/intl/FormatBuffer.h"
# include "builtin/intl/SharedIntlData.h"
#endif
#include "builtin/BigInt.h"
#include "builtin/JSON.h"
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
#include "builtin/TestingUtility.h" // js::ParseCompileOptions, js::ParseDebugMetadata
#include "ds/IdValuePair.h" // js::IdValuePair
#include "frontend/CompilationStencil.h" // frontend::CompilationStencil
#include "frontend/FrontendContext.h" // AutoReportFrontendContext
#include "gc/GC.h"
#include "gc/GCEnum.h"
#include "gc/GCLock.h"
#include "gc/Zone.h"
#include "jit/BaselineJIT.h"
#include "jit/CacheIRSpewer.h"
#include "jit/Disassemble.h"
#include "jit/FlushICache.h"
#include "jit/InlinableNatives.h"
#include "jit/Invalidation.h"
#include "jit/Ion.h"
#include "jit/JitOptions.h"
#include "jit/JitRuntime.h"
#include "jit/JitScript.h"
#include "jit/TrialInlining.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/ArrayBuffer.h" // JS::{DetachArrayBuffer,GetArrayBufferLengthAndData,NewArrayBufferWithContents}
#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable, JS::IsConstructor, JS_CallFunction
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h" // JS::CompileOptions, JS::DecodeOptions, JS::InstantiateOptions
#include "js/Conversions.h"
#include "js/Date.h"
#include "js/experimental/CodeCoverage.h" // js::GetCodeCoverageSummary
#include "js/experimental/CompileScript.h" // JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::PrepareForInstantiate
#include "js/experimental/JSStencil.h" // JS::Stencil, JS::EncodeStencil, JS::DecodeStencil, JS::InstantiateGlobalStencil
#include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
#include "js/experimental/TypedData.h" // JS_GetObjectAsUint8Array
#include "js/friend/DumpFunctions.h" // js::Dump{Backtrace,Heap,Object}, JS::FormatStackDump, js::IgnoreNurseryObjects
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/WindowProxy.h" // js::ToWindowProxyIfWindow
#include "js/GlobalObject.h"
#include "js/HashTable.h"
#include "js/Interrupt.h"
#include "js/LocaleSensitive.h"
#include "js/Prefs.h"
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_DefineProperties, JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetElement, JS_SetProperty
#include "js/PropertySpec.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Stack.h"
#include "js/String.h" // JS::GetLinearStringLength, JS::StringToLinearString
#include "js/StructuredClone.h"
#include "js/Transcoding.h" // JS::TranscodeResult, JS::TranscodeRange, JS::TranscodeBuffer, JS::IsTranscodeFailureResult
#include "js/UbiNode.h"
#include "js/UbiNodeBreadthFirst.h"
#include "js/UbiNodeShortestPaths.h"
#include "js/UniquePtr.h"
#include "js/Vector.h"
#include "js/Wrapper.h"
#include "threading/CpuCount.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/BooleanObject.h"
#include "vm/DateObject.h"
#include "vm/DateTime.h"
#include "vm/ErrorObject.h"
#include "vm/GlobalObject.h"
#include "vm/HelperThreads.h"
#include "vm/HelperThreadState.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/NumberObject.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseSlot_*
#include "vm/ProxyObject.h"
#include "vm/RealmFuses.h"
#include "vm/SavedStacks.h"
#include "vm/ScopeKind.h"
#include "vm/Stack.h"
#include "vm/StencilObject.h" // StencilObject, StencilXDRBufferObject
#include "vm/StringObject.h"
#include "vm/StringType.h"
#include "wasm/AsmJS.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmBuiltinModule.h"
#include "wasm/WasmDump.h"
#include "wasm/WasmFeatures.h"
#include "wasm/WasmGcObject.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmValType.h"
#include "wasm/WasmValue.h"
#include "debugger/DebugAPI-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectFlags-inl.h"
#include "vm/StringType-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using mozilla::AssertedCast;
using mozilla::AsWritableChars;
using mozilla::Maybe;
using mozilla::Span;
using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::SliceBudget;
using JS::SourceText;
using JS::WorkBudget;
// If fuzzingSafe is set, remove functionality that could cause problems with
// fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
mozilla::Atomic<bool> js::fuzzingSafe(false);
// If disableOOMFunctions is set, disable functionality that causes artificial
// OOM conditions.
static mozilla::Atomic<bool> disableOOMFunctions(false);
static bool EnvVarIsDefined(const char* name) {
const char* value = getenv(name);
return value && *value;
}
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
static bool EnvVarAsInt(const char* name, int* valueOut) {
if (!EnvVarIsDefined(name)) {
return false;
}
*valueOut = atoi(getenv(name));
return true;
}
#endif
static bool GetRealmConfiguration(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return false;
}
if (args.length() > 1) {
ReportUsageErrorASCII(cx, callee, "Must have zero or one arguments");
return false;
}
if (args.length() == 1 && !args[0].isString()) {
ReportUsageErrorASCII(cx, callee, "Argument must be a string");
return false;
}
bool importAttributes = cx->options().importAttributes();
if (!JS_SetProperty(cx, info, "importAttributes",
importAttributes ? TrueHandleValue : FalseHandleValue)) {
return false;
}
if (args.length() == 1) {
RootedString str(cx, ToString(cx, args[0]));
if (!str) {
return false;
}
RootedId id(cx);
if (!JS_StringToId(cx, str, &id)) {
return false;
}
bool hasProperty;
if (JS_HasPropertyById(cx, info, id, &hasProperty) && hasProperty) {
// Returning a true/false from GetProperty
return GetProperty(cx, info, info, id, args.rval());
}
ReportUsageErrorASCII(cx, callee, "Invalid option name");
return false;
}
args.rval().setObject(*info);
return true;
}
static bool GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return false;
}
if (args.length() > 1) {
ReportUsageErrorASCII(cx, callee, "Must have zero or one arguments");
return false;
}
if (args.length() == 1 && !args[0].isString()) {
ReportUsageErrorASCII(cx, callee, "Argument must be a string");
return false;
}
if (!JS_SetProperty(cx, info, "rooting-analysis", FalseHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "exact-rooting", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "trace-jscalls-api", FalseHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "incremental-gc", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "generational-gc", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "oom-backtraces", FalseHandleValue)) {
return false;
}
RootedValue value(cx);
#ifdef DEBUG
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "debug", value)) {
return false;
}
#ifdef RELEASE_OR_BETA
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "release_or_beta", value)) {
return false;
}
#ifdef EARLY_BETA_OR_EARLIER
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "early_beta_or_earlier", value)) {
return false;
}
#ifdef MOZ_CODE_COVERAGE
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "coverage", value)) {
return false;
}
#ifdef JS_HAS_CTYPES
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "has-ctypes", value)) {
return false;
}
#if defined(_M_IX86) || defined(__i386__)
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "x86", value)) {
return false;
}
#if defined(_M_X64) || defined(__x86_64__)
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "x64", value)) {
return false;
}
#ifdef JS_CODEGEN_ARM
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm", value)) {
return false;
}
#ifdef JS_SIMULATOR_ARM
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm-simulator", value)) {
return false;
}
#ifdef ANDROID
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "android", value)) {
return false;
}
#ifdef XP_WIN
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "windows", value)) {
return false;
}
#ifdef XP_MACOSX
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "osx", value)) {
return false;
}
#ifdef JS_CODEGEN_ARM64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm64", value)) {
return false;
}
#ifdef JS_SIMULATOR_ARM64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm64-simulator", value)) {
return false;
}
#ifdef JS_CODEGEN_MIPS64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips64", value)) {
return false;
}
#ifdef JS_SIMULATOR_MIPS64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips64-simulator", value)) {
return false;
}
#ifdef JS_SIMULATOR
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "simulator", value)) {
return false;
}
#ifdef __wasi__
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "wasi", value)) {
return false;
}
#ifdef ENABLE_PORTABLE_BASELINE_INTERP
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "pbl", value)) {
return false;
}
#ifdef JS_CODEGEN_LOONG64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "loong64", value)) {
return false;
}
#ifdef JS_SIMULATOR_LOONG64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "loong64-simulator", value)) {
return false;
}
#ifdef JS_CODEGEN_RISCV64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "riscv64", value)) {
return false;
}
#ifdef JS_SIMULATOR_RISCV64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "riscv64-simulator", value)) {
return false;
}
#ifdef MOZ_ASAN
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "asan", value)) {
return false;
}
#ifdef MOZ_TSAN
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "tsan", value)) {
return false;
}
#ifdef MOZ_UBSAN
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "ubsan", value)) {
return false;
}
#ifdef JS_GC_ZEAL
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "has-gczeal", value)) {
return false;
}
#ifdef MOZ_PROFILING
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "profiling", value)) {
return false;
}
#ifdef INCLUDE_MOZILLA_DTRACE
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "dtrace", value)) {
return false;
}
#ifdef MOZ_VALGRIND
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "valgrind", value)) {
return false;
}
#ifdef JS_HAS_INTL_API
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "intl-api", value)) {
return false;
}
#if defined(SOLARIS)
value = BooleanValue(false);
#else
value = BooleanValue(true);
#endif
if (!JS_SetProperty(cx, info, "mapped-array-buffer", value)) {
return false;
}
#ifdef MOZ_MEMORY
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "moz-memory", value)) {
return false;
}
value.setInt32(sizeof(void*));
if (!JS_SetProperty(cx, info, "pointer-byte-size", value)) {
return false;
}
#ifdef ENABLE_DECORATORS
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "decorators", value)) {
return false;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "explicit-resource-management", value)) {
return false;
}
#ifdef FUZZING
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "fuzzing-defined", value)) {
return false;
}
#if (defined(__GNUC__) && defined(__SSE__) && defined(__x86_64__)) || \
defined(__arm__) || defined(__aarch64__)
// See js.cpp "disable-main-thread-denormals" command line option.
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "can-disable-main-thread-denormals", value)) {
return false;
}
value = Int32Value(JSFatInlineString::MAX_LENGTH_LATIN1);
if (!JS_SetProperty(cx, info, "inline-latin1-chars", value)) {
return false;
}
value = Int32Value(JSFatInlineString::MAX_LENGTH_TWO_BYTE);
if (!JS_SetProperty(cx, info, "inline-two-byte-chars", value)) {
return false;
}
value = Int32Value(JSThinInlineString::MAX_LENGTH_LATIN1);
if (!JS_SetProperty(cx, info, "thin-inline-latin1-chars", value)) {
return false;
}
value = Int32Value(JSThinInlineString::MAX_LENGTH_TWO_BYTE);
if (!JS_SetProperty(cx, info, "thin-inline-two-byte-chars", value)) {
return false;
}
if (js::ThinInlineAtom::EverInstantiated) {
value = Int32Value(js::ThinInlineAtom::MAX_LENGTH_LATIN1);
if (!JS_SetProperty(cx, info, "thin-inline-atom-latin1-chars", value)) {
return false;
}
value = Int32Value(js::ThinInlineAtom::MAX_LENGTH_TWO_BYTE);
if (!JS_SetProperty(cx, info, "thin-inline-atom-two-byte-chars", value)) {
return false;
}
}
value = Int32Value(js::FatInlineAtom::MAX_LENGTH_LATIN1);
if (!JS_SetProperty(cx, info, "fat-inline-atom-latin1-chars", value)) {
return false;
}
value = Int32Value(js::FatInlineAtom::MAX_LENGTH_TWO_BYTE);
if (!JS_SetProperty(cx, info, "fat-inline-atom-two-byte-chars", value)) {
return false;
}
if (args.length() == 1) {
RootedString str(cx, ToString(cx, args[0]));
if (!str) {
return false;
}
RootedId id(cx);
if (!JS_StringToId(cx, str, &id)) {
return false;
}
bool hasProperty;
if (JS_HasPropertyById(cx, info, id, &hasProperty) && hasProperty) {
// Returning a true/false from GetProperty
return GetProperty(cx, info, info, id, args.rval());
}
ReportUsageErrorASCII(cx, callee, "Invalid option name");
return false;
}
args.rval().setObject(*info);
return true;
}
static bool IsLCovEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(coverage::IsLCovEnabled());
return true;
}
static bool TrialInline(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
FrameIter iter(cx);
if (iter.done() || !iter.isBaseline() || iter.realm() != cx->realm()) {
return true;
}
jit::BaselineFrame* frame = iter.abstractFramePtr().asBaselineFrame();
if (!jit::CanIonCompileScript(cx, frame->script())) {
return true;
}
return jit::DoTrialInlining(cx, frame);
}
static bool ReturnStringCopy(JSContext* cx, CallArgs& args,
const char* message) {
JSString* str = JS_NewStringCopyZ(cx, message);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool MaybeGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JS_MaybeGC(cx);
args.rval().setUndefined();
return true;
}
static bool GC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
/*
* If the first argument is 'zone', we collect any zones previously
* scheduled for GC via schedulegc. If the first argument is an object, we
* collect the object's zone (and any other zones scheduled for
* GC). Otherwise, we collect all zones.
*/
bool zone = false;
if (args.length() >= 1) {
Value arg = args[0];
if (arg.isString()) {
if (!JS_StringEqualsLiteral(cx, arg.toString(), "zone", &zone)) {
return false;
}
} else if (arg.isObject()) {
PrepareZoneForGC(cx, UncheckedUnwrap(&arg.toObject())->zone());
zone = true;
}
}
JS::GCOptions options = JS::GCOptions::Normal;
JS::GCReason reason = JS::GCReason::API;
if (args.length() >= 2) {
Value arg = args[1];
if (arg.isString()) {
bool shrinking = false;
bool last_ditch = false;
if (!JS_StringEqualsLiteral(cx, arg.toString(), "shrinking",
&shrinking)) {
return false;
}
if (!JS_StringEqualsLiteral(cx, arg.toString(), "last-ditch",
&last_ditch)) {
return false;
}
if (shrinking) {
options = JS::GCOptions::Shrink;
} else if (last_ditch) {
options = JS::GCOptions::Shrink;
reason = JS::GCReason::LAST_DITCH;
}
}
}
size_t preBytes = cx->runtime()->gc.heapSize.bytes();
if (zone) {
PrepareForDebugGC(cx->runtime());
} else {
JS::PrepareForFullGC(cx);
}
JS::NonIncrementalGC(cx, options, reason);
char buf[256] = {'\0'};
if (!js::SupportDifferentialTesting()) {
SprintfLiteral(buf, "before %zu, after %zu\n", preBytes,
cx->runtime()->gc.heapSize.bytes());
}
return ReturnStringCopy(cx, args, buf);
}
static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.get(0) == BooleanValue(true)) {
gc::GCRuntime& gc = cx->runtime()->gc;
if (gc.nursery().isEnabled()) {
gc.storeBuffer().setAboutToOverflow(JS::GCReason::FULL_GENERIC_BUFFER);
}
}
cx->minorGC(JS::GCReason::API);
args.rval().setUndefined();
return true;
}
#define PARAM_NAME_LIST_ENTRY(name, key, writable) " " name
#define GC_PARAMETER_ARGS_LIST FOR_EACH_GC_PARAM(PARAM_NAME_LIST_ENTRY)
static bool GCParameter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSString* str = ToString(cx, args.get(0));
if (!str) {
return false;
}
UniqueChars name = EncodeLatin1(cx, str);
if (!name) {
return false;
}
JSGCParamKey param;
bool writable;
if (!GetGCParameterInfo(name.get(), ¶m, &writable)) {
JS_ReportErrorASCII(
cx, "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
return false;
}
// Request mode.
if (args.length() == 1) {
uint32_t value = JS_GetGCParameter(cx, param);
args.rval().setNumber(value);
return true;
}
if (!writable) {
JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s",
name.get());
return false;
}
if (fuzzingSafe) {
// Some Params are not yet fuzzing safe and so we silently skip
// changing said parameters.
switch (param) {
case JSGC_SEMISPACE_NURSERY_ENABLED:
args.rval().setUndefined();
return true;
default:
break;
}
}
if (disableOOMFunctions) {
switch (param) {
case JSGC_MAX_BYTES:
case JSGC_MAX_NURSERY_BYTES:
args.rval().setUndefined();
return true;
default:
break;
}
}
double d;
if (!ToNumber(cx, args[1], &d)) {
return false;
}
if (d < 0 || d > UINT32_MAX) {
JS_ReportErrorASCII(cx, "Parameter value out of range");
return false;
}
uint32_t value = floor(d);
bool ok = cx->runtime()->gc.setParameter(cx, param, value);
if (!ok) {
JS_ReportErrorASCII(cx, "Parameter value out of range");
return false;
}
args.rval().setUndefined();
return true;
}
static bool FinishBackgroundFree(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
cx->runtime()->gc.waitBackgroundFreeEnd();
args.rval().setUndefined();
return true;
}
static bool RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp) {
// Relazifying functions on GC is usually only done for compartments that are
// not active. To aid fuzzing, this testing function allows us to relazify
// even if the compartment is active.
CallArgs args = CallArgsFromVp(argc, vp);
// Disable relazification of all scripts on stack. It is a pervasive
// assumption in the engine that running scripts still have bytecode.
for (AllScriptFramesIter i(cx); !i.done(); ++i) {
i.script()->clearAllowRelazify();
}
cx->runtime()->allowRelazificationForTesting = true;
JS::PrepareForFullGC(cx);
JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
cx->runtime()->allowRelazificationForTesting = false;
args.rval().setUndefined();
return true;
}
static bool IsProxy(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "the function takes exactly one argument");
return false;
}
if (!args[0].isObject()) {
args.rval().setBoolean(false);
return true;
}
args.rval().setBoolean(args[0].toObject().is<ProxyObject>());
return true;
}
static bool WasmIsSupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasSupport(cx) &&
wasm::AnyCompilerAvailable(cx));
return true;
}
static bool WasmIsSupportedByHardware(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasPlatformSupport());
return true;
}
static bool WasmDebuggingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasSupport(cx) && wasm::BaselineAvailable(cx));
return true;
}
static bool WasmStreamingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::StreamingCompilationAvailable(cx));
return true;
}
static bool WasmCachingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::CodeCachingAvailable(cx));
return true;
}
static bool WasmHugeMemorySupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef WASM_SUPPORTS_HUGE_MEMORY
args.rval().setBoolean(true);
#else
args.rval().setBoolean(false);
#endif
return true;
}
static bool WasmMaxMemoryPages(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isString()) {
JS_ReportErrorASCII(cx, "address type must be a string");
return false;
}
RootedString s(cx, args.get(0).toString());
Rooted<JSLinearString*> ls(cx, s->ensureLinear(cx));
if (!ls) {
return false;
}
if (StringEqualsLiteral(ls, "i32")) {
args.rval().setInt32(
int32_t(wasm::MaxMemoryPages(wasm::AddressType::I32).value()));
return true;
}
if (StringEqualsLiteral(ls, "i64")) {
#ifdef ENABLE_WASM_MEMORY64
if (wasm::Memory64Available(cx)) {
args.rval().setInt32(
int32_t(wasm::MaxMemoryPages(wasm::AddressType::I64).value()));
return true;
}
#endif
JS_ReportErrorASCII(cx, "memory64 not enabled");
return false;
}
JS_ReportErrorASCII(cx, "bad address type");
return false;
}
static bool WasmThreadsEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::ThreadsAvailable(cx));
return true;
}
#define WASM_FEATURE(NAME, ...) \
static bool Wasm##NAME##Enabled(JSContext* cx, unsigned argc, Value* vp) { \
CallArgs args = CallArgsFromVp(argc, vp); \
args.rval().setBoolean(wasm::NAME##Available(cx)); \
return true; \
}
JS_FOR_WASM_FEATURES(WASM_FEATURE);
#undef WASM_FEATURE
static bool WasmSimdEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::SimdAvailable(cx));
return true;
}
static bool WasmCompilersPresent(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
char buf[256];
*buf = 0;
if (wasm::BaselinePlatformSupport()) {
strcat(buf, "baseline");
}
if (wasm::IonPlatformSupport()) {
if (*buf) {
strcat(buf, ",");
}
strcat(buf, "ion");
}
JSString* result = JS_NewStringCopyZ(cx, buf);
if (!result) {
return false;
}
args.rval().setString(result);
return true;
}
static bool WasmCompileMode(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// This triplet of predicates will select zero or one baseline compiler and
// zero or one optimizing compiler.
bool baseline = wasm::BaselineAvailable(cx);
bool ion = wasm::IonAvailable(cx);
bool none = !baseline && !ion;
bool tiered = baseline && ion;
JSStringBuilder result(cx);
if (none && !result.append("none")) {
return false;
}
if (baseline && !result.append("baseline")) {
return false;
}
if (tiered && !result.append("+")) {
return false;
}
if (ion && !result.append("ion")) {
return false;
}
if (JSString* str = result.finishString()) {
args.rval().setString(str);
return true;
}
return false;
}
static bool WasmLazyTieringEnabled(JSContext* cx, unsigned argc, Value* vp) {
// Note: ensure this function stays in sync with `PlatformCanTier()`.
CallArgs args = CallArgsFromVp(argc, vp);
bool baseline = wasm::BaselineAvailable(cx);
bool ion = wasm::IonAvailable(cx);
bool enabled =
baseline && ion && JS::Prefs::wasm_lazy_tiering() &&
(JS::Prefs::wasm_lazy_tiering_synchronous() ||
(CanUseExtraThreads() && jit::CanFlushExecutionContextForAllThreads()));
args.rval().setBoolean(enabled);
return true;
}
static bool WasmBaselineDisabledByFeatures(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool isDisabled = false;
JSStringBuilder reason(cx);
if (!wasm::BaselineDisabledByFeatures(cx, &isDisabled, &reason)) {
return false;
}
if (isDisabled) {
JSString* result = reason.finishString();
if (!result) {
return false;
}
args.rval().setString(result);
} else {
args.rval().setBoolean(false);
}
return true;
}
static bool WasmIonDisabledByFeatures(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool isDisabled = false;
JSStringBuilder reason(cx);
if (!wasm::IonDisabledByFeatures(cx, &isDisabled, &reason)) {
return false;
}
if (isDisabled) {
JSString* result = reason.finishString();
if (!result) {
return false;
}
args.rval().setString(result);
} else {
args.rval().setBoolean(false);
}
return true;
}
#ifdef ENABLE_WASM_SIMD
# ifdef DEBUG
static char lastAnalysisResult[1024];
namespace js {
namespace wasm {
void ReportSimdAnalysis(const char* data) {
strncpy(lastAnalysisResult, data, sizeof(lastAnalysisResult));
lastAnalysisResult[sizeof(lastAnalysisResult) - 1] = 0;
}
} // namespace wasm
} // namespace js
// Unstable API for white-box testing of SIMD optimizations.
//
// Current API: takes no arguments, returns a string describing the last Simd
// simplification applied.
static bool WasmSimdAnalysis(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSString* result =
JS_NewStringCopyZ(cx, *lastAnalysisResult ? lastAnalysisResult : "none");
if (!result) {
return false;
}
args.rval().setString(result);
*lastAnalysisResult = (char)0;
return true;
}
# endif
#endif
static bool WasmGlobalFromArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
// Get the type of the value
wasm::ValType valType;
if (!wasm::ToValType(cx, args.get(0), &valType)) {
return false;
}
// Get the array buffer for the value
if (!args.get(1).isObject() ||
!args.get(1).toObject().is<ArrayBufferObject>()) {
JS_ReportErrorASCII(cx, "argument is not an array buffer");
return false;
}
Rooted<ArrayBufferObject*> buffer(
cx, &args.get(1).toObject().as<ArrayBufferObject>());
// Only allow POD to be created from bytes
switch (valType.kind()) {
case wasm::ValType::I32:
case wasm::ValType::I64:
case wasm::ValType::F32:
case wasm::ValType::F64:
case wasm::ValType::V128:
break;
default:
JS_ReportErrorASCII(
cx, "invalid valtype for creating WebAssembly.Global from bytes");
return false;
}
// Check we have all the bytes we need
if (valType.size() != buffer->byteLength()) {
JS_ReportErrorASCII(cx, "array buffer has incorrect size");
return false;
}
// Copy the bytes from buffer into a tagged val
wasm::RootedVal val(cx);
val.get().initFromRootedLocation(valType, buffer->dataPointer());
// Create the global object
RootedObject proto(
cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
if (!proto) {
return false;
}
Rooted<WasmGlobalObject*> result(
cx, WasmGlobalObject::create(cx, val, false, proto));
if (!result) {
return false;
}
args.rval().setObject(*result.get());
return true;
}
enum class LaneInterp {
I32x4,
I64x2,
F32x4,
F64x2,
};
size_t LaneInterpLanes(LaneInterp interp) {
switch (interp) {
case LaneInterp::I32x4:
return 4;
case LaneInterp::I64x2:
return 2;
case LaneInterp::F32x4:
return 4;
case LaneInterp::F64x2:
return 2;
default:
MOZ_ASSERT_UNREACHABLE();
return 0;
}
}
static bool ToLaneInterp(JSContext* cx, HandleValue v, LaneInterp* out) {
RootedString interpStr(cx, ToString(cx, v));
if (!interpStr) {
return false;
}
Rooted<JSLinearString*> interpLinearStr(cx, interpStr->ensureLinear(cx));
if (!interpLinearStr) {
return false;
}
if (StringEqualsLiteral(interpLinearStr, "i32x4")) {
*out = LaneInterp::I32x4;
return true;
} else if (StringEqualsLiteral(interpLinearStr, "i64x2")) {
*out = LaneInterp::I64x2;
return true;
} else if (StringEqualsLiteral(interpLinearStr, "f32x4")) {
*out = LaneInterp::F32x4;
return true;
} else if (StringEqualsLiteral(interpLinearStr, "f64x2")) {
*out = LaneInterp::F64x2;
return true;
}
JS_ReportErrorASCII(cx, "invalid lane interpretation");
return false;
}
static bool WasmGlobalExtractLane(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 3) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
// Get the global value
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx, "argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> global(
cx, &args.get(0).toObject().as<WasmGlobalObject>());
// Check that we have a v128 value
if (global->type().kind() != wasm::ValType::V128) {
JS_ReportErrorASCII(cx, "global is not a v128 value");
return false;
}
wasm::V128 v128 = global->val().get().v128();
// Get the passed interpretation of lanes
LaneInterp interp;
if (!ToLaneInterp(cx, args.get(1), &interp)) {
return false;
}
// Get the lane to extract
int32_t lane;
if (!ToInt32(cx, args.get(2), &lane)) {
return false;
}
// Check that the lane interp is valid
if (lane < 0 || size_t(lane) >= LaneInterpLanes(interp)) {
JS_ReportErrorASCII(cx, "invalid lane for interp");
return false;
}
wasm::RootedVal val(cx);
switch (interp) {
case LaneInterp::I32x4: {
uint32_t i;
v128.extractLane<uint32_t>(lane, &i);
val.set(wasm::Val(i));
break;
}
case LaneInterp::I64x2: {
uint64_t i;
v128.extractLane<uint64_t>(lane, &i);
val.set(wasm::Val(i));
break;
}
case LaneInterp::F32x4: {
float f;
v128.extractLane<float>(lane, &f);
val.set(wasm::Val(f));
break;
}
case LaneInterp::F64x2: {
double d;
v128.extractLane<double>(lane, &d);
val.set(wasm::Val(d));
break;
}
default:
MOZ_ASSERT_UNREACHABLE();
}
RootedObject proto(
cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
Rooted<WasmGlobalObject*> result(
cx, WasmGlobalObject::create(cx, val, false, proto));
args.rval().setObject(*result.get());
return true;
}
static bool WasmGlobalsEqual(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>() ||
!args.get(1).isObject() ||
!args.get(1).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx, "argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> a(cx,
&args.get(0).toObject().as<WasmGlobalObject>());
Rooted<WasmGlobalObject*> b(cx,
&args.get(1).toObject().as<WasmGlobalObject>());
if (a->type().kind() != b->type().kind()) {
JS_ReportErrorASCII(cx, "globals are of different kind");
return false;
}
bool result;
const wasm::Val& aVal = a->val().get();
const wasm::Val& bVal = b->val().get();
switch (a->type().kind()) {
case wasm::ValType::I32: {
result = aVal.i32() == bVal.i32();
break;
}
case wasm::ValType::I64: {
result = aVal.i64() == bVal.i64();
break;
}
case wasm::ValType::F32: {
result = mozilla::BitwiseCast<uint32_t>(aVal.f32()) ==
mozilla::BitwiseCast<uint32_t>(bVal.f32());
break;
}
case wasm::ValType::F64: {
result = mozilla::BitwiseCast<uint64_t>(aVal.f64()) ==
mozilla::BitwiseCast<uint64_t>(bVal.f64());
break;
}
case wasm::ValType::V128: {
// Don't know the interpretation of the v128, so we only can do an exact
// bitwise equality. Testing code can use wasmGlobalExtractLane to
// workaround this if needed.
result = aVal.v128() == bVal.v128();
break;
}
case wasm::ValType::Ref: {
result = aVal.ref() == bVal.ref();
break;
}
default:
JS_ReportErrorASCII(cx, "unsupported type");
return false;
}
args.rval().setBoolean(result);
return true;
}
// Flavors of NaN values for WebAssembly.
// See
enum class NaNFlavor {
// A canonical NaN value.
// - the sign bit is unspecified,
// - the 8-bit exponent is set to all 1s
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
Canonical,
// An arithmetic NaN. This is the same as a canonical NaN including that the
// payload MSB is set to 1, but one or more of the remaining payload bits MAY
// BE set to 1 (a canonical NaN specifies all 0s).
Arithmetic,
};
static bool IsNaNFlavor(uint32_t bits, NaNFlavor flavor) {
switch (flavor) {
case NaNFlavor::Canonical: {
return (bits & 0x7fffffff) == 0x7fc00000;
}
case NaNFlavor::Arithmetic: {
const uint32_t ArithmeticNaN = 0x7f800000;
const uint32_t ArithmeticPayloadMSB = 0x00400000;
bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
bool isMSBSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
return isNaN && isMSBSet;
}
default:
MOZ_CRASH();
}
}
static bool IsNaNFlavor(uint64_t bits, NaNFlavor flavor) {
switch (flavor) {
case NaNFlavor::Canonical: {
return (bits & 0x7fffffffffffffff) == 0x7ff8000000000000;
}
case NaNFlavor::Arithmetic: {
uint64_t ArithmeticNaN = 0x7ff0000000000000;
uint64_t ArithmeticPayloadMSB = 0x0008000000000000;
bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
bool isMsbSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
return isNaN && isMsbSet;
}
default:
MOZ_CRASH();
}
}
static bool ToNaNFlavor(JSContext* cx, HandleValue v, NaNFlavor* out) {
RootedString flavorStr(cx, ToString(cx, v));
if (!flavorStr) {
return false;
}
Rooted<JSLinearString*> flavorLinearStr(cx, flavorStr->ensureLinear(cx));
if (!flavorLinearStr) {
return false;
}
if (StringEqualsLiteral(flavorLinearStr, "canonical_nan")) {
*out = NaNFlavor::Canonical;
return true;
} else if (StringEqualsLiteral(flavorLinearStr, "arithmetic_nan")) {
*out = NaNFlavor::Arithmetic;
return true;
}
JS_ReportErrorASCII(cx, "invalid nan flavor");
return false;
}
static bool WasmGlobalIsNaN(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx, "argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> global(
cx, &args.get(0).toObject().as<WasmGlobalObject>());
NaNFlavor flavor;
if (!ToNaNFlavor(cx, args.get(1), &flavor)) {
return false;
}
bool result;
const wasm::Val& val = global->val().get();
switch (global->type().kind()) {
case wasm::ValType::F32: {
result = IsNaNFlavor(mozilla::BitwiseCast<uint32_t>(val.f32()), flavor);
break;
}
case wasm::ValType::F64: {
result = IsNaNFlavor(mozilla::BitwiseCast<uint64_t>(val.f64()), flavor);
break;
}
default:
JS_ReportErrorASCII(cx, "global is not a floating point value");
return false;
}
args.rval().setBoolean(result);
return true;
}
static bool WasmGlobalToString(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx, "argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> global(
cx, &args.get(0).toObject().as<WasmGlobalObject>());
const wasm::Val& globalVal = global->val().get();
UniqueChars result;
switch (globalVal.type().kind()) {
case wasm::ValType::I32: {
result = JS_smprintf("i32:%" PRIx32, globalVal.i32());
break;
}
case wasm::ValType::I64: {
result = JS_smprintf("i64:%" PRIx64, globalVal.i64());
break;
}
case wasm::ValType::F32: {
result = JS_smprintf("f32:%f", globalVal.f32());
break;
}
case wasm::ValType::F64: {
result = JS_smprintf("f64:%lf", globalVal.f64());
break;
}
case wasm::ValType::V128: {
wasm::V128 v128 = globalVal.v128();
result = JS_smprintf(
"v128:%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", v128.bytes[0],
v128.bytes[1], v128.bytes[2], v128.bytes[3], v128.bytes[4],
v128.bytes[5], v128.bytes[6], v128.bytes[7], v128.bytes[8],
v128.bytes[9], v128.bytes[10], v128.bytes[11], v128.bytes[12],
v128.bytes[13], v128.bytes[14], v128.bytes[15]);
break;
}
case wasm::ValType::Ref: {
result = JS_smprintf("ref:%" PRIxPTR, globalVal.ref().rawValue());
break;
}
default:
MOZ_ASSERT_UNREACHABLE();
}
args.rval().setString(JS_NewStringCopyZ(cx, result.get()));
return true;
}
static bool WasmLosslessInvoke(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isObject() || !args.get(0).toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
RootedFunction func(cx, &args[0].toObject().as<JSFunction>());
if (!func || !func->isWasm()) {
JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
return false;
}
// Switch to the function's realm
AutoRealm ar(cx, func);
// Get the instance and funcIndex for calling the function
wasm::Instance& instance = func->wasmInstance();
uint32_t funcIndex = func->wasmFuncIndex();
// Set up a modified call frame following the standard JS
// [callee, this, arguments...] convention.
RootedValueVector wasmCallFrame(cx);
size_t len = 2 + args.length();
if (!wasmCallFrame.resize(len)) {
return false;
}
wasmCallFrame[0].set(ObjectValue(*func));
wasmCallFrame[1].set(args.thisv());
// Copy over the arguments needed to invoke the provided wasm function,
// skipping the wasm function we're calling that is at `args.get(0)`.
for (size_t i = 1; i < args.length(); i++) {
size_t wasmArg = i - 1;
wasmCallFrame[2 + wasmArg].set(args.get(i));
}
size_t wasmArgc = argc - 1;
CallArgs wasmCallArgs(CallArgsFromVp(wasmArgc, wasmCallFrame.begin()));
// Invoke the function with the new call frame
bool result = instance.callExport(cx, funcIndex, wasmCallArgs,
wasm::CoercionLevel::Lossless);
// Assign the wasm rval to our rval
args.rval().set(wasmCallArgs.rval());
return result;
}
static bool ConvertToTier(JSContext* cx, HandleValue value,
const wasm::Code& code, wasm::Tier* tier) {
RootedString option(cx, JS::ToString(cx, value));
if (!option) {
return false;
}
bool stableTier = false;
bool bestTier = false;
bool baselineTier = false;
bool ionTier = false;
if (!JS_StringEqualsLiteral(cx, option, "stable", &stableTier) ||
!JS_StringEqualsLiteral(cx, option, "best", &bestTier) ||
!JS_StringEqualsLiteral(cx, option, "baseline", &baselineTier) ||
!JS_StringEqualsLiteral(cx, option, "ion", &ionTier)) {
return false;
}
if (stableTier) {
*tier = code.stableCompleteTier();
} else if (bestTier) {
*tier = code.bestCompleteTier();
} else if (baselineTier) {
*tier = wasm::Tier::Baseline;
} else if (ionTier) {
*tier = wasm::Tier::Optimized;
} else {
// You can omit the argument but you can't pass just anything you like
return false;
}
return true;
}
static bool WasmExtractCode(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
Rooted<WasmModuleObject*> module(
cx, args[0].toObject().maybeUnwrapIf<WasmModuleObject>());
if (!module) {
JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
return false;
}
wasm::Tier tier = module->module().code().stableCompleteTier();
if (args.length() > 1 &&
!ConvertToTier(cx, args[1], module->module().code(), &tier)) {
args.rval().setNull();
return false;
}
RootedValue result(cx);
if (!module->module().extractCode(cx, tier, &result)) {
return false;
}
args.rval().set(result);
return true;
}
static bool HasDisassembler(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(jit::HasDisassembler());
return true;
}
MOZ_THREAD_LOCAL(JSSprinter*) disasmPrinter;
static void captureDisasmText(const char* text) {
JSSprinter* printer = disasmPrinter.get();
printer->put(text);
printer->printf("\n");
}
static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_MORE_ARGS_NEEDED, "disnative", "1", "",
"0");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument must be a function.");
return false;
}
JSSprinter sprinter(cx);
if (!sprinter.init()) {
return false;
}
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
uint8_t* jit_begin = nullptr;
uint8_t* jit_end = nullptr;
if (fun->isAsmJSNative() || fun->isWasmWithJitEntry()) {
if (IsAsmJSModule(fun)) {
JS_ReportErrorASCII(cx, "Can't disassemble asm.js module function.");
return false;
}
if (fun->isAsmJSNative()) {
sprinter.printf("; backend=asmjs\n");
}
sprinter.printf("; backend=wasm\n");
js::wasm::Instance& inst = fun->wasmInstance();
const uint32_t funcIndex = fun->wasmFuncIndex();
const js::wasm::CodeBlock& codeBlock = inst.code().funcCodeBlock(funcIndex);
const js::wasm::FuncExport& func = codeBlock.lookupFuncExport(funcIndex);
const js::wasm::CodeRange& codeRange = codeBlock.codeRange(func);
jit_begin = codeBlock.base() + codeRange.begin();
jit_end = codeBlock.base() + codeRange.end();
} else if (fun->hasJitScript() && fun->nonLazyScript()->hasIonScript()) {
sprinter.printf("; backend=ion\n");
jit_begin = fun->nonLazyScript()->ionScript()->method()->raw();
jit_end = fun->nonLazyScript()->ionScript()->method()->rawEnd();
} else if (fun->hasJitScript() && fun->nonLazyScript()->hasBaselineScript()) {
sprinter.printf("; backend=baseline\n");
jit_begin = fun->nonLazyScript()->baselineScript()->method()->raw();
jit_end = fun->nonLazyScript()->baselineScript()->method()->rawEnd();
} else {
JS_ReportErrorASCII(cx,
"The function hasn't been warmed up, hence no JIT code "
"to disassemble.");
return false;
}
MOZ_ASSERT(jit_begin);
MOZ_ASSERT(jit_end);
#ifdef JS_CODEGEN_ARM
// The ARM32 disassembler is currently not fuzzing-safe because it doesn't
if (fuzzingSafe) {
JS_ReportErrorASCII(cx, "disnative is not fuzzing-safe on ARM32");
return false;
}
#endif
// Dump the raw code to a file before disassembling in case
// finishString triggers a GC and discards the jitcode.
if (!fuzzingSafe && args.length() > 1 && args[1].isString()) {
RootedString str(cx, args[1].toString());
JS::UniqueChars fileNameBytes = JS_EncodeStringToUTF8(cx, str);
const char* fileName = fileNameBytes.get();
if (!fileName) {
ReportOutOfMemory(cx);
return false;
}
FILE* f = fopen(fileName, "w");
if (!f) {
JS_ReportErrorASCII(cx, "Could not open file for writing.");
return false;
}
uintptr_t expected_length = reinterpret_cast<uintptr_t>(jit_end) -
reinterpret_cast<uintptr_t>(jit_begin);
if (expected_length != fwrite(jit_begin, jit_end - jit_begin, 1, f)) {
JS_ReportErrorASCII(cx, "Did not write all function bytes to the file.");
fclose(f);
return false;
}
fclose(f);
}
disasmPrinter.set(&sprinter);
auto onFinish = mozilla::MakeScopeExit([&] { disasmPrinter.set(nullptr); });
jit::Disassemble(jit_begin, jit_end - jit_begin, &captureDisasmText);
JSString* str = sprinter.release(cx);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool DisassembleBaselineICs(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
if (!args.requireAtLeast(cx, "disblic", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument must be a function.");
return false;
}
JSSprinter sprinter(cx);
if (!sprinter.init()) {
return false;
}
disasmPrinter.set(&sprinter);
auto onFinish = mozilla::MakeScopeExit([&] { disasmPrinter.set(nullptr); });
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
if (!fun->hasJitScript()) {
args.rval().setUndefined();
return true;
}
#ifdef JS_CODEGEN_ARM
// The ARM32 disassembler is currently not fuzzing-safe because it doesn't
if (fuzzingSafe) {
JS_ReportErrorASCII(cx, "disblic is not fuzzing-safe on ARM32");
return false;
}
#endif
RootedScript script(cx, fun->nonLazyScript());
jit::ICScript* icScript = script->jitScript()->icScript();
for (uint32_t i = 0; i < icScript->numICEntries(); i++) {
jit::ICEntry& entry = icScript->icEntry(i);
jit::ICStub* stub = entry.firstStub();
jit::ICStub* fallbackStub = stub;
while (!fallbackStub->isFallback()) {
fallbackStub = fallbackStub->toCacheIRStub()->next();
}
uint32_t pcOffset = fallbackStub->toFallbackStub()->pcOffset();
sprinter.printf("; %s (pcOffset %05u)\n",
CodeName(JSOp(*script->offsetToPC(pcOffset))), pcOffset);
uint32_t stubNum = 1;
while (!stub->isFallback()) {
sprinter.printf("; Stub #%d (entry count: %d)\n", stubNum,
stub->enteredCount());
jit::ICCacheIRStub* cacheIRStub = stub->toCacheIRStub();
uint8_t* jit_begin = stub->jitCode()->raw();
uint8_t* jit_end = stub->jitCode()->rawEnd();
#ifdef JS_CACHEIR_SPEW
sprinter.printf("; IR:\n");
SpewCacheIROps(sprinter, "; ", cacheIRStub->stubInfo());
#endif
jit::Disassemble(jit_begin, jit_end - jit_begin, &captureDisasmText);
stub = cacheIRStub->next();
stubNum++;
}
}
JSString* str = sprinter.release(cx);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool ComputeTier(JSContext* cx, const wasm::Code& code,
HandleValue tierSelection, wasm::Tier* tier) {
*tier = code.stableCompleteTier();
if (!tierSelection.isUndefined() &&
!ConvertToTier(cx, tierSelection, code, tier)) {
JS_ReportErrorASCII(cx, "invalid tier");
return false;
}
return true;
}
template <typename DisasmFunction>
static bool DisassembleIt(JSContext* cx, bool asString, MutableHandleValue rval,
DisasmFunction&& disassembleIt) {
if (asString) {
JSSprinter sprinter(cx);
if (!sprinter.init()) {
return false;
}
disasmPrinter.set(&sprinter);
auto onFinish = mozilla::MakeScopeExit([&] { disasmPrinter.set(nullptr); });
disassembleIt(captureDisasmText);
JSString* sresult = sprinter.release(cx);
if (!sresult) {
return false;
}
rval.setString(sresult);
return true;
}
disassembleIt([](const char* text) { fprintf(stderr, "%s\n", text); });
return true;
}
static bool WasmDisassembleFunction(JSContext* cx, const HandleFunction& func,
HandleValue tierSelection, bool asString,
MutableHandleValue rval) {
wasm::Instance& instance = func->wasmInstance();
uint32_t funcIndex = func->wasmFuncIndex();
wasm::Tier tier;
if (!ComputeTier(cx, instance.code(), tierSelection, &tier)) {
return false;
}
if (!instance.code().funcHasTier(funcIndex, tier)) {
JS_ReportErrorASCII(cx, "function missing selected tier");
return false;
}
return DisassembleIt(
cx, asString, rval, [&](void (*captureText)(const char*)) {
instance.disassembleExport(cx, funcIndex, tier, captureText);
});
}
static bool WasmDisassembleCode(JSContext* cx, const wasm::Code& code,
HandleValue tierSelection, int kindSelection,
bool asString, MutableHandleValue rval) {
wasm::Tier tier;
if (!ComputeTier(cx, code, tierSelection, &tier)) {
return false;
}
return DisassembleIt(cx, asString, rval,
[&](void (*captureText)(const char*)) {
code.disassemble(cx, tier, kindSelection, captureText);
});
}
static bool WasmDisassemble(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(UndefinedValue());
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
bool asString = false;
RootedValue tierSelection(cx);
int kindSelection = (1 << wasm::CodeRange::Function);
if (args.length() > 1 && args[1].isObject()) {
RootedObject options(cx, &args[1].toObject());
RootedValue val(cx);
if (!JS_GetProperty(cx, options, "asString", &val)) {
return false;
}
asString = val.isBoolean() && val.toBoolean();
if (!JS_GetProperty(cx, options, "tier", &tierSelection)) {
return false;
}
if (!JS_GetProperty(cx, options, "kinds", &val)) {
return false;
}
if (val.isString() && val.toString()->hasLatin1Chars()) {
AutoStableStringChars stable(cx);
if (!stable.init(cx, val.toString())) {
return false;
}
const char* p = (const char*)(stable.latin1Chars());
const char* end = p + val.toString()->length();
int selection = 0;
for (;;) {
if (strncmp(p, "Function", 8) == 0) {
selection |= (1 << wasm::CodeRange::Function);
p += 8;
} else if (strncmp(p, "InterpEntry", 11) == 0) {
selection |= (1 << wasm::CodeRange::InterpEntry);
p += 11;
} else if (strncmp(p, "JitEntry", 8) == 0) {
selection |= (1 << wasm::CodeRange::JitEntry);
p += 8;
} else if (strncmp(p, "ImportInterpExit", 16) == 0) {
selection |= (1 << wasm::CodeRange::ImportInterpExit);
p += 16;
} else if (strncmp(p, "ImportJitExit", 13) == 0) {
selection |= (1 << wasm::CodeRange::ImportJitExit);
p += 13;
} else if (strncmp(p, "all", 3) == 0) {
selection = ~0;
p += 3;
} else {
break;
}
if (p == end || *p != ',') {
break;
}
p++;
}
if (p == end) {
kindSelection = selection;
} else {
JS_ReportErrorASCII(cx, "argument object has invalid `kinds`");
return false;
}
}
}
RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
if (func && func->isWasm()) {
return WasmDisassembleFunction(cx, func, tierSelection, asString,
args.rval());
}
if (args[0].toObject().is<WasmModuleObject>()) {
return WasmDisassembleCode(
cx, args[0].toObject().as<WasmModuleObject>().module().code(),
tierSelection, kindSelection, asString, args.rval());
}
if (args[0].toObject().is<WasmInstanceObject>()) {
return WasmDisassembleCode(
cx, args[0].toObject().as<WasmInstanceObject>().instance().code(),
tierSelection, kindSelection, asString, args.rval());
}
JS_ReportErrorASCII(
cx, "argument is not an exported wasm function or a wasm module");
return false;
}
static bool WasmModuleToText(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
if (!args[0].toObject().is<WasmModuleObject>()) {
JS_ReportErrorASCII(cx, "argument is not a wasm module");
return false;
}
const wasm::Module& mod = args[0].toObject().as<WasmModuleObject>().module();
JSSprinter out(cx);
if (!out.init()) {
ReportOutOfMemory(cx);
return false;
}
wasm::DumpModule(mod, out);
JSString* str = out.release(cx);
if (!str) {
ReportOutOfMemory(cx);
return false;
}
args.rval().set(StringValue(str));
return true;
}
static bool WasmFunctionTier(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(UndefinedValue());
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
if (func && func->isWasm()) {
uint32_t funcIndex = func->wasmFuncIndex();
wasm::Instance& instance = func->wasmInstance();
if (funcIndex < instance.code().funcImports().length()) {
JS_ReportErrorASCII(cx, "argument is an imported function");
return false;
}
wasm::Tier tier = instance.code().funcTier(funcIndex);
RootedString tierString(cx, JS_NewStringCopyZ(cx, wasm::ToString(tier)));
if (!tierString) {
ReportOutOfMemory(cx);
return false;
}
args.rval().set(StringValue(tierString));
return true;
}
JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
return false;
}
static bool ToIonDumpContents(JSContext* cx, HandleValue value,
wasm::IonDumpContents* contents) {
RootedString option(cx, JS::ToString(cx, value));
if (!option) {
return false;
}
bool isEqual = false;
if (!JS_StringEqualsLiteral(cx, option, "mir", &isEqual) || isEqual) {
*contents = wasm::IonDumpContents::UnoptimizedMIR;
return isEqual;
} else if (!JS_StringEqualsLiteral(cx, option, "unopt-mir", &isEqual) ||
isEqual) {
*contents = wasm::IonDumpContents::UnoptimizedMIR;
return isEqual;
} else if (!JS_StringEqualsLiteral(cx, option, "opt-mir", &isEqual) ||
isEqual) {
*contents = wasm::IonDumpContents::OptimizedMIR;
return isEqual;
} else if (!JS_StringEqualsLiteral(cx, option, "lir", &isEqual) || isEqual) {
*contents = wasm::IonDumpContents::LIR;
return isEqual;
} else {
return false;
}
}
static bool WasmDumpIon(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().set(UndefinedValue());
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
uint32_t targetFuncIndex;
if (!ToUint32(cx, args.get(1), &targetFuncIndex)) {
JS_ReportErrorASCII(cx, "argument is not a func index");
return false;
}
wasm::IonDumpContents contents = wasm::IonDumpContents::Default;
if (args.length() > 2 && !ToIonDumpContents(cx, args.get(2), &contents)) {
JS_ReportErrorASCII(cx, "argument is not a valid dump contents");
return false;
}
SharedMem<uint8_t*> dataPointer;
size_t byteLength;
if (!IsBufferSource(cx, args.get(0).toObjectOrNull(), /*allowShared*/ false,
/*allowResizable*/ false, &dataPointer, &byteLength)) {
JS_ReportErrorASCII(cx, "argument is not a buffer source");
return false;
}
wasm::MutableBytes bytecode = cx->new_<wasm::ShareableBytes>();
if (!bytecode) {
return false;
}
if (!bytecode->append(dataPointer.unwrap(), byteLength)) {
ReportOutOfMemory(cx);
return false;
}
UniqueChars error;
JSSprinter out(cx);
if (!out.init()) {
ReportOutOfMemory(cx);
return false;
}
if (!wasm::DumpIonFunctionInModule(*bytecode, targetFuncIndex, contents, out,
&error)) {
if (error) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_COMPILE_ERROR, error.get());
return false;
}
ReportOutOfMemory(cx);
return false;
}
JSString* str = out.release(cx);
if (!str) {
ReportOutOfMemory(cx);
return false;
}
args.rval().set(StringValue(str));
return true;
}
enum class Flag { Tier2Complete, Deserialized, ParsedBranchHints };
static bool WasmReturnFlag(JSContext* cx, unsigned argc, Value* vp, Flag flag) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
Rooted<WasmModuleObject*> module(
cx, args[0].toObject().maybeUnwrapIf<WasmModuleObject>());
if (!module) {
JS_ReportErrorASCII(cx, "argument is not a WebAssembly.Module");
return false;
}
bool b;
switch (flag) {
case Flag::Tier2Complete:
b = !module->module().testingTier2Active();
break;
case Flag::Deserialized:
b = module->module().loggingDeserialized();
break;
case Flag::ParsedBranchHints:
b = !module->module().codeMeta().branchHints.failedParse();
break;
}
args.rval().set(BooleanValue(b));
return true;
}
static bool wasmMetadataAnalysis(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
if (args[0].toObject().is<WasmModuleObject>()) {
HashMap<const char*, uint32_t, mozilla::CStringHasher, SystemAllocPolicy>
hashmap = args[0]
.toObject()
.as<WasmModuleObject>()
.module()
.code()
.metadataAnalysis(cx);
if (hashmap.empty()) {
JS_ReportErrorASCII(cx, "Metadata analysis has failed");
return false;
}
// metadataAnalysis returned a map of {key, value} with various statistics
// convert it into a dictionary to be used by JS
Rooted<IdValueVector> props(cx, IdValueVector(cx));
for (auto iter = hashmap.iter(); !iter.done(); iter.next()) {
const auto* key = iter.get().key();
auto value = iter.get().value();
JSString* string = JS_NewStringCopyZ(cx, key);
if (!string) {
return false;
}
if (!props.append(
IdValuePair(NameToId(string->asLinear().toPropertyName(cx)),
NumberValue(value)))) {
return false;
}
}
JSObject* results = NewPlainObjectWithUniqueNames(cx, props);
if (!results) {
return false;
}
args.rval().setObject(*results);
return true;
}
JS_ReportErrorASCII(
cx, "argument is not an exported wasm function or a wasm module");
return false;
}
static bool WasmHasTier2CompilationCompleted(JSContext* cx, unsigned argc,
Value* vp) {
return WasmReturnFlag(cx, argc, vp, Flag::Tier2Complete);
}
static bool WasmLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) {
return WasmReturnFlag(cx, argc, vp, Flag::Deserialized);
}
#ifdef ENABLE_WASM_BRANCH_HINTING
static bool WasmParsedBranchHints(JSContext* cx, unsigned argc, Value* vp) {
return WasmReturnFlag(cx, argc, vp, Flag::ParsedBranchHints);
}
#endif // ENABLE_WASM_BRANCH_HINTING
static bool WasmBuiltinI8VecMul(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<WasmModuleObject*> module(cx);
if (!wasm::CompileBuiltinModule(cx, wasm::BuiltinModuleId::SelfTest,
&module)) {
return false;
}
args.rval().set(ObjectValue(*module.get()));
return true;
}
static bool WasmGcReadField(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
if (!args.requireAtLeast(cx, "wasmGcReadField", 2)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<WasmGcObject>()) {
ReportUsageErrorASCII(cx, callee,
"First argument must be a WebAssembly GC object");
return false;
}
int32_t fieldIndex;
if (!JS::ToInt32(cx, args[1], &fieldIndex) || fieldIndex < 0) {
ReportUsageErrorASCII(cx, callee,
"Second argument must be a non-negative integer");
return false;
}
Rooted<WasmGcObject*> gcObject(cx, &args[0].toObject().as<WasmGcObject>());
Rooted<Value> gcValue(cx);
if (!WasmGcObject::loadValue(cx, gcObject, jsid::Int(int32_t(fieldIndex)),
&gcValue)) {
return false;
}
args.rval().set(gcValue);
return true;
}
static bool WasmGcArrayLength(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
if (!args.requireAtLeast(cx, "wasmGcArrayLength", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<WasmArrayObject>()) {
ReportUsageErrorASCII(cx, callee,
"First argument must be a WebAssembly GC array");
return false;
}
WasmArrayObject& arr = args[0].toObject().as<WasmArrayObject>();
args.rval().setInt32(int32_t(arr.numElements_));
return true;
}
static bool LargeArrayBufferSupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(ArrayBufferObject::ByteLengthLimit >
ArrayBufferObject::ByteLengthLimitForSmallBuffer);
return true;
}
static bool IsLazyFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
JSFunction* fun = &args[0].toObject().as<JSFunction>();
args.rval().setBoolean(fun->isInterpreted() && !fun->hasBytecode());
return true;
}
static bool IsRelazifiableFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
JSFunction* fun = &args[0].toObject().as<JSFunction>();
args.rval().setBoolean(fun->hasBytecode() &&
fun->nonLazyScript()->allowRelazify());
return true;
}
static bool IsCollectingDelazifications(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
if (fuzzingSafe) {
// When running code concurrently to fill-up the stencil cache, the content
// is not garanteed to be present.
args.rval().setBoolean(false);
return true;
}
JSFunction* fun = &args[0].toObject().as<JSFunction>();
BaseScript* script = fun->baseScript();
args.rval().setBoolean(
bool(script->sourceObject()->isCollectingDelazifications()));
return true;
}
static bool IsDelazificationsPopulated(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
if (fuzzingSafe) {
// When running code concurrently to fill-up the stencil cache, the content
// is not garanteed to be present.
args.rval().setBoolean(false);
return true;
}
JSFunction* fun = &args[0].toObject().as<JSFunction>();
BaseScript* script = fun->baseScript();
RefPtr<frontend::InitialStencilAndDelazifications> stencils =
script->sourceObject()->maybeGetStencils();
if (!stencils) {
args.rval().setBoolean(false);
return true;
}
const frontend::CompilationStencil* stencil =
stencils->getDelazificationFor(script->extent());
args.rval().setBoolean(bool(stencil));
return true;
}
static bool WaitForDelazificationOf(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
args.rval().setUndefined();
JSFunction* fun = &args[0].toObject().as<JSFunction>();
BaseScript* script = fun->baseScript();
if (!script->sourceObject()->isSharingDelazifications()) {
return true;
}
RefPtr<frontend::InitialStencilAndDelazifications> stencils =
script->sourceObject()->maybeGetStencils();
AutoLockHelperThreadState lock;
if (!HelperThreadState().isInitialized(lock)) {
return true;
}
while (true) {
const frontend::CompilationStencil* stencil =
stencils->getDelazificationFor(script->extent());
if (stencil) {
break;
}
HelperThreadState().wait(lock);
}
return true;
}
static bool HasSameBytecodeData(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2) {
JS_ReportErrorASCII(cx, "The function takes exactly two argument.");
return false;
}
auto GetSharedData = [](JSContext* cx,
HandleValue v) -> SharedImmutableScriptData* {
if (!v.isObject()) {
JS_ReportErrorASCII(cx, "The arguments must be interpreted functions.");
return nullptr;
}
RootedObject obj(cx, CheckedUnwrapDynamic(&v.toObject(), cx));
if (!obj) {
return nullptr;
}
if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) {
JS_ReportErrorASCII(cx, "The arguments must be interpreted functions.");
return nullptr;
}
AutoRealm ar(cx, obj);
RootedFunction fun(cx, &obj->as<JSFunction>());
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
if (!script) {
return nullptr;
}
MOZ_ASSERT(script->sharedData());
return script->sharedData();
};
// NOTE: We use RefPtr below to keep the data alive across possible GC since
// the functions may be in different Zones.
RefPtr<SharedImmutableScriptData> sharedData1 = GetSharedData(cx, args[0]);
if (!sharedData1) {
return false;
}
RefPtr<SharedImmutableScriptData> sharedData2 = GetSharedData(cx, args[1]);
if (!sharedData2) {
return false;
}
args.rval().setBoolean(sharedData1 == sharedData2);
return true;
}
static bool InternalConst(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0) {
JS_ReportErrorASCII(cx, "the function takes exactly one argument");
return false;
}
JSString* str = ToString(cx, args[0]);
if (!str) {
return false;
}
JSLinearString* linear = JS_EnsureLinearString(cx, str);
if (!linear) {
return false;
}
if (JS_LinearStringEqualsLiteral(linear, "MARK_STACK_BASE_CAPACITY")) {
args.rval().setNumber(uint32_t(js::MARK_STACK_BASE_CAPACITY));
} else {
JS_ReportErrorASCII(cx, "unknown const name");
return false;
}
return true;
}
static bool GCPreserveCode(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
cx->runtime()->gc.setAlwaysPreserveCode();
args.rval().setUndefined();
return true;
}
#ifdef JS_GC_ZEAL
static bool ParseGCZealMode(JSContext* cx, const CallArgs& args,
js::gc::GCRuntime::ZealSettings* zeal) {
JSString* modestr = ToString(cx, args.get(0));
if (!modestr) {
return false;
}
UniqueChars mode = EncodeLatin1(cx, modestr);
if (!mode) {
return false;
}
bool invalid;
if (!cx->runtime()->gc.parseZeal(mode.get(), strlen(mode.get()), zeal,
&invalid)) {
js::ReportOutOfMemory(cx);
return false;
}
if (invalid) {
JS_ReportErrorASCII(cx, "invalid gczeal argument");
return false;
}
return true;
}
static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 2) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
if (args.length() == 0) {
uint32_t zealBits, unused1, unused2;
cx->runtime()->gc.getZealBits(&zealBits, &unused1, &unused2);
args.rval().setNumber(zealBits);
return true;
}
js::gc::GCRuntime::ZealSettings zeal;
if (!ParseGCZealMode(cx, args, &zeal)) {
return false;
}
Maybe<uint32_t> forceFrequency;
if (args.length() >= 2) {
uint32_t frequency;
if (!ToUint32(cx, args.get(1), &frequency)) {
return false;
}
forceFrequency.emplace(frequency);
}
for (auto [mode, frequency] : zeal) {
JS::SetGCZeal(cx, mode, forceFrequency.valueOr(frequency));
}
args.rval().setUndefined();
return true;
}
static bool UnsetGCZeal(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
js::gc::GCRuntime::ZealSettings zeal;
if (!ParseGCZealMode(cx, args, &zeal)) {
return false;
}
for (auto [mode, _frequency] : zeal) {
JS::UnsetGCZeal(cx, mode);
}
args.rval().setUndefined();
return true;
}
static bool ScheduleGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
if (args.length() == 0) {
/* Fetch next zeal trigger only. */
} else if (args[0].isNumber()) {
/* Schedule a GC to happen after |arg| allocations. */
JS::ScheduleGC(cx, std::max(int(args[0].toNumber()), 0));
} else {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Bad argument - expecting number");
return false;
}
uint32_t zealBits;
uint32_t freq;
uint32_t next;
JS::GetGCZealBits(cx, &zealBits, &freq, &next);
args.rval().setInt32(next);
return true;
}
static bool SelectForGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
/*
* The selectedForMarking set is intended to be manually marked at slice
* start to detect missing pre-barriers. It is invalid for nursery things
* to be in the set, so evict the nursery before adding items.
*/
cx->runtime()->gc.evictNursery();
for (unsigned i = 0; i < args.length(); i++) {
if (args[i].isObject()) {
if (!cx->runtime()->gc.selectForMarking(&args[i].toObject())) {
ReportOutOfMemory(cx);
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) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
gc::VerifyBarriers(cx->runtime(), gc::PostBarrierVerifier);
args.rval().setUndefined();
return true;
}
static bool CurrentGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
RootedObject result(cx, JS_NewPlainObject(cx));
if (!result) {
return false;
}
js::gc::GCRuntime& gc = cx->runtime()->gc;
const char* state = StateName(gc.state());
RootedString str(cx, JS_NewStringCopyZ(cx, state));
if (!str) {
return false;
}
RootedValue val(cx, StringValue(str));
if (!JS_DefineProperty(cx, result, "incrementalState", val,
JSPROP_ENUMERATE)) {
return false;
}
if (gc.state() == js::gc::State::Sweep) {
val = Int32Value(gc.getCurrentSweepGroupIndex());
if (!JS_DefineProperty(cx, result, "sweepGroup", val, JSPROP_ENUMERATE)) {
return false;
}
}
val = BooleanValue(gc.isIncrementalGCInProgress() && gc.isShrinkingGC());
if (!JS_DefineProperty(cx, result, "isShrinking", val, JSPROP_ENUMERATE)) {
return false;
}
val = Int32Value(gc.gcNumber());
if (!JS_DefineProperty(cx, result, "number", val, JSPROP_ENUMERATE)) {
return false;
}
val = Int32Value(gc.minorGCCount());
if (!JS_DefineProperty(cx, result, "minorCount", val, JSPROP_ENUMERATE)) {
return false;
}
val = Int32Value(gc.majorGCCount());
if (!JS_DefineProperty(cx, result, "majorCount", val, JSPROP_ENUMERATE)) {
return false;
}
val = BooleanValue(gc.isFullGc());
if (!JS_DefineProperty(cx, result, "isFull", val, JSPROP_ENUMERATE)) {
return false;
}
val = BooleanValue(gc.isCompactingGc());
if (!JS_DefineProperty(cx, result, "isCompacting", val, JSPROP_ENUMERATE)) {
return false;
}
# ifdef DEBUG
val = Int32Value(gc.testMarkQueuePos());
if (!JS_DefineProperty(cx, result, "queuePos", val, JSPROP_ENUMERATE)) {
return false;
}
# endif
args.rval().setObject(*result);
return true;
}
static bool DeterministicGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
cx->runtime()->gc.setDeterministic(ToBoolean(args[0]));
args.rval().setUndefined();
return true;
}
static bool DumpGCArenaInfo(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
js::gc::DumpArenaInfo();
args.rval().setUndefined();
return true;
}
static bool SetMarkStackLimit(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
int32_t value;
if (!ToInt32(cx, args[0], &value) || value <= 0) {
JS_ReportErrorASCII(cx, "Bad argument to SetMarkStackLimit");
return false;
}
if (JS::IsIncrementalGCInProgress(cx)) {
JS_ReportErrorASCII(
cx, "Attempt to set markStackLimit while a GC is in progress");
return false;
}
JSRuntime* runtime = cx->runtime();
AutoLockGC lock(runtime);
runtime->gc.setMarkStackLimit(value, lock);
args.rval().setUndefined();
return true;
}
#endif /* JS_GC_ZEAL */
static bool SetMallocMaxDirtyPageModifier(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
constexpr int32_t MinSupportedValue = -5;
constexpr int32_t MaxSupportedValue = 16;
int32_t value;
if (!ToInt32(cx, args[0], &value)) {
return false;
}
if (value < MinSupportedValue || value > MaxSupportedValue) {
JS_ReportErrorASCII(cx, "Bad argument to setMallocMaxDirtyPageModifier");
return false;
}
#ifdef MOZ_MEMORY
moz_set_max_dirty_page_modifier(value);
#endif
args.rval().setUndefined();
return true;
}
static bool GCState(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
const char* state;
if (args.length() == 1) {
if (!args[0].isObject()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected object");
return false;
}
JSObject* obj = UncheckedUnwrap(&args[0].toObject());
state = gc::StateName(obj->zone()->gcState());
} else {
state = gc::StateName(cx->runtime()->gc.state());
}
return ReturnStringCopy(cx, args, state);
}
static bool ScheduleZoneForGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expecting a single argument");
return false;
}
if (args[0].isObject()) {
// Ensure that |zone| is collected during the next GC.
Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone();
PrepareZoneForGC(cx, zone);
} else if (args[0].isString()) {
// This allows us to schedule the atoms zone for GC.
Zone* zone = args[0].toString()->zoneFromAnyThread();
if (!CurrentThreadCanAccessZone(zone)) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Specified zone not accessible for GC");
return false;
}
PrepareZoneForGC(cx, zone);
} else {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Bad argument - expecting object or string");
return false;
}
args.rval().setUndefined();
return true;
}
static bool StartGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 2) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
auto budget = SliceBudget::unlimited();
if (args.length() >= 1) {
uint32_t work = 0;
if (!ToUint32(cx, args[0], &work)) {
return false;
}
budget = SliceBudget(WorkBudget(work));
}
bool shrinking = false;
if (args.length() >= 2) {
Value arg = args[1];
if (arg.isString()) {
if (!JS_StringEqualsLiteral(cx, arg.toString(), "shrinking",
&shrinking)) {
return false;
}
}
}
JSRuntime* rt = cx->runtime();
if (rt->gc.isIncrementalGCInProgress()) {
RootedObject callee(cx, &args.callee());
JS_ReportErrorASCII(cx, "Incremental GC already in progress");
return false;
}
JS::GCOptions options =
shrinking ? JS::GCOptions::Shrink : JS::GCOptions::Normal;
rt->gc.startDebugGC(options, budget);
args.rval().setUndefined();
return true;
}
static bool FinishGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
JSRuntime* rt = cx->runtime();
if (rt->gc.isIncrementalGCInProgress()) {
rt->gc.finishGC(JS::GCReason::DEBUG_GC);
}
args.rval().setUndefined();
return true;
}
static bool GCSlice(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 2) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
auto budget = SliceBudget::unlimited();
if (args.length() >= 1) {
uint32_t work = 0;
if (!ToUint32(cx, args[0], &work)) {
return false;
}
budget = SliceBudget(WorkBudget(work));
}
bool dontStart = false;
if (args.get(1).isObject()) {
RootedObject options(cx, &args[1].toObject());
RootedValue v(cx);
if (!JS_GetProperty(cx, options, "dontStart", &v)) {
return false;
}
dontStart = ToBoolean(v);
}
JSRuntime* rt = cx->runtime();
if (rt->gc.isIncrementalGCInProgress()) {
rt->gc.debugGCSlice(budget);
} else if (!dontStart) {
rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
}
args.rval().setUndefined();
return true;
}
static bool AbortGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
JS::AbortIncrementalGC(cx);
args.rval().setUndefined();
return true;
}
static bool FullCompartmentChecks(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
cx->runtime()->gc.setFullCompartmentChecks(ToBoolean(args[0]));
args.rval().setUndefined();
return true;
}
static bool NondeterministicGetWeakMapKeys(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
if (!args[0].isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_EXPECTED_TYPE,
"nondeterministicGetWeakMapKeys", "WeakMap",
InformalValueTypeName(args[0]));
return false;
}
RootedObject arr(cx);
RootedObject mapObj(cx, &args[0].toObject());
if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &arr)) {
return false;
}
if (!arr) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_EXPECTED_TYPE,
"nondeterministicGetWeakMapKeys", "WeakMap",
args[0].toObject().getClass()->name);
return false;
}
args.rval().setObject(*arr);
return true;
}
class HasChildTracer final : public JS::CallbackTracer {
RootedValue child_;
bool found_;
void onChild(JS::GCCellPtr thing, const char* name) override {
if (thing.asCell() == child_.toGCThing()) {
found_ = true;
}
}
public:
HasChildTracer(JSContext* cx, HandleValue child)
: JS::CallbackTracer(cx, JS::TracerKind::Callback,
JS::WeakMapTraceAction::TraceKeysAndValues),
child_(cx, child),
found_(false) {}
bool found() const { return found_; }
};
static bool HasChild(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue parent(cx, args.get(0));
RootedValue child(cx, args.get(1));
if (!parent.isGCThing() || !child.isGCThing()) {
args.rval().setBoolean(false);
return true;
}
HasChildTracer trc(cx, child);
TraceChildren(&trc, JS::GCCellPtr(parent.toGCThing(), parent.traceKind()));
args.rval().setBoolean(trc.found());
return true;
}
static bool SetSavedStacksRNGState(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "setSavedStacksRNGState", 1)) {
return false;
}
int32_t seed;
if (!ToInt32(cx, args[0], &seed)) {
return false;
}
// Either one or the other of the seed arguments must be non-zero;
// make this true no matter what value 'seed' has.
cx->realm()->savedStacks().setRNGState(seed, (seed + 1) * 33);
return true;
}
static bool GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setNumber(cx->realm()->savedStacks().count());
return true;
}
static bool ClearSavedFrames(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
js::SavedStacks& savedStacks = cx->realm()->savedStacks();
savedStacks.clear();
for (ActivationIterator iter(cx); !iter.done(); ++iter) {
iter->clearLiveSavedFrameCache();
}
args.rval().setUndefined();
return true;
}
static bool SaveStack(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JS::StackCapture capture((JS::AllFrames()));
if (args.length() >= 1) {
double maxDouble;
if (!ToNumber(cx, args[0], &maxDouble)) {
return false;
}
if (std::isnan(maxDouble) || maxDouble < 0 || maxDouble > UINT32_MAX) {
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
nullptr, "not a valid maximum frame count");
return false;
}
uint32_t max = uint32_t(maxDouble);
if (max > 0) {
capture = JS::StackCapture(JS::MaxFrames(max));
}
}
RootedObject compartmentObject(cx);
if (args.length() >= 2) {
if (!args[1].isObject()) {
ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
nullptr, "not an object");
return false;
}
compartmentObject = UncheckedUnwrap(&args[1].toObject());
if (!compartmentObject) {
return false;
}
}
RootedObject stack(cx);
{
Maybe<AutoRealm> ar;
if (compartmentObject) {
ar.emplace(cx, compartmentObject);
}
if (!JS::CaptureCurrentStack(cx, &stack, std::move(capture))) {
return false;
}
}
if (stack && !cx->compartment()->wrap(cx, &stack)) {
return false;
}
args.rval().setObjectOrNull(stack);
return true;
}
static bool CaptureFirstSubsumedFrame(JSContext* cx, unsigned argc,
JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "captureFirstSubsumedFrame", 1)) {
return false;
}
if (!args[0].isObject()) {
JS_ReportErrorASCII(cx, "The argument must be an object");
return false;
}
RootedObject obj(cx, &args[0].toObject());
obj = CheckedUnwrapStatic(obj);
if (!obj) {
JS_ReportErrorASCII(cx, "Denied permission to object.");
return false;
}
JS::StackCapture capture(
JS::FirstSubsumedFrame(cx, obj->nonCCWRealm()->principals()));
if (args.length() > 1) {
capture.as<JS::FirstSubsumedFrame>().ignoreSelfHosted =
JS::ToBoolean(args[1]);
}
JS::RootedObject capturedStack(cx);
if (!JS::CaptureCurrentStack(cx, &capturedStack, std::move(capture))) {
return false;
}
args.rval().setObjectOrNull(capturedStack);
return true;
}
static bool CallFunctionFromNativeFrame(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "The function takes exactly one argument.");
return false;
}
if (!args[0].isObject() || !IsCallable(args[0])) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
RootedObject function(cx, &args[0].toObject());
return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
args.rval());
}
static bool CallFunctionWithAsyncStack(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 3) {
JS_ReportErrorASCII(cx, "The function takes exactly three arguments.");
return false;
}
if (!args[0].isObject() || !IsCallable(args[0])) {
JS_ReportErrorASCII(cx, "The first argument should be a function.");
return false;
}
if (!args[1].isObject() || !args[1].toObject().is<SavedFrame>()) {
JS_ReportErrorASCII(cx, "The second argument should be a SavedFrame.");
return false;
}
if (!args[2].isString() || args[2].toString()->empty()) {
JS_ReportErrorASCII(cx, "The third argument should be a non-empty string.");
return false;
}
RootedObject function(cx, &args[0].toObject());
RootedObject stack(cx, &args[1].toObject());
RootedString asyncCause(cx, args[2].toString());
UniqueChars utf8Cause = JS_EncodeStringToUTF8(cx, asyncCause);
if (!utf8Cause) {
MOZ_ASSERT(cx->isExceptionPending());
return false;
}
JS::AutoSetAsyncStackForNewCalls sas(
cx, stack, utf8Cause.get(),
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
args.rval());
}
static bool EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) {
SetAllocationMetadataBuilder(cx, &SavedStacks::metadataBuilder);
return true;
}
static bool DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) {
SetAllocationMetadataBuilder(cx, nullptr);
return true;
}
static bool SetTestFilenameValidationCallback(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Accept all filenames that start with "safe". In system code also accept
// filenames starting with "system".
auto testCb = [](JSContext* cx, const char* filename) -> bool {
if (strstr(filename, "safe") == filename) {
return true;
}
if (cx->realm()->isSystem() && strstr(filename, "system") == filename) {
return true;
}
return false;
};
JS::SetFilenameValidationCallback(testCb);
args.rval().setUndefined();
return true;
}
static JSAtom* GetPropertiesAddedName(JSContext* cx) {
const char* propName = "_propertiesAdded";
return Atomize(cx, propName, strlen(propName));
}
static bool NewObjectWithAddPropertyHook(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
auto addPropHook = [](JSContext* cx, HandleObject obj, HandleId id,
HandleValue v) -> bool {
Rooted<JSAtom*> propName(cx, GetPropertiesAddedName(cx));
if (!propName) {
return false;
}
// Don't do anything if we're adding the _propertiesAdded property.
RootedId propId(cx, AtomToId(propName));
if (id == propId) {
return true;
}
// Increment _propertiesAdded.
RootedValue val(cx);
if (!JS_GetPropertyById(cx, obj, propId, &val)) {
return false;
}
if (!val.isInt32() || val.toInt32() == INT32_MAX) {
return true;
}
val.setInt32(val.toInt32() + 1);
return JS_DefinePropertyById(cx, obj, propId, val, 0);
};
static const JSClassOps classOps = {
addPropHook, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
nullptr, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
static const JSClass cls = {
"ObjectWithAddPropHook",
0,
&classOps,
};
RootedObject obj(cx, JS_NewObject(cx, &cls));
if (!obj) {
return false;
}
// Initialize _propertiesAdded to 0.
Rooted<JSAtom*> propName(cx, GetPropertiesAddedName(cx));
if (!propName) {
return false;
}
RootedId propId(cx, AtomToId(propName));
RootedValue val(cx, Int32Value(0));
if (!JS_DefinePropertyById(cx, obj, propId, val, 0)) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static bool NewObjectWithCallHook(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
static auto hookShared = [](JSContext* cx, CallArgs& args) {
Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
if (!obj) {
return false;
}
// Define |this|. We can't expose the MagicValue to JS, so we use
// "<is_constructing>" in that case.
Rooted<Value> thisv(cx, args.thisv());
if (thisv.isMagic(JS_IS_CONSTRUCTING)) {
JSString* str = NewStringCopyZ<CanGC>(cx, "<is_constructing>");
if (!str) {
return false;
}
thisv.setString(str);
}
if (!DefineDataProperty(cx, obj, cx->names().this_, thisv,
JSPROP_ENUMERATE)) {
return false;
}
// Define |callee|.
if (!DefineDataProperty(cx, obj, cx->names().callee, args.calleev(),
JSPROP_ENUMERATE)) {
return false;
}
// Define |arguments| array.
Rooted<ArrayObject*> arr(
cx, NewDenseCopiedArray(cx, args.length(), args.array()));
if (!arr) {
return false;
}
Rooted<Value> arrVal(cx, ObjectValue(*arr));
if (!DefineDataProperty(cx, obj, cx->names().arguments, arrVal,
JSPROP_ENUMERATE)) {
return false;
}
// Define |newTarget| if constructing.
if (args.isConstructing()) {
const char* propName = "newTarget";
Rooted<JSAtom*> name(cx, Atomize(cx, propName, strlen(propName)));
if (!name) {
return false;
}
Rooted<PropertyKey> key(cx, NameToId(name->asPropertyName()));
if (!DefineDataProperty(cx, obj, key, args.newTarget(),
JSPROP_ENUMERATE)) {
return false;
}
}
args.rval().setObject(*obj);
return true;
};
static auto callHook = [](JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(!args.isConstructing());
return hookShared(cx, args);
};
static auto constructHook = [](JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.isConstructing());
return hookShared(cx, args);
};
static const JSClassOps classOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
nullptr, // finalize
callHook, // call
constructHook, // construct
nullptr, // trace
};
static const JSClass cls = {
"ObjectWithCallHook",
0,
&classOps,
};
Rooted<JSObject*> obj(cx, JS_NewObject(cx, &cls));
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static constexpr JSClass ObjectWithManyReservedSlotsClass = {
"ObjectWithManyReservedSlots",
JSCLASS_HAS_RESERVED_SLOTS(40),
};
static bool NewObjectWithManyReservedSlots(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
static constexpr size_t NumReservedSlots =
JSCLASS_RESERVED_SLOTS(&ObjectWithManyReservedSlotsClass);
static_assert(NumReservedSlots > NativeObject::MAX_FIXED_SLOTS);
RootedObject obj(cx, JS_NewObject(cx, &ObjectWithManyReservedSlotsClass));
if (!obj) {
return false;
}
for (size_t i = 0; i < NumReservedSlots; i++) {
JS_SetReservedSlot(obj, i, Int32Value(i));
}
args.rval().setObject(*obj);
return true;
}
static bool CheckObjectWithManyReservedSlots(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isObject() ||
args[0].toObject().getClass() != &ObjectWithManyReservedSlotsClass) {
JS_ReportErrorASCII(cx,
"Expected object from newObjectWithManyReservedSlots");
return false;
}
JSObject* obj = &args[0].toObject();
static constexpr size_t NumReservedSlots =
JSCLASS_RESERVED_SLOTS(&ObjectWithManyReservedSlotsClass);
for (size_t i = 0; i < NumReservedSlots; i++) {
MOZ_RELEASE_ASSERT(JS::GetReservedSlot(obj, i).toInt32() == int32_t(i));
}
args.rval().setUndefined();
return true;
}
static bool GetWatchtowerLog(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
if (auto* log = cx->runtime()->watchtowerTestingLog.ref().get()) {
Rooted<JSObject*> elem(cx);
for (PlainObject* obj : *log) {
elem = obj;
if (!cx->compartment()->wrap(cx, &elem)) {
return false;
}
if (!values.append(ObjectValue(*elem))) {
return false;
}
}
log->clearAndFree();
}
ArrayObject* arr = NewDenseCopiedArray(cx, values.length(), values.begin());
if (!arr) {
return false;
}
args.rval().setObject(*arr);
return true;
}
static bool AddWatchtowerTarget(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isObject()) {
JS_ReportErrorASCII(cx, "Expected a single object argument.");
return false;
}
if (!cx->runtime()->watchtowerTestingLog.ref()) {
auto vec = cx->make_unique<JSRuntime::RootedPlainObjVec>(cx);
if (!vec) {
return false;
}
cx->runtime()->watchtowerTestingLog = std::move(vec);
}
RootedObject obj(cx, &args[0].toObject());
if (!JSObject::setUseWatchtowerTestingLog(cx, obj)) {
return false;
}
args.rval().setUndefined();
return true;
}
struct TestExternalString : public JSExternalStringCallbacks {
void finalize(JS::Latin1Char* chars) const override { js_free(chars); }
void finalize(char16_t* chars) const override { js_free(chars); }
size_t sizeOfBuffer(const JS::Latin1Char* chars,
mozilla::MallocSizeOf mallocSizeOf) const override {
return mallocSizeOf(chars);
}
size_t sizeOfBuffer(const char16_t* chars,
mozilla::MallocSizeOf mallocSizeOf) const override {
return mallocSizeOf(chars);
}
};
static constexpr TestExternalString TestExternalStringCallbacks;
static bool NewString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedString src(cx, ToString(cx, args.get(0)));
if (!src) {
return false;
}
gc::Heap heap = gc::Heap::Default;
bool wantTwoByte = false;
bool forceExternal = false;
bool maybeExternal = false;
bool newStringBuffer = false;
bool shareStringBuffer = false;
uint32_t capacity = 0;
if (args.get(1).isObject()) {
RootedObject options(cx, &args[1].toObject());
RootedValue v(cx);
bool requestTenured = false;
struct BoolSetting {
const char* name;
bool* value;
};
for (auto [name, setting] :
{BoolSetting{"tenured", &requestTenured},
BoolSetting{"twoByte", &wantTwoByte},
BoolSetting{"external", &forceExternal},
BoolSetting{"maybeExternal", &maybeExternal},
BoolSetting{"newStringBuffer", &newStringBuffer},
BoolSetting{"shareStringBuffer", &shareStringBuffer}}) {
if (!JS_GetProperty(cx, options, name, &v)) {
return false;
}
*setting = ToBoolean(v); // false if not given (or otherwise undefined)
}
struct Uint32Setting {
const char* name;
uint32_t* value;
};
for (auto [name, setting] : {Uint32Setting{"capacity", &capacity}}) {
if (!JS_GetProperty(cx, options, name, &v)) {
return false;
}
int32_t i32;
if (!ToInt32(cx, v, &i32)) {
return false;
}
if (i32 < 0) {
JS_ReportErrorASCII(cx, "nonnegative value required");
return false;
}
*setting = static_cast<uint32_t>(i32);
}
heap = requestTenured ? gc::Heap::Tenured : gc::Heap::Default;
if (forceExternal || maybeExternal) {
wantTwoByte = true;
}
unsigned kinds = forceExternal + maybeExternal + (capacity != 0) +
newStringBuffer + shareStringBuffer;
if (kinds > 1) {
JS_ReportErrorASCII(cx,
"external, capacity, and stringBuffer options can "
"not be used at the same time");
return false;
}
}
auto len = src->length();
RootedString dest(cx);
if (forceExternal || maybeExternal) {
auto buf = cx->make_pod_array<char16_t>(len);
if (!buf) {
return false;
}
if (!JS_CopyStringChars(cx, mozilla::Range<char16_t>(buf.get(), len),
src)) {
return false;
}
bool isExternal = true;
if (forceExternal) {
dest = JSExternalString::new_(cx, buf.get(), len,
&TestExternalStringCallbacks);
} else {
dest = NewMaybeExternalString(
cx, buf.get(), len, &TestExternalStringCallbacks, &isExternal, heap);
}
if (dest && isExternal) {
(void)buf.release(); // Ownership was transferred.
}
} else if (shareStringBuffer) {
if (!src->isLinear() || !src->asLinear().hasStringBuffer()) {
JS_ReportErrorASCII(cx, "source string must have a string buffer");
return false;
}
RefPtr<mozilla::StringBuffer> buffer = src->asLinear().stringBuffer();
if (src->hasLatin1Chars()) {
Rooted<JSString::OwnedChars<Latin1Char>> owned(cx, std::move(buffer),
len);
dest =
JSLinearString::newValidLength<CanGC, Latin1Char>(cx, &owned, heap);
} else {
Rooted<JSString::OwnedChars<char16_t>> owned(cx, std::move(buffer), len);
dest = JSLinearString::newValidLength<CanGC, char16_t>(cx, &owned, heap);
}
} else {
AutoStableStringChars stable(cx);
if (!wantTwoByte && src->hasLatin1Chars()) {
if (!stable.init(cx, src)) {
return false;
}
} else {
if (!stable.initTwoByte(cx, src)) {
return false;
}
}
if (newStringBuffer) {
auto allocString = [&](const auto* chars) -> JSLinearString* {
using CharT =
std::remove_const_t<std::remove_pointer_t<decltype(chars)>>;
if (JSInlineString::lengthFits<CharT>(len)) {
JS_ReportErrorASCII(cx, "Cannot create small non-inline strings");
return nullptr;
}
RefPtr<mozilla::StringBuffer> buffer =
mozilla::StringBuffer::Create(chars, len);
if (!buffer) {
ReportOutOfMemory(cx);
return nullptr;
}
Rooted<JSString::OwnedChars<CharT>> owned(cx, std::move(buffer), len);
return JSLinearString::newValidLength<CanGC, CharT>(cx, &owned, heap);
};
if (stable.isLatin1()) {
dest = allocString(stable.latin1Chars());
} else {
dest = allocString(stable.twoByteChars());
}
} else if (capacity) {
if (capacity < len) {
capacity = len;
}
if (len == 0) {
JS_ReportErrorASCII(cx, "Cannot set capacity of empty string");
return false;
}
auto createLinearString = [&](const auto* chars) -> JSLinearString* {
using CharT =
std::remove_const_t<std::remove_pointer_t<decltype(chars)>>;
if (JSInlineString::lengthFits<CharT>(len)) {
JS_ReportErrorASCII(cx, "Cannot create small non-inline strings");
return nullptr;
}
auto news =
cx->make_pod_arena_array<CharT>(js::StringBufferArena, capacity);
if (!news) {
return nullptr;
}
mozilla::PodCopy(news.get(), chars, len);
Rooted<JSString::OwnedChars<CharT>> owned(cx, std::move(news), len);
return JSLinearString::newValidLength<CanGC, CharT>(cx, &owned, heap);
};
if (stable.isLatin1()) {
dest = createLinearString(stable.latin1Chars());
} else {
dest = createLinearString(stable.twoByteChars());
}
if (dest) {
dest->asLinear().makeExtensible(capacity);
}
} else if (wantTwoByte) {
dest = NewStringCopyNDontDeflate<CanGC>(cx, stable.twoByteChars(), len,
heap);
} else if (stable.isLatin1()) {
dest = NewStringCopyN<CanGC>(cx, stable.latin1Chars(), len, heap);
} else {
// Normal behavior: auto-deflate to latin1 if possible.
dest = NewStringCopyN<CanGC>(cx, stable.twoByteChars(), len, heap);
}
}
if (!dest) {
return false;
}
args.rval().setString(dest);
return true;
}
static bool NewDependentString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedString src(cx, ToString(cx, args.get(0)));
if (!src) {
return false;
}
uint64_t indexStart = 0;
mozilla::Maybe<uint64_t> indexEnd;
gc::Heap heap = gc::Heap::Default;
mozilla::Maybe<gc::Heap> requiredHeap;
bool suppressContraction = false;
if (!ToIndex(cx, args.get(1), &indexStart)) {
return false;
}
Rooted<Value> options(cx);
if (args.get(2).isObject()) {
options = args[2];
} else {
uint64_t idx;
if (args.hasDefined(2)) {
if (!ToIndex(cx, args.get(2), &idx)) {
return false;
}
indexEnd.emplace(idx);
}
options = args.get(3);
}
if (options.isObject()) {
Rooted<Value> v(cx);
Rooted<JSObject*> optObj(cx, &options.toObject());
if (!JS_GetProperty(cx, optObj, "tenured", &v)) {
return false;
}
if (v.isBoolean()) {
requiredHeap.emplace(v.toBoolean() ? gc::Heap::Tenured
: gc::Heap::Default);
heap = *requiredHeap;
}
if (!JS_GetProperty(cx, optObj, "suppress-contraction", &v)) {
return false;
}
suppressContraction = ToBoolean(v);
}
if (indexEnd.isNothing()) {
// Read the length now that no more JS code can run.
indexEnd.emplace(src->length());
}
if (indexStart > src->length() || *indexEnd > src->length() ||
indexStart >= *indexEnd) {
JS_ReportErrorASCII(cx, "invalid dependent string bounds");
return false;
}
if (!src->ensureLinear(cx)) {
return false;
}
Rooted<JSString*> result(
cx, js::NewDependentStringForTesting(
cx, src, indexStart, *indexEnd - indexStart,
suppressContraction ? JS::ContractBaseChain::AllowLong
: JS::ContractBaseChain::Contract,
heap));
if (!result) {
return false;
}
if (!result->isDependent()) {
JS_ReportErrorASCII(cx, "resulting string is not dependent (too short?)");
return false;
}
if (requiredHeap.isSome()) {
if ((*requiredHeap == gc::Heap::Tenured) != result->isTenured()) {
if (result->isTenured()) {
JS_ReportErrorASCII(cx, "nursery string created in tenured heap");
return false;
} else {
JS_ReportErrorASCII(cx, "tenured string created in nursery heap");
return false;
}
}
}
args.rval().setString(result);
return true;
}
// Warning! This will let you create ropes that I'm not sure would be possible
// otherwise, specifically:
//
// - a rope with a zero-length child
// - a rope that would fit into an inline string
//
static bool NewRope(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isString() || !args.get(1).isString()) {
JS_ReportErrorASCII(cx, "newRope requires two string arguments.");
return false;
}
gc::Heap heap = js::gc::Heap::Default;
if (args.get(2).isObject()) {
RootedObject options(cx, &args[2].toObject());
RootedValue v(cx);
if (!JS_GetProperty(cx, options, "nursery", &v)) {
return false;
}
if (!v.isUndefined() && !ToBoolean(v)) {
heap = js::gc::Heap::Tenured;
}
}
RootedString left(cx, args[0].toString());
RootedString right(cx, args[1].toString());
size_t length = JS_GetStringLength(left) + JS_GetStringLength(right);
if (length > JSString::MAX_LENGTH) {
JS_ReportErrorASCII(cx, "rope length exceeds maximum string length");
return false;
}
// Disallow creating ropes where one side is empty.
if (left->empty() || right->empty()) {
JS_ReportErrorASCII(cx, "rope child mustn't be the empty string");
return false;
}
// Disallow creating ropes which fit into inline strings.
if (left->hasLatin1Chars() && right->hasLatin1Chars()) {
if (JSInlineString::lengthFits<JS::Latin1Char>(length)) {
JS_ReportErrorASCII(cx, "Cannot create small non-inline ropes");
return false;
}
} else {
if (JSInlineString::lengthFits<char16_t>(length)) {
JS_ReportErrorASCII(cx, "Cannot create small non-inline ropes");
return false;
}
}
auto* str = JSRope::new_<CanGC>(cx, left, right, length, heap);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool IsRope(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isString()) {
JS_ReportErrorASCII(cx, "isRope requires a string argument.");
return false;
}
JSString* str = args[0].toString();
args.rval().setBoolean(str->isRope());
return true;
}
static bool EnsureLinearString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isString()) {
JS_ReportErrorASCII(
cx, "ensureLinearString takes exactly one string argument.");
return false;
}
JSLinearString* linear = args[0].toString()->ensureLinear(cx);
if (!linear) {
return false;
}
args.rval().setString(linear);
return true;
}
static bool RepresentativeStringArray(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject array(cx, JS::NewArrayObject(cx, 0));
if (!array) {
return false;
}
if (!JSString::fillWithRepresentatives(cx, array.as<ArrayObject>())) {
return false;
}
args.rval().setObject(*array);
return true;
}
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
static bool OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setInt32(js::THREAD_TYPE_MAX);
return true;
}
static bool CheckCanSimulateOOM(JSContext* cx) {
if (js::oom::GetThreadType() != js::THREAD_TYPE_MAIN) {
JS_ReportErrorASCII(
cx, "Simulated OOM failure is only supported on the main thread");
return false;
}
return true;
}
static bool SetupOOMFailure(JSContext* cx, bool failAlways, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (disableOOMFunctions) {
args.rval().setUndefined();
return true;
}
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "Count argument required");
return false;
}
if (args.length() > 2) {
JS_ReportErrorASCII(cx, "Too many arguments");
return false;
}
int32_t count;
if (!JS::ToInt32(cx, args.get(0), &count)) {
return false;
}
if (count <= 0) {
JS_ReportErrorASCII(cx, "OOM cutoff should be positive");
return false;
}
uint32_t targetThread = js::THREAD_TYPE_MAIN;
if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread)) {
return false;
}
if (targetThread == js::THREAD_TYPE_NONE ||
targetThread == js::THREAD_TYPE_WORKER ||
targetThread >= js::THREAD_TYPE_MAX) {
JS_ReportErrorASCII(cx, "Invalid thread type specified");
return false;
}
if (!CheckCanSimulateOOM(cx)) {
return false;
}
js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM,
count, targetThread, failAlways);
args.rval().setUndefined();
return true;
}
static bool OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp) {
return SetupOOMFailure(cx, true, argc, vp);
}
static bool OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp) {
return SetupOOMFailure(cx, false, argc, vp);
}
static bool ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!CheckCanSimulateOOM(cx)) {
return false;
}
args.rval().setBoolean(js::oom::HadSimulatedOOM());
js::oom::simulator.reset();
return true;
}
static size_t CountCompartments(JSContext* cx) {
size_t count = 0;
for (auto zone : cx->runtime()->gc.zones()) {
count += zone->compartments().length();
}
return count;
}
// Iterative failure testing: test a function by simulating failures at indexed
// locations throughout the normal execution path and checking that the
// resulting state of the environment is consistent with the error result.
//
// For example, trigger OOM at every allocation point and test that the function
// either recovers and succeeds or raises an exception and fails.
class MOZ_STACK_CLASS IterativeFailureTest {
public:
struct FailureSimulator {
virtual void setup(JSContext* cx) {}
virtual void teardown(JSContext* cx) {}
virtual void startSimulating(JSContext* cx, unsigned iteration,
unsigned thread, bool keepFailing) = 0;
virtual bool stopSimulating() = 0;
virtual void cleanup(JSContext* cx) {}
};
IterativeFailureTest(JSContext* cx, FailureSimulator& simulator);
bool initParams(const CallArgs& args);
bool test();
private:
bool setup();
bool testThread(unsigned thread);
bool testIteration(unsigned thread, unsigned iteration,
bool& failureWasSimulated, MutableHandleValue exception);
void cleanup();
void teardown();
JSContext* const cx;
FailureSimulator& simulator;
size_t compartmentCount;
// Test parameters set by initParams.
RootedFunction testFunction;
unsigned threadStart = 0;
unsigned threadEnd = 0;
bool expectExceptionOnFailure = true;
bool keepFailing = false;
bool verbose = false;
};
bool RunIterativeFailureTest(
JSContext* cx, const CallArgs& args,
IterativeFailureTest::FailureSimulator& simulator) {
IterativeFailureTest test(cx, simulator);
return test.initParams(args) && test.test();
}
IterativeFailureTest::IterativeFailureTest(JSContext* cx,
FailureSimulator& simulator)
: cx(cx), simulator(simulator), testFunction(cx) {}
bool IterativeFailureTest::test() {
if (disableOOMFunctions) {
return true;
}
if (!setup()) {
return false;
}
auto onExit = mozilla::MakeScopeExit([this] { teardown(); });
for (unsigned thread = threadStart; thread <= threadEnd; thread++) {
if (!testThread(thread)) {
return false;
}
}
return true;
}
bool IterativeFailureTest::setup() {
if (!CheckCanSimulateOOM(cx)) {
return false;
}
// Disallow nested tests.
if (cx->runningOOMTest) {
JS_ReportErrorASCII(
cx, "Nested call to iterative failure test is not allowed.");
return false;
}
cx->runningOOMTest = true;
MOZ_ASSERT(!cx->isExceptionPending());
# ifdef JS_GC_ZEAL
JS::SetGCZeal(cx, 0, JS::ShellDefaultGCZealFrequency);
# endif
// Delazify the function here if necessary so we don't end up testing that.
if (testFunction->isInterpreted() &&
!JSFunction::getOrCreateScript(cx, testFunction)) {
return false;
}
compartmentCount = CountCompartments(cx);
simulator.setup(cx);
return true;
}
bool IterativeFailureTest::testThread(unsigned thread) {
if (verbose) {
fprintf(stderr, "thread %u\n", thread);
}
RootedValue exception(cx);
unsigned iteration = 1;
bool failureWasSimulated;
do {
if (!testIteration(thread, iteration, failureWasSimulated, &exception)) {
return false;
}
iteration++;
} while (failureWasSimulated);
if (verbose) {
fprintf(stderr, " finished after %u iterations\n", iteration - 1);
if (!exception.isUndefined()) {
RootedString str(cx, JS::ToString(cx, exception));
if (!str) {
fprintf(stderr, " error while trying to print exception, giving up\n");
return false;
}
UniqueChars bytes(JS_EncodeStringToUTF8(cx, str));
if (!bytes) {
return false;
}
fprintf(stderr, " threw %s\n", bytes.get());
}
}
return true;
}
bool IterativeFailureTest::testIteration(unsigned thread, unsigned iteration,
bool& failureWasSimulated,
MutableHandleValue exception) {
if (verbose) {
fprintf(stderr, " iteration %u\n", iteration);
}
MOZ_RELEASE_ASSERT(!cx->isExceptionPending());
simulator.startSimulating(cx, iteration, thread, keepFailing);
RootedValue result(cx);
bool ok = JS_CallFunction(cx, cx->global(), testFunction,
HandleValueArray::empty(), &result);
failureWasSimulated = simulator.stopSimulating();
if (ok && cx->isExceptionPending()) {
MOZ_CRASH(
"Thunk execution succeeded but an exception was raised - missing error "
"check?");
}
if (!ok && !cx->isExceptionPending() && expectExceptionOnFailure) {
MOZ_CRASH(
"Thunk execution failed but no exception was raised - missing call to "
"js::ReportOutOfMemory()?");
}
// Note that it is possible that the function throws an exception unconnected
// to the simulated failure, in which case we ignore it. More correct would be
// to have the caller pass some kind of exception specification and to check
// the exception against it.
if (!failureWasSimulated && cx->isExceptionPending()) {
if (!cx->getPendingException(exception)) {
return false;
}
}
cx->clearPendingException();
cleanup();
return true;
}
void IterativeFailureTest::cleanup() {
simulator.cleanup(cx);
gc::FinishGC(cx);
// Some tests create a new compartment or zone on every iteration. Our GC is
// triggered by GC allocations and not by number of compartments or zones, so
// these won't normally get cleaned up. The check here stops some tests
// running out of memory. ("Gentlemen, you can't fight in here! This is the
// War oom!")
if (CountCompartments(cx) > compartmentCount + 100) {
JS_GC(cx);
compartmentCount = CountCompartments(cx);
}
}
void IterativeFailureTest::teardown() {
simulator.teardown(cx);
cx->runningOOMTest = false;
}
bool IterativeFailureTest::initParams(const CallArgs& args) {
if (args.length() < 1 || args.length() > 2) {
JS_ReportErrorASCII(cx, "function takes between 1 and 2 arguments.");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "The first argument must be the function to test.");
return false;
}
testFunction = &args[0].toObject().as<JSFunction>();
if (args.length() == 2) {
if (args[1].isBoolean()) {
expectExceptionOnFailure = args[1].toBoolean();
} else if (args[1].isObject()) {
RootedObject options(cx, &args[1].toObject());
RootedValue value(cx);
if (!JS_GetProperty(cx, options, "expectExceptionOnFailure", &value)) {
return false;
}
if (!value.isUndefined()) {
expectExceptionOnFailure = ToBoolean(value);
}
if (!JS_GetProperty(cx, options, "keepFailing", &value)) {
return false;
}
if (!value.isUndefined()) {
keepFailing = ToBoolean(value);
}
} else {
JS_ReportErrorASCII(
cx, "The optional second argument must be an object or a boolean.");
return false;
}
}
// There are some places where we do fail without raising an exception, so
// we can't expose this to the fuzzers by default.
if (fuzzingSafe) {
expectExceptionOnFailure = false;
}
// Test all threads by default except worker threads.
threadStart = oom::FirstThreadTypeToTest;
threadEnd = oom::LastThreadTypeToTest;
// Test a single thread type if specified by the OOM_THREAD environment
// variable.
int threadOption = 0;
if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
if (threadOption < oom::FirstThreadTypeToTest ||
threadOption > oom::LastThreadTypeToTest) {
JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
return false;
}
threadStart = threadOption;
threadEnd = threadOption;
}
verbose = EnvVarIsDefined("OOM_VERBOSE");
return true;
}
struct OOMSimulator : public IterativeFailureTest::FailureSimulator {
void setup(JSContext* cx) override { cx->runtime()->hadOutOfMemory = false; }
void startSimulating(JSContext* cx, unsigned i, unsigned thread,
bool keepFailing) override {
MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
js::oom::simulator.simulateFailureAfter(
js::oom::FailureSimulator::Kind::OOM, i, thread, keepFailing);
}
bool stopSimulating() override {
bool handledOOM = js::oom::HadSimulatedOOM();
js::oom::simulator.reset();
return handledOOM;
}
void cleanup(JSContext* cx) override {
cx->runtime()->hadOutOfMemory = false;
}
};
static bool OOMTest(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
OOMSimulator simulator;
if (!RunIterativeFailureTest(cx, args, simulator)) {
return false;
}
args.rval().setUndefined();
return true;
}
struct StackOOMSimulator : public IterativeFailureTest::FailureSimulator {
void startSimulating(JSContext* cx, unsigned i, unsigned thread,
bool keepFailing) override {
js::oom::simulator.simulateFailureAfter(
js::oom::FailureSimulator::Kind::StackOOM, i, thread, keepFailing);
}
bool stopSimulating() override {
bool handledOOM = js::oom::HadSimulatedStackOOM();
js::oom::simulator.reset();
return handledOOM;
}
};
static bool StackTest(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
StackOOMSimulator simulator;
if (!RunIterativeFailureTest(cx, args, simulator)) {
return false;
}
args.rval().setUndefined();
return true;
}
struct FailingIterruptSimulator
: public IterativeFailureTest::FailureSimulator {
JSInterruptCallback* prevEnd = nullptr;
static bool failingInterruptCallback(JSContext* cx) { return false; }
void setup(JSContext* cx) override {
prevEnd = cx->interruptCallbacks().end();
JS_AddInterruptCallback(cx, failingInterruptCallback);
}
void teardown(JSContext* cx) override {
cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
}
void startSimulating(JSContext* cx, unsigned i, unsigned thread,
bool keepFailing) override {
js::oom::simulator.simulateFailureAfter(
js::oom::FailureSimulator::Kind::Interrupt, i, thread, keepFailing);
}
bool stopSimulating() override {
bool handledInterrupt = js::oom::HadSimulatedInterrupt();
js::oom::simulator.reset();
return handledInterrupt;
}
};
static bool InterruptTest(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
FailingIterruptSimulator simulator;
if (!RunIterativeFailureTest(cx, args, simulator)) {
return false;
}
args.rval().setUndefined();
return true;
}
#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
static bool SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "settlePromiseNow", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<PromiseObject>()) {
JS_ReportErrorASCII(cx, "first argument must be a Promise object");
return false;
}
Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
JS_ReportErrorASCII(
cx, "async function/generator's promise shouldn't be manually settled");
return false;
}
if (promise->state() != JS::PromiseState::Pending) {
JS_ReportErrorASCII(cx, "cannot settle an already-resolved promise");
return false;
}
if (IsPromiseWithDefaultResolvingFunction(promise)) {
SetAlreadyResolvedPromiseWithDefaultResolvingFunction(promise);
}
int32_t flags = promise->flags();
promise->setFixedSlot(
PromiseSlot_Flags,
Int32Value(flags | PROMISE_FLAG_RESOLVED | PROMISE_FLAG_FULFILLED));
promise->setFixedSlot(PromiseSlot_ReactionsOrResult, UndefinedValue());
DebugAPI::onPromiseSettled(cx, promise);
return true;
}
static bool GetWaitForAllPromise(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "getWaitForAllPromise", 1)) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ArrayObject>() ||
args[0].toObject().as<NativeObject>().isIndexed()) {
JS_ReportErrorASCII(
cx, "first argument must be a dense Array of Promise objects");
return false;
}
Rooted<NativeObject*> list(cx, &args[0].toObject().as<NativeObject>());
RootedObjectVector promises(cx);
uint32_t count = list->getDenseInitializedLength();
if (!promises.resize(count)) {
return false;
}
for (uint32_t i = 0; i < count; i++) {
RootedValue elem(cx, list->getDenseElement(i));
if (!elem.isObject() || !elem.toObject().is<PromiseObject>()) {
JS_ReportErrorASCII(
cx, "Each entry in the passed-in Array must be a Promise");
return false;
}
promises[i].set(&elem.toObject());
}
RootedObject resultPromise(cx, JS::GetWaitForAllPromise(cx, promises));
if (!resultPromise) {
return false;
}
args.rval().set(ObjectValue(*resultPromise));
return true;
}
static bool ResolvePromise(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "resolvePromise", 2)) {
return false;
}
if (!args[0].isObject() ||
!UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
JS_ReportErrorASCII(
cx, "first argument must be a maybe-wrapped Promise object");
return false;
}
RootedObject promise(cx, &args[0].toObject());
RootedValue resolution(cx, args[1]);
mozilla::Maybe<AutoRealm> ar;
if (IsWrapper(promise)) {
promise = UncheckedUnwrap(promise);
ar.emplace(cx, promise);
if (!cx->compartment()->wrap(cx, &resolution)) {
return false;
}
}
if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
JS_ReportErrorASCII(
cx,
"async function/generator's promise shouldn't be manually resolved");
return false;
}
bool result = JS::ResolvePromise(cx, promise, resolution);
if (result) {
args.rval().setUndefined();
}
return result;
}
static bool RejectPromise(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "rejectPromise", 2)) {
return false;
}
if (!args[0].isObject() ||
!UncheckedUnwrap(&args[0].toObject())->is<PromiseObject>()) {
JS_ReportErrorASCII(
cx, "first argument must be a maybe-wrapped Promise object");
return false;
}
RootedObject promise(cx, &args[0].toObject());
RootedValue reason(cx, args[1]);
mozilla::Maybe<AutoRealm> ar;
if (IsWrapper(promise)) {
promise = UncheckedUnwrap(promise);
ar.emplace(cx, promise);
if (!cx->compartment()->wrap(cx, &reason)) {
return false;
}
}
if (IsPromiseForAsyncFunctionOrGenerator(promise)) {
JS_ReportErrorASCII(
cx,
"async function/generator's promise shouldn't be manually rejected");
return false;
}
bool result = JS::RejectPromise(cx, promise, reason);
if (result) {
args.rval().setUndefined();
}
return result;
}
static unsigned finalizeCount = 0;
static void finalize_counter_finalize(JS::GCContext* gcx, JSObject* obj) {
++finalizeCount;
}
static const JSClassOps FinalizeCounterClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
finalize_counter_finalize, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
static const JSClass FinalizeCounterClass = {
"FinalizeCounter",
JSCLASS_FOREGROUND_FINALIZE,
&FinalizeCounterClassOps,
};
static bool MakeFinalizeObserver(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSObject* obj =
JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static bool FinalizeCount(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setInt32(finalizeCount);
return true;
}
static bool ResetFinalizeCount(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
finalizeCount = 0;
args.rval().setUndefined();
return true;
}
static bool DumpHeap(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
FILE* dumpFile = stdout;
auto closeFile = mozilla::MakeScopeExit([&dumpFile] {
if (dumpFile && dumpFile != stdout) {
fclose(dumpFile);
}
});
if (args.length() > 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
if (!args.get(0).isUndefined()) {
RootedString str(cx, ToString(cx, args[0]));
if (!str) {
return false;
}
if (!fuzzingSafe) {
UniqueChars fileNameBytes = JS_EncodeStringToUTF8(cx, str);
if (!fileNameBytes) {
return false;
}
#ifdef XP_WIN
UniqueWideChars wideFileNameBytes =
JS::EncodeUtf8ToWide(cx, fileNameBytes.get());
if (!wideFileNameBytes) {
return false;
}
dumpFile = _wfopen(wideFileNameBytes.get(), L"w");
#else
UniqueChars narrowFileNameBytes =
JS::EncodeUtf8ToNarrow(cx, fileNameBytes.get());
if (!narrowFileNameBytes) {
return false;
}
dumpFile = fopen(narrowFileNameBytes.get(), "w");
#endif
if (!dumpFile) {
JS_ReportErrorUTF8(cx, "can't open %s", fileNameBytes.get());
return false;
}
}
}
js::DumpHeap(cx, dumpFile, js::IgnoreNurseryObjects);
args.rval().setUndefined();
return true;
}
static bool Terminate(JSContext* cx, unsigned arg, Value* vp) {
// Print a message to stderr in differential testing to help jsfunfuzz
// find uncatchable-exception bugs.
if (js::SupportDifferentialTesting()) {
fprintf(stderr, "terminate called\n");
}
JS::ReportUncatchableException(cx);
return false;
}
static bool ReadGeckoProfilingStack(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
// Return boolean 'false' if profiler is not enabled.
if (!cx->runtime()->geckoProfiler().enabled()) {
args.rval().setBoolean(false);
return true;
}
// Array holding physical jit stack frames.
RootedObject stack(cx, NewDenseEmptyArray(cx));
if (!stack) {
return false;
}
// If profiler sampling has been suppressed, return an empty
// stack.
if (!cx->isProfilerSamplingEnabled()) {
args.rval().setObject(*stack);
return true;
}
struct InlineFrameInfo {
InlineFrameInfo(const char* kind, UniqueChars label)
: kind(kind), label(std::move(label)) {}
const char* kind;
UniqueChars label;
};
Vector<Vector<InlineFrameInfo, 0, TempAllocPolicy>, 0, TempAllocPolicy>
frameInfo(cx);
JS::ProfilingFrameIterator::RegisterState state;
for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
MOZ_ASSERT(i.stackAddress() != nullptr);
if (!frameInfo.emplaceBack(cx)) {
return false;
}
const size_t MaxInlineFrames = 16;
JS::ProfilingFrameIterator::Frame frames[MaxInlineFrames];
uint32_t nframes = i.extractStack(frames, 0, MaxInlineFrames);
MOZ_ASSERT(nframes <= MaxInlineFrames);
for (uint32_t i = 0; i < nframes; i++) {
const char* frameKindStr = nullptr;
switch (frames[i].kind) {
case JS::ProfilingFrameIterator::Frame_BaselineInterpreter:
frameKindStr = "baseline-interpreter";
break;
case JS::ProfilingFrameIterator::Frame_Baseline:
frameKindStr = "baseline-jit";
break;
case JS::ProfilingFrameIterator::Frame_Ion:
frameKindStr = "ion";
break;
case JS::ProfilingFrameIterator::Frame_WasmBaseline:
case JS::ProfilingFrameIterator::Frame_WasmIon:
case JS::ProfilingFrameIterator::Frame_WasmOther:
frameKindStr = "wasm";
break;
default:
frameKindStr = "unknown";
}
UniqueChars label =
DuplicateStringToArena(js::StringBufferArena, cx, frames[i].label);
if (!label) {
return false;
}
if (!frameInfo.back().emplaceBack(frameKindStr, std::move(label))) {
return false;
}
}
}
RootedObject inlineFrameInfo(cx);
RootedString frameKind(cx);
RootedString frameLabel(cx);
RootedId idx(cx);
const unsigned propAttrs = JSPROP_ENUMERATE;
uint32_t physicalFrameNo = 0;
for (auto& frame : frameInfo) {
// Array holding all inline frames in a single physical jit stack frame.
RootedObject inlineStack(cx, NewDenseEmptyArray(cx));
if (!inlineStack) {
return false;
}
uint32_t inlineFrameNo = 0;
for (auto& inlineFrame : frame) {
// Object holding frame info.
RootedObject inlineFrameInfo(cx, NewPlainObject(cx));
if (!inlineFrameInfo) {
return false;
}
frameKind = NewStringCopyZ<CanGC>(cx, inlineFrame.kind);
if (!frameKind) {
return false;
}
if (!JS_DefineProperty(cx, inlineFrameInfo, "kind", frameKind,
propAttrs)) {
return false;
}
frameLabel = NewLatin1StringZ(cx, std::move(inlineFrame.label));
if (!frameLabel) {
return false;
}
if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel,
propAttrs)) {
return false;
}
idx = PropertyKey::Int(inlineFrameNo);
if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0)) {
return false;
}
++inlineFrameNo;
}
// Push inline array into main array.
idx = PropertyKey::Int(physicalFrameNo);
if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0)) {
return false;
}
++physicalFrameNo;
}
args.rval().setObject(*stack);
return true;
}
static bool ReadGeckoInterpProfilingStack(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
// Return boolean 'false' if profiler is not enabled.
if (!cx->runtime()->geckoProfiler().enabled()) {
args.rval().setBoolean(false);
return true;
}
// Array with information about each frame.
Rooted<JSObject*> stack(cx, NewDenseEmptyArray(cx));
if (!stack) {
return false;
}
uint32_t stackIndex = 0;
ProfilingStack* profStack = cx->geckoProfiler().getProfilingStack();
MOZ_ASSERT(profStack);
for (size_t i = 0; i < profStack->stackSize(); i++) {
const auto& frame = profStack->frames[i];
if (!frame.isJsFrame()) {
continue;
}
// Skip fake JS frame pushed for js::RunScript by GeckoProfilerEntryMarker.
const char* dynamicStr = frame.dynamicString();
if (!dynamicStr) {
continue;
}
Rooted<PlainObject*> frameInfo(cx, NewPlainObject(cx));
if (!frameInfo) {
return false;
}
Rooted<JSString*> dynamicString(
cx, JS_NewStringCopyUTF8Z(
cx, JS::ConstUTF8CharsZ(dynamicStr, strlen(dynamicStr))));
if (!dynamicString) {
return false;
}
if (!JS_DefineProperty(cx, frameInfo, "dynamicString", dynamicString,
JSPROP_ENUMERATE)) {
return false;
}
if (!JS_DefineElement(cx, stack, stackIndex, frameInfo, JSPROP_ENUMERATE)) {
return false;
}
stackIndex++;
}
args.rval().setObject(*stack);
return true;
}
static bool EnableOsiPointRegisterChecks(JSContext*, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef CHECK_OSIPOINT_REGISTERS
jit::JitOptions.checkOsiPointRegisters = true;
#endif
args.rval().setUndefined();
return true;
}
static bool DisplayName(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject() || !args[0].toObject().is<JSFunction>()) {
RootedObject arg(cx, &args.callee());
ReportUsageErrorASCII(cx, arg, "Must have one function argument");
return false;
}
JSFunction* fun = &args[0].toObject().as<JSFunction>();
JS::Rooted<JSAtom*> str(cx);
if (!fun->getDisplayAtom(cx, &str)) {
return false;
}
args.rval().setString(str ? str : cx->runtime()->emptyString.ref());
return true;
}
static bool IsAvxPresent(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
int minVersion = 1;
if (argc > 0 && args.get(0).isNumber()) {
minVersion = std::max(1, int(args[0].toNumber()));
}
switch (minVersion) {
case 1:
args.rval().setBoolean(jit::Assembler::HasAVX());
return true;
case 2:
args.rval().setBoolean(jit::Assembler::HasAVX2());
return true;
}
#endif
args.rval().setBoolean(false);
return true;
}
class ShellAllocationMetadataBuilder : public AllocationMetadataBuilder {
public:
ShellAllocationMetadataBuilder() = default;
virtual JSObject* build(JSContext* cx, HandleObject,
AutoEnterOOMUnsafeRegion& oomUnsafe) const override;
static const ShellAllocationMetadataBuilder metadataBuilder;
};
JSObject* ShellAllocationMetadataBuilder::build(
JSContext* cx, HandleObject, AutoEnterOOMUnsafeRegion& oomUnsafe) const {
RootedObject obj(cx, NewPlainObject(cx));
if (!obj) {
oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
}
RootedObject stack(cx, NewDenseEmptyArray(cx));
if (!stack) {
oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
}
static int createdIndex = 0;
createdIndex++;
if (!JS_DefineProperty(cx, obj, "index", createdIndex, 0)) {
oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
}
if (!JS_DefineProperty(cx, obj, "stack", stack, 0)) {
oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
}
int stackIndex = 0;
RootedId id(cx);
RootedValue callee(cx);
for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) {
if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) {
id = PropertyKey::Int(stackIndex);
RootedObject callee(cx, iter.callee(cx));
if (!JS_DefinePropertyById(cx, stack, id, callee, JSPROP_ENUMERATE)) {
oomUnsafe.crash("ShellAllocationMetadataBuilder::build");
}
stackIndex++;
}
}
return obj;
}
const ShellAllocationMetadataBuilder
ShellAllocationMetadataBuilder::metadataBuilder;
static bool EnableShellAllocationMetadataBuilder(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
SetAllocationMetadataBuilder(
cx, &ShellAllocationMetadataBuilder::metadataBuilder);
args.rval().setUndefined();
return true;
}
static bool GetAllocationMetadata(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isObject()) {
JS_ReportErrorASCII(cx, "Argument must be an object");
return false;
}
args.rval().setObjectOrNull(GetAllocationMetadata(&args[0].toObject()));
return true;
}
static bool testingFunc_bailout(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// NOP when not in IonMonkey
args.rval().setUndefined();
return true;
}
static bool testingFunc_bailAfter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
JS_ReportErrorASCII(
cx, "Argument must be a positive number that fits in an int32");
return false;
}
#ifdef DEBUG
if (auto* jitRuntime = cx->runtime()->jitRuntime()) {
uint32_t bailAfter = args[0].toInt32();
bool enableBailAfter = bailAfter > 0;
if (jitRuntime->ionBailAfterEnabled() != enableBailAfter) {
// Force JIT code to be recompiled with (or without) instrumentation.
ReleaseAllJITCode(cx->gcContext());
jitRuntime->setIonBailAfterEnabled(enableBailAfter);
}
jitRuntime->setIonBailAfterCounter(bailAfter);
}
#endif
args.rval().setUndefined();
return true;
}
static bool testingFunc_invalidate(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// If the topmost frame is Ion/Warp, find the IonScript and invalidate it.
FrameIter iter(cx);
if (!iter.done() && iter.isIon()) {
while (!iter.isPhysicalJitFrame()) {
++iter;
}
if (iter.script()->hasIonScript()) {
js::jit::Invalidate(cx, iter.script());
}
}
args.rval().setUndefined();
return true;
}
static constexpr unsigned JitWarmupResetLimit = 20;
static_assert(JitWarmupResetLimit <=
unsigned(JSScript::MutableFlags::WarmupResets_MASK),
"JitWarmupResetLimit exceeds max value");
static bool testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!jit::IsBaselineJitEnabled(cx)) {
return ReturnStringCopy(cx, args, "Baseline is disabled.");
}
// Use frame iterator to inspect caller.
FrameIter iter(cx);
// We may be invoked directly, not in a JS context, e.g. if inJit is added as
// a callback on the event queue.
if (iter.done()) {
args.rval().setBoolean(false);
return true;
}
if (iter.hasScript()) {
// Detect repeated attempts to compile, resetting the counter if inJit
// succeeds. Note: This script may have be inlined into its caller.
if (iter.isJSJit()) {
iter.script()->resetWarmUpResetCounter();
} else if (iter.script()->getWarmUpResetCount() >= JitWarmupResetLimit) {
return ReturnStringCopy(
cx, args, "Compilation is being repeatedly prevented. Giving up.");
}
}
// Returns true for any JIT (including WASM).
MOZ_ASSERT_IF(iter.isJSJit(), cx->currentlyRunningInJit());
args.rval().setBoolean(cx->currentlyRunningInJit());
return true;
}
static bool testingFunc_inIon(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!jit::IsIonEnabled(cx)) {
return ReturnStringCopy(cx, args, "Ion is disabled.");
}
// Use frame iterator to inspect caller.
FrameIter iter(cx);
// We may be invoked directly, not in a JS context, e.g. if inIon is added as
// a callback on the event queue.
if (iter.done()) {
args.rval().setBoolean(false);
return true;
}
if (iter.hasScript()) {
// Detect repeated attempts to compile, resetting the counter if inIon
// succeeds. Note: This script may have be inlined into its caller.
if (iter.isIon()) {
iter.script()->resetWarmUpResetCounter();
} else if (!iter.script()->canIonCompile()) {
return ReturnStringCopy(cx, args, "Unable to Ion-compile this script.");
} else if (iter.script()->getWarmUpResetCount() >= JitWarmupResetLimit) {
return ReturnStringCopy(
cx, args, "Compilation is being repeatedly prevented. Giving up.");
}
}
args.rval().setBoolean(iter.isIon());
return true;
}
bool js::testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2) {
JS_ReportErrorASCII(cx, "Expects only 2 arguments");
return false;
}
// NOP when not in IonMonkey
args.rval().setUndefined();
return true;
}
static bool TestingFunc_assertJitStackInvariants(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
jit::AssertJitStackInvariants(cx);
args.rval().setUndefined();
return true;
}
bool js::testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2) {
JS_ReportErrorASCII(cx, "Expects only 2 arguments");
return false;
}
// NOP when not in IonMonkey
args.rval().setUndefined();
return true;
}
static bool GetJitCompilerOptions(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return false;
}
uint32_t intValue = 0;
RootedValue value(cx);
#define JIT_COMPILER_MATCH(key, string) \
opt = JSJITCOMPILER_##key; \
if (JS_GetGlobalJitCompilerOption(cx, opt, &intValue)) { \
value.setInt32(intValue); \
if (!JS_SetProperty(cx, info, string, value)) return false; \
}
JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
#undef JIT_COMPILER_MATCH
args.rval().setObject(*info);
return true;
}
static bool SetIonCheckGraphCoherency(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
jit::JitOptions.checkGraphConsistency = ToBoolean(args.get(0));
args.rval().setUndefined();
return true;
}
// A JSObject that holds structured clone data, similar to the C++ class
// JSAutoStructuredCloneBuffer.
class CloneBufferObject : public NativeObject {
static const JSPropertySpec props_[3];
static const size_t DATA_SLOT = 0;
static const size_t SYNTHETIC_SLOT = 1;
static const size_t NUM_SLOTS = 2;
public:
static const JSClass class_;
static CloneBufferObject* Create(JSContext* cx) {
RootedObject obj(cx, JS_NewObject(cx, &class_));
if (!obj) {
return nullptr;
}
obj->as<CloneBufferObject>().setReservedSlot(DATA_SLOT,
PrivateValue(nullptr));
obj->as<CloneBufferObject>().setReservedSlot(SYNTHETIC_SLOT,
BooleanValue(false));
if (!JS_DefineProperties(cx, obj, props_)) {
return nullptr;
}
return &obj->as<CloneBufferObject>();
}
static CloneBufferObject* Create(JSContext* cx,
JSAutoStructuredCloneBuffer* buffer) {
Rooted<CloneBufferObject*> obj(cx, Create(cx));
if (!obj) {
return nullptr;
}
auto data = js::MakeUnique<JSStructuredCloneData>(buffer->scope());
if (!data) {
ReportOutOfMemory(cx);
return nullptr;
}
buffer->giveTo(data.get());
obj->setData(data.release(), false);
return obj;
}
JSStructuredCloneData* data() const {
return static_cast<JSStructuredCloneData*>(
getReservedSlot(DATA_SLOT).toPrivate());
}
bool isSynthetic() const {
return getReservedSlot(SYNTHETIC_SLOT).toBoolean();
}
void setData(JSStructuredCloneData* aData, bool synthetic) {
MOZ_ASSERT(!data());
setReservedSlot(DATA_SLOT, PrivateValue(aData));
setReservedSlot(SYNTHETIC_SLOT, BooleanValue(synthetic));
}
// Discard an owned clone buffer.
void discard() {
js_delete(data());
setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
}
static bool setCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
Rooted<CloneBufferObject*> obj(
cx, &args.thisv().toObject().as<CloneBufferObject>());
const char* data = nullptr;
UniqueChars dataOwner;
size_t nbytes;
if (args.get(0).isObject() && args[0].toObject().is<ArrayBufferObject>()) {
ArrayBufferObject* buffer = &args[0].toObject().as<ArrayBufferObject>();
bool isSharedMemory;
uint8_t* dataBytes = nullptr;
JS::GetArrayBufferLengthAndData(buffer, &nbytes, &isSharedMemory,
&dataBytes);
MOZ_ASSERT(!isSharedMemory);
data = reinterpret_cast<char*>(dataBytes);
} else {
JSString* str = JS::ToString(cx, args.get(0));
if (!str) {
return false;
}
dataOwner = JS_EncodeStringToLatin1(cx, str);
if (!dataOwner) {
return false;
}
data = dataOwner.get();
nbytes = JS_GetStringLength(str);
}
if (nbytes == 0 || (nbytes % sizeof(uint64_t) != 0)) {
JS_ReportErrorASCII(cx, "Invalid length for clonebuffer data");
return false;
}
auto buf = js::MakeUnique<JSStructuredCloneData>(
JS::StructuredCloneScope::DifferentProcess);
if (!buf || !buf->Init(nbytes)) {
ReportOutOfMemory(cx);
return false;
}
MOZ_ALWAYS_TRUE(buf->AppendBytes(data, nbytes));
obj->discard();
obj->setData(buf.release(), true);
args.rval().setUndefined();
return true;
}
static bool is(HandleValue v) {
return v.isObject() && v.toObject().is<CloneBufferObject>();
}
static bool setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<is, setCloneBuffer_impl>(cx, args);
}
static bool getData(JSContext* cx, Handle<CloneBufferObject*> obj,
JSStructuredCloneData** data) {
if (!obj->data()) {
*data = nullptr;
return true;
}
bool hasTransferable;
if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable)) {
return false;
}
if (hasTransferable) {
JS_ReportErrorASCII(
cx, "cannot retrieve structured clone buffer with transferables");
return false;
}
*data = obj->data();
return true;
}
static bool getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
Rooted<CloneBufferObject*> obj(
cx, &args.thisv().toObject().as<CloneBufferObject>());
if (args.length() != 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
JSStructuredCloneData* data;
if (!getData(cx, obj, &data)) {
return false;
}
if (data == nullptr) {
args.rval().setUndefined();
return true;
}
size_t size = data->Size();
UniqueChars buffer(js_pod_malloc<char>(size));
if (!buffer) {
ReportOutOfMemory(cx);
return false;
}
auto iter = data->Start();
if (!data->ReadBytes(iter, buffer.get(), size)) {
ReportOutOfMemory(cx);
return false;
}
JSString* str = JS_NewStringCopyN(cx, buffer.get(), size);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool getCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args);
}
static bool getCloneBufferAsArrayBuffer_impl(JSContext* cx,
const CallArgs& args) {
Rooted<CloneBufferObject*> obj(
cx, &args.thisv().toObject().as<CloneBufferObject>());
if (args.length() != 0) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
JSStructuredCloneData* data;
if (!getData(cx, obj, &data)) {
return false;
}
if (data == nullptr) {
args.rval().setUndefined();
return true;
}
size_t size = data->Size();
UniqueChars buffer(js_pod_malloc<char>(size));
if (!buffer) {
ReportOutOfMemory(cx);
return false;
}
auto iter = data->Start();
if (!data->ReadBytes(iter, buffer.get(), size)) {
ReportOutOfMemory(cx);
return false;
}
JSObject* arrayBuffer =
JS::NewArrayBufferWithContents(cx, size, std::move(buffer));
if (!arrayBuffer) {
return false;
}
args.rval().setObject(*arrayBuffer);
return true;
}
static bool getCloneBufferAsArrayBuffer(JSContext* cx, unsigned int argc,
JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<is, getCloneBufferAsArrayBuffer_impl>(cx, args);
}
static void Finalize(JS::GCContext* gcx, JSObject* obj) {
obj->as<CloneBufferObject>().discard();
}
};
static const JSClassOps CloneBufferObjectClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
CloneBufferObject::Finalize, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
const JSClass CloneBufferObject::class_ = {
"CloneBuffer",
JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&CloneBufferObjectClassOps,
};
const JSPropertySpec CloneBufferObject::props_[] = {
JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
JS_PSGS("arraybuffer", getCloneBufferAsArrayBuffer, setCloneBuffer, 0),
JS_PS_END,
};
static mozilla::Maybe<JS::StructuredCloneScope> ParseCloneScope(
JSContext* cx, HandleString str) {
mozilla::Maybe<JS::StructuredCloneScope> scope;
JSLinearString* scopeStr = str->ensureLinear(cx);
if (!scopeStr) {
return scope;
}
if (StringEqualsLiteral(scopeStr, "SameProcess")) {
scope.emplace(JS::StructuredCloneScope::SameProcess);
} else if (StringEqualsLiteral(scopeStr, "DifferentProcess")) {
scope.emplace(JS::StructuredCloneScope::DifferentProcess);
} else if (StringEqualsLiteral(scopeStr, "DifferentProcessForIndexedDB")) {
scope.emplace(JS::StructuredCloneScope::DifferentProcessForIndexedDB);
}
return scope;
}
// A custom object that is serializable and transferable using
// the engine's custom hooks. The callbacks log their activity
// to a JSRuntime-wide log (tagging actions with IDs to distinguish them).
class CustomSerializableObject : public NativeObject {
static const size_t ID_SLOT = 0;
static const size_t DETACHED_SLOT = 1;
static const size_t BEHAVIOR_SLOT = 2;
static const size_t NUM_SLOTS = 3;
static constexpr size_t MAX_LOG_LEN = 100;
// The activity log should be specific to a JSRuntime.
struct ActivityLog {
uint32_t buffer[MAX_LOG_LEN];
size_t length = 0;
static MOZ_THREAD_LOCAL(ActivityLog*) self;
static ActivityLog* getThreadLog() {
if (!self.initialized() || !self.get()) {
self.infallibleInit();
AutoEnterOOMUnsafeRegion oomUnsafe;
self.set(js_new<ActivityLog>());
if (!self.get()) {
oomUnsafe.crash("allocating activity log");
}
if (!TlsContext.get()->runtime()->atExit(
[](void* vpData) {
auto* log = static_cast<ActivityLog*>(vpData);
js_delete(log);
},
self.get())) {
oomUnsafe.crash("atExit");
}
}
return self.get();
}
static bool log(int32_t id, char action) {
return getThreadLog()->logImpl(id, action);
}
bool logImpl(int32_t id, char action) {
if (length + 2 > MAX_LOG_LEN) {
return false;
}
buffer[length++] = id;
buffer[length++] = uint32_t(action);
return true;
}
};
public:
enum class Behavior {
Nothing = 0,
FailDuringReadTransfer = 1,
FailDuringRead = 2
};
static constexpr JSClass class_ = {
"CustomSerializable",
JSCLASS_HAS_RESERVED_SLOTS(NUM_SLOTS),
};
static bool is(HandleValue v) {
return v.isObject() && v.toObject().is<CustomSerializableObject>();
}
static CustomSerializableObject* Create(JSContext* cx, int32_t id,
Behavior behavior) {
Rooted<CustomSerializableObject*> obj(
cx, static_cast<CustomSerializableObject*>(JS_NewObject(cx, &class_)));
if (!obj) {
return nullptr;
}
obj->setReservedSlot(ID_SLOT, Int32Value(id));
obj->setReservedSlot(DETACHED_SLOT, BooleanValue(false));
obj->setReservedSlot(BEHAVIOR_SLOT,
Int32Value(static_cast<int32_t>(behavior)));
if (!JS_DefineProperty(cx, obj, "log", getLog, clearLog, 0)) {
return nullptr;
}
return obj;
}
public:
static uint32_t tag() { return JS_SCTAG_USER_MIN; }
static bool log(int32_t id, char action) {
return ActivityLog::log(id, action);
}
bool log(char action) {
return log(getReservedSlot(ID_SLOT).toInt32(), action);
}
void detach() { setReservedSlot(DETACHED_SLOT, BooleanValue(true)); }
bool isDetached() { return getReservedSlot(DETACHED_SLOT).toBoolean(); }
uint32_t id() const { return getReservedSlot(ID_SLOT).toInt32(); }
Behavior behavior() {
return static_cast<Behavior>(getReservedSlot(BEHAVIOR_SLOT).toInt32());
}
static bool getLog(JSContext* cx, unsigned int argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<is, getLog_impl>(cx, args);
}
static bool getLog_impl(JSContext* cx, const CallArgs& args) {
Rooted<CustomSerializableObject*> obj(
cx, &args.thisv().toObject().as<CustomSerializableObject>());
size_t len = ActivityLog::getThreadLog()->length;
uint32_t* logBuffer = ActivityLog::getThreadLog()->buffer;
Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, len));
if (!result) {
return false;
}
result->ensureDenseInitializedLength(0, len);
for (size_t p = 0; p < len; p += 2) {
int32_t id = int32_t(logBuffer[p]);
char action = char(logBuffer[p + 1]);
result->setDenseElement(p, Int32Value(id));
JSString* str = JS_NewStringCopyN(cx, &action, 1);
if (!str) {
return false;
}
result->setDenseElement(p + 1, StringValue(str));
}
args.rval().setObject(*result);
return true;
}
static bool clearLog(JSContext* cx, unsigned int argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isNullOrUndefined()) {
JS_ReportErrorASCII(cx, "log may only be assigned null/undefined");
return false;
}
ActivityLog::getThreadLog()->length = 0;
args.rval().setUndefined();
return true;
}
static bool Write(JSContext* cx, JSStructuredCloneWriter* w,
JS::HandleObject aObj, bool* sameProcessScopeRequired,
void* closure) {
Rooted<CustomSerializableObject*> obj(cx);
if ((obj = aObj->maybeUnwrapIf<CustomSerializableObject>())) {
obj->log('w');
// Write a regular clone as a <tag, id> pair, followed by <0, behavior>.
// Note that transferring will communicate the behavior via a different
// mechanism.
return JS_WriteUint32Pair(w, obj->tag(), obj->id()) &&
JS_WriteUint32Pair(w, 0, static_cast<uint32_t>(obj->behavior()));
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_UNSUPPORTED_TYPE);
return false;
}
static JSObject* Read(JSContext* cx, JSStructuredCloneReader* r,
const JS::CloneDataPolicy& cloneDataPolicy,
uint32_t tag, uint32_t id, void* closure) {
uint32_t dummy, behaviorData;
if (!JS_ReadUint32Pair(r, &dummy, &behaviorData)) {
return nullptr;
}
if (dummy != 0 || id > INT32_MAX) {
JS_ReportErrorASCII(cx, "out of range");
return nullptr;
}
auto b = static_cast<Behavior>(behaviorData);
Rooted<CustomSerializableObject*> obj(
cx, Create(cx, static_cast<int32_t>(id), b));
if (!obj) {
return nullptr;
}
obj->log('r');
if (obj->behavior() == Behavior::FailDuringRead) {
JS_ReportErrorASCII(cx,
"Failed as requested in read during deserialization");
return nullptr;
}
return obj;
}
static bool CanTransfer(JSContext* cx, JS::Handle<JSObject*> wrapped,
bool* sameProcessScopeRequired, void* closure) {
Rooted<CustomSerializableObject*> obj(cx);
if ((obj = wrapped->maybeUnwrapIf<CustomSerializableObject>())) {
obj->log('?');
// For now, all CustomSerializable objects are considered to be
// transferable.
return true;
}
return false;
}
static bool WriteTransfer(JSContext* cx, JS::Handle<JSObject*> aObj,
void* closure, uint32_t* tag,
JS::TransferableOwnership* ownership,
void** content, uint64_t* extraData) {
Rooted<CustomSerializableObject*> obj(cx);
if ((obj = aObj->maybeUnwrapIf<CustomSerializableObject>())) {
if (obj->isDetached()) {
JS_ReportErrorASCII(cx, "Attempted to transfer detached object");
return false;
}
obj->log('W');
*content = reinterpret_cast<void*>(obj->id());
*extraData = static_cast<uint64_t>(obj->behavior());
*tag = obj->tag();
*ownership = JS::SCTAG_TMO_CUSTOM;
obj->detach();
return true;
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SC_NOT_TRANSFERABLE);
return false;
}
static bool ReadTransfer(JSContext* cx, JSStructuredCloneReader* r,
const JS::CloneDataPolicy& cloneDataPolicy,
uint32_t tag, void* content, uint64_t extraData,
void* closure,
JS::MutableHandleObject returnObject) {
if (tag == CustomSerializableObject::tag()) {
int32_t id = int32_t(reinterpret_cast<uintptr_t>(content));
Rooted<CustomSerializableObject*> obj(
cx, CustomSerializableObject::Create(
cx, id, static_cast<Behavior>(extraData)));
if (!obj) {
return false;
}
obj->log('R');
if (obj->behavior() == Behavior::FailDuringReadTransfer) {
return false;
}
returnObject.set(obj);
return true;
}
return false;
}
static void FreeTransfer(uint32_t tag, JS::TransferableOwnership ownership,
void* content, uint64_t extraData, void* closure) {
CustomSerializableObject::log(uint32_t(reinterpret_cast<intptr_t>(content)),
'F');
}
};
MOZ_THREAD_LOCAL(CustomSerializableObject::ActivityLog*)
CustomSerializableObject::ActivityLog::self;
static bool MakeSerializable(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
int32_t id = 0;
if (args.get(0).isInt32()) {
id = args[0].toInt32();
if (id < 0) {
JS_ReportErrorASCII(cx, "id out of range");
return false;
}
}
CustomSerializableObject::Behavior behavior =
CustomSerializableObject::Behavior::Nothing;
if (args.get(1).isInt32()) {
int32_t iv = args[1].toInt32();
constexpr int32_t min =
static_cast<int32_t>(CustomSerializableObject::Behavior::Nothing);
constexpr int32_t max = static_cast<int32_t>(
CustomSerializableObject::Behavior::FailDuringRead);
if (iv < min || iv > max) {
JS_ReportErrorASCII(cx, "behavior out of range");
return false;
}
behavior = static_cast<CustomSerializableObject::Behavior>(iv);
}
JSObject* obj = CustomSerializableObject::Create(cx, id, behavior);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static JSStructuredCloneCallbacks gCloneCallbacks = {
.read = CustomSerializableObject::Read,
.write = CustomSerializableObject::Write,
.reportError = nullptr,
.readTransfer = CustomSerializableObject::ReadTransfer,
.writeTransfer = CustomSerializableObject::WriteTransfer,
.freeTransfer = CustomSerializableObject::FreeTransfer,
.canTransfer = CustomSerializableObject::CanTransfer,
.sabCloned = nullptr};
bool js::testingFunc_serialize(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Function unavailable in differential testing mode.");
return false;
}
mozilla::Maybe<JSAutoStructuredCloneBuffer> clonebuf;
JS::CloneDataPolicy policy;
if (!args.get(2).isUndefined()) {
RootedObject opts(cx, ToObject(cx, args.get(2)));
if (!opts) {
return false;
}
RootedValue v(cx);
if (!JS_GetProperty(cx, opts, "SharedArrayBuffer", &v)) {
return false;
}
if (!v.isUndefined()) {
JSString* str = JS::ToString(cx, v);
if (!str) {
return false;
}
JSLinearString* poli = str->ensureLinear(cx);
if (!poli) {
return false;
}
if (StringEqualsLiteral(poli, "allow")) {
policy.allowSharedMemoryObjects();
policy.allowIntraClusterClonableSharedObjects();
} else if (StringEqualsLiteral(poli, "deny")) {
// default
} else {
JS_ReportErrorASCII(cx, "Invalid policy value for 'SharedArrayBuffer'");
return false;
}
}
if (!JS_GetProperty(cx, opts, "scope", &v)) {
return false;
}
if (!v.isUndefined()) {
RootedString str(cx, JS::ToString(cx, v));
if (!str) {
return false;
}
auto scope = ParseCloneScope(cx, str);
if (!scope) {
JS_ReportErrorASCII(cx, "Invalid structured clone scope");
return false;
}
clonebuf.emplace(*scope, &gCloneCallbacks, nullptr);
}
}
if (!clonebuf) {
clonebuf.emplace(JS::StructuredCloneScope::SameProcess, &gCloneCallbacks,
nullptr);
}
if (!clonebuf->write(cx, args.get(0), args.get(1), policy)) {
return false;
}
RootedObject obj(cx, CloneBufferObject::Create(cx, clonebuf.ptr()));
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
static bool Deserialize(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Function unavailable in differential testing mode.");
return false;
}
if (!args.get(0).isObject() || !args[0].toObject().is<CloneBufferObject>()) {
JS_ReportErrorASCII(cx, "deserialize requires a clonebuffer argument");
return false;
}
Rooted<CloneBufferObject*> obj(cx,
&args[0].toObject().as<CloneBufferObject>());
JS::CloneDataPolicy policy;
JS::StructuredCloneScope scope =
obj->isSynthetic() ? JS::StructuredCloneScope::DifferentProcess
: JS::StructuredCloneScope::SameProcess;
if (args.get(1).isObject()) {
RootedObject opts(cx, &args[1].toObject());
if (!opts) {
return false;
}
RootedValue v(cx);
if (!JS_GetProperty(cx, opts, "SharedArrayBuffer", &v)) {
return false;
}
if (!v.isUndefined()) {
JSString* str = JS::ToString(cx, v);
if (!str) {
return false;
}
JSLinearString* poli = str->ensureLinear(cx);
if (!poli) {
return false;
}
if (StringEqualsLiteral(poli, "allow")) {
policy.allowSharedMemoryObjects();
policy.allowIntraClusterClonableSharedObjects();
} else if (StringEqualsLiteral(poli, "deny")) {
// default
} else {
JS_ReportErrorASCII(cx, "Invalid policy value for 'SharedArrayBuffer'");
return false;
}
}
if (!JS_GetProperty(cx, opts, "scope", &v)) {
return false;
}
if (!v.isUndefined()) {
RootedString str(cx, JS::ToString(cx, v));
if (!str) {
return false;
}
auto maybeScope = ParseCloneScope(cx, str);
if (!maybeScope) {
JS_ReportErrorASCII(cx, "Invalid structured clone scope");
return false;
}
if (*maybeScope < scope) {
JS_ReportErrorASCII(cx,
"Cannot use less restrictive scope "
"than the deserialized clone buffer's scope");
return false;
}
scope = *maybeScope;
}
}
// Clone buffer was already consumed?
if (!obj->data()) {
JS_ReportErrorASCII(cx,
"deserialize given invalid clone buffer "
"(transferables already consumed?)");
return false;
}
bool hasTransferable;
if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable)) {
return false;
}
RootedValue deserialized(cx);
if (!JS_ReadStructuredClone(cx, *obj->data(), JS_STRUCTURED_CLONE_VERSION,
scope, &deserialized, policy, &gCloneCallbacks,
nullptr)) {
return false;
}
args.rval().set(deserialized);
// Consume any clone buffer with transferables; throw an error if it is
// deserialized again.
if (hasTransferable) {
obj->discard();
}
return true;
}
static bool DetachArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "detachArrayBuffer() requires a single argument");
return false;
}
if (!args[0].isObject()) {
JS_ReportErrorASCII(cx, "detachArrayBuffer must be passed an object");
return false;
}
RootedObject obj(cx, &args[0].toObject());
if (!JS::DetachArrayBuffer(cx, obj)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool EnsureNonInline(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<JSObject*> callee(cx, &args.callee());
if (!args.get(0).isObject()) {
js::ReportUsageErrorASCII(cx, callee, "Single object argument required");
return false;
}
RootedObject obj(cx, &args[0].toObject());
if (!JS::EnsureNonInlineArrayBufferOrView(cx, obj)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool PinArrayBufferOrViewLength(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<JSObject*> callee(cx, &args.callee());
if (!args.get(0).isObject()) {
js::ReportUsageErrorASCII(
cx, callee, "ArrayBuffer or ArrayBufferView argument required");
return false;
}
RootedObject obj(cx, &args[0].toObject());
if (!obj->canUnwrapAs<ArrayBufferViewObject>() &&
!obj->canUnwrapAs<ArrayBufferObjectMaybeShared>()) {
js::ReportUsageErrorASCII(
cx, callee, "ArrayBuffer or ArrayBufferView argument required");
return false;
}
bool pin = args.get(1).isUndefined() ? true : ToBoolean(args.get(1));
args.rval().setBoolean(JS::PinArrayBufferOrViewLength(obj, pin));
return true;
}
static bool JSONStringify(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue value(cx, args.get(0));
RootedValue behaviorVal(cx, args.get(1));
StringifyBehavior behavior = StringifyBehavior::Normal;
if (behaviorVal.isString()) {
bool matches;
#define MATCH(name) \
if (!JS_StringEqualsLiteral(cx, behaviorVal.toString(), #name, &matches)) { \
return false; \
} \
if (matches) { \
behavior = StringifyBehavior::name; \
}
MATCH(Normal)
MATCH(FastOnly)
MATCH(SlowOnly)
MATCH(Compare)
#undef MATCH
}
JSStringBuilder sb(cx);
if (!Stringify(cx, &value, nullptr, UndefinedValue(), sb, behavior)) {
return false;
}
if (!sb.empty()) {
JSString* str = sb.finishString();
if (!str) {
return false;
}
args.rval().setString(str);
} else {
args.rval().setUndefined();
}
return true;
}
static bool HelperThreadCount(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
// Always return 0 to get consistent output with and without --no-threads.
args.rval().setInt32(0);
return true;
}
if (CanUseExtraThreads()) {
args.rval().setInt32(GetHelperThreadCount());
} else {
args.rval().setInt32(0);
}
return true;
}
static bool EnableShapeConsistencyChecks(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef DEBUG
NativeObject::enableShapeConsistencyChecks();
#endif
args.rval().setUndefined();
return true;
}
// ShapeSnapshot holds information about an object's properties. This is used
// for checking object and shape changes between two points in time.
class ShapeSnapshot {
HeapPtr<JSObject*> object_;
HeapPtr<Shape*> shape_;
HeapPtr<BaseShape*> baseShape_;
ObjectFlags objectFlags_;
GCVector<HeapPtr<Value>, 8> slots_;
struct PropertySnapshot {
HeapPtr<PropMap*> propMap;
uint32_t propMapIndex;
HeapPtr<PropertyKey> key;
PropertyInfo prop;
explicit PropertySnapshot(PropMap* map, uint32_t index)
: propMap(map),
propMapIndex(index),
key(map->getKey(index)),
prop(map->getPropertyInfo(index)) {}
void trace(JSTracer* trc) {
TraceEdge(trc, &propMap, "propMap");
TraceEdge(trc, &key, "key");
}
bool operator==(const PropertySnapshot& other) const {
return propMap == other.propMap && propMapIndex == other.propMapIndex &&
key == other.key && prop == other.prop;
}
bool operator!=(const PropertySnapshot& other) const {
return !operator==(other);
}
};
GCVector<PropertySnapshot, 8> properties_;
public:
explicit ShapeSnapshot(JSContext* cx) : slots_(cx), properties_(cx) {}
void checkSelf(JSContext* cx) const;
void check(JSContext* cx, const ShapeSnapshot& other) const;
bool init(JSObject* obj);
void trace(JSTracer* trc);
JSObject* object() const { return object_; }
};
// A JSObject that holds a ShapeSnapshot.
class ShapeSnapshotObject : public NativeObject {
static constexpr size_t SnapshotSlot = 0;
static constexpr size_t ReservedSlots = 1;
public:
static const JSClassOps classOps_;
static const JSClass class_;
bool hasSnapshot() const {
// The snapshot may not be present yet if we GC during initialization.
return !getReservedSlot(SnapshotSlot).isUndefined();
}
ShapeSnapshot& snapshot() const {
void* ptr = getReservedSlot(SnapshotSlot).toPrivate();
MOZ_ASSERT(ptr);
return *static_cast<ShapeSnapshot*>(ptr);
}
static ShapeSnapshotObject* create(JSContext* cx, HandleObject obj);
static void finalize(JS::GCContext* gcx, JSObject* obj) {
if (obj->as<ShapeSnapshotObject>().hasSnapshot()) {
js_delete(&obj->as<ShapeSnapshotObject>().snapshot());
}
}
static void trace(JSTracer* trc, JSObject* obj) {
if (obj->as<ShapeSnapshotObject>().hasSnapshot()) {
obj->as<ShapeSnapshotObject>().snapshot().trace(trc);
}
}
};
/*static */ const JSClassOps ShapeSnapshotObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
ShapeSnapshotObject::finalize, // finalize
nullptr, // call
nullptr, // construct
ShapeSnapshotObject::trace, // trace
};
/*static */ const JSClass ShapeSnapshotObject::class_ = {
"ShapeSnapshotObject",
JSCLASS_HAS_RESERVED_SLOTS(ShapeSnapshotObject::ReservedSlots) |
JSCLASS_BACKGROUND_FINALIZE,
&ShapeSnapshotObject::classOps_,
};
bool ShapeSnapshot::init(JSObject* obj) {
object_ = obj;
shape_ = obj->shape();
baseShape_ = shape_->base();
objectFlags_ = shape_->objectFlags();
if (obj->is<NativeObject>()) {
NativeObject* nobj = &obj->as<NativeObject>();
// Snapshot the slot values.
size_t slotSpan = nobj->slotSpan();
if (!slots_.growBy(slotSpan)) {
return false;
}
for (size_t i = 0; i < slotSpan; i++) {
slots_[i] = nobj->getSlot(i);
}
// Snapshot property information.
if (uint32_t len = nobj->shape()->propMapLength(); len > 0) {
PropMap* map = nobj->shape()->propMap();
while (true) {
for (uint32_t i = 0; i < len; i++) {
if (!map->hasKey(i)) {
continue;
}
if (!properties_.append(PropertySnapshot(map, i))) {
return false;
}
}
if (!map->hasPrevious()) {
break;
}
map = map->asLinked()->previous();
len = PropMap::Capacity;
}
}
}
return true;
}
void ShapeSnapshot::trace(JSTracer* trc) {
TraceEdge(trc, &object_, "object");
TraceEdge(trc, &shape_, "shape");
TraceEdge(trc, &baseShape_, "baseShape");
slots_.trace(trc);
properties_.trace(trc);
}
void ShapeSnapshot::checkSelf(JSContext* cx) const {
// Assertions based on a single snapshot.
// Non-dictionary shapes must not be mutated.
if (!shape_->isDictionary()) {
MOZ_RELEASE_ASSERT(shape_->base() == baseShape_);
MOZ_RELEASE_ASSERT(shape_->objectFlags() == objectFlags_);
}
for (const PropertySnapshot& propSnapshot : properties_) {
PropMap* propMap = propSnapshot.propMap;
uint32_t propMapIndex = propSnapshot.propMapIndex;
PropertyInfo prop = propSnapshot.prop;
// Skip if the map no longer matches the snapshotted data. This can
// only happen for dictionary maps because they can be mutated or compacted
// after a shape change.
if (!propMap->hasKey(propMapIndex) ||
PropertySnapshot(propMap, propMapIndex) != propSnapshot) {
MOZ_RELEASE_ASSERT(propMap->isDictionary());
MOZ_RELEASE_ASSERT(object_->shape() != shape_);
continue;
}
// Ensure ObjectFlags depending on property information are set if needed.
ObjectFlags expectedFlags = GetObjectFlagsForNewProperty(
shape_->getObjectClass(), shape_->objectFlags(), propSnapshot.key,
prop.flags(), cx);
MOZ_RELEASE_ASSERT(expectedFlags == objectFlags_);
// Accessors must have a PrivateGCThingValue(GetterSetter*) slot value.
if (prop.isAccessorProperty()) {
Value slotVal = slots_[prop.slot()];
MOZ_RELEASE_ASSERT(slotVal.isPrivateGCThing());
MOZ_RELEASE_ASSERT(slotVal.toGCThing()->is<GetterSetter>());
}
// Data properties must not have a PrivateGCThingValue slot value.
if (prop.isDataProperty()) {
Value slotVal = slots_[prop.slot()];
MOZ_RELEASE_ASSERT(!slotVal.isPrivateGCThing());
}
}
}
void ShapeSnapshot::check(JSContext* cx, const ShapeSnapshot& later) const {
checkSelf(cx);
later.checkSelf(cx);
if (object_ != later.object_) {
// Snapshots are for different objects. Assert dictionary shapes aren't
// shared.
if (object_->is<NativeObject>()) {
NativeObject* nobj = &object_->as<NativeObject>();
if (nobj->inDictionaryMode()) {
MOZ_RELEASE_ASSERT(nobj->shape() != later.shape_);
}
}
return;
}
// We have two snapshots for the same object. Check the shape information
// wasn't changed in invalid ways.
// If the Shape is still the same, the object must have the same BaseShape,
// ObjectFlags and property information.
if (shape_ == later.shape_) {
MOZ_RELEASE_ASSERT(objectFlags_ == later.objectFlags_);
MOZ_RELEASE_ASSERT(baseShape_ == later.baseShape_);
MOZ_RELEASE_ASSERT(slots_.length() == later.slots_.length());
MOZ_RELEASE_ASSERT(properties_.length() == later.properties_.length());
for (size_t i = 0; i < properties_.length(); i++) {
MOZ_RELEASE_ASSERT(properties_[i] == later.properties_[i]);
// Non-configurable accessor properties and non-configurable, non-writable
// data properties shouldn't have had their slot mutated.
PropertyInfo prop = properties_[i].prop;
if (!prop.configurable()) {
if (prop.isAccessorProperty() ||
(prop.isDataProperty() && !prop.writable())) {
size_t slot = prop.slot();
MOZ_RELEASE_ASSERT(slots_[slot] == later.slots_[slot]);
}
}
}
}
// Object flags should not be lost. The exception is the Indexed flag, it
// can be cleared when densifying elements, so clear that flag first.
{
ObjectFlags flags = objectFlags_;
ObjectFlags flagsLater = later.objectFlags_;
flags.clearFlag(ObjectFlag::Indexed);
flagsLater.clearFlag(ObjectFlag::Indexed);
MOZ_RELEASE_ASSERT((flags.toRaw() & flagsLater.toRaw()) == flags.toRaw());
}
// If the HadGetterSetterChange flag wasn't set, all GetterSetter slots must
// be unchanged.
if (!later.objectFlags_.hasFlag(ObjectFlag::HadGetterSetterChange)) {
for (size_t i = 0; i < slots_.length(); i++) {
if (slots_[i].isPrivateGCThing() &&
slots_[i].toGCThing()->is<GetterSetter>()) {
MOZ_RELEASE_ASSERT(i < later.slots_.length());
MOZ_RELEASE_ASSERT(later.slots_[i] == slots_[i]);
}
}
}
}
// static
ShapeSnapshotObject* ShapeSnapshotObject::create(JSContext* cx,
HandleObject obj) {
Rooted<UniquePtr<ShapeSnapshot>> snapshot(cx,
cx->make_unique<ShapeSnapshot>(cx));
if (!snapshot || !snapshot->init(obj)) {
return nullptr;
}
auto* snapshotObj = NewObjectWithGivenProto<ShapeSnapshotObject>(cx, nullptr);
if (!snapshotObj) {
return nullptr;
}
snapshotObj->initReservedSlot(SnapshotSlot, PrivateValue(snapshot.release()));
return snapshotObj;
}
static bool CreateShapeSnapshot(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "createShapeSnapshot requires an object argument");
return false;
}
RootedObject obj(cx, &args[0].toObject());
auto* res = ShapeSnapshotObject::create(cx, obj);
if (!res) {
return false;
}
res->snapshot().check(cx, res->snapshot());
args.rval().setObject(*res);
return true;
}
static bool CheckShapeSnapshot(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject() ||
!args[0].toObject().is<ShapeSnapshotObject>()) {
JS_ReportErrorASCII(cx, "checkShapeSnapshot requires a snapshot argument");
return false;
}
// Get the object to use from the snapshot if the second argument is not an
// object.
RootedObject obj(cx);
if (args.get(1).isObject()) {
obj = &args[1].toObject();
} else {
auto& snapshot = args[0].toObject().as<ShapeSnapshotObject>().snapshot();
obj = snapshot.object();
}
RootedObject otherSnapshot(cx, ShapeSnapshotObject::create(cx, obj));
if (!otherSnapshot) {
return false;
}
auto& snapshot1 = args[0].toObject().as<ShapeSnapshotObject>().snapshot();
auto& snapshot2 = otherSnapshot->as<ShapeSnapshotObject>().snapshot();
snapshot1.check(cx, snapshot2);
args.rval().setUndefined();
return true;
}
#if defined(DEBUG) || defined(JS_JITSPEW)
static bool DumpObject(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx, ToObject(cx, args.get(0)));
if (!obj) {
return false;
}
DumpObject(obj);
args.rval().setUndefined();
return true;
}
static bool DumpValue(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.get(0).get().dump();
args.rval().setUndefined();
return true;
}
static bool DumpValueToString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSSprinter out(cx);
if (!out.init()) {
return false;
}
args.get(0).get().dump(out);
JSString* rep = out.release(cx);
if (!rep) {
return false;
}
args.rval().setString(rep);
return true;
}
#endif
static bool SharedMemoryEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(
cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
return true;
}
static bool SharedArrayRawBufferRefcount(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isObject()) {
JS_ReportErrorASCII(cx, "Expected SharedArrayBuffer object");
return false;
}
RootedObject obj(cx, &args[0].toObject());
if (!obj->is<SharedArrayBufferObject>()) {
JS_ReportErrorASCII(cx, "Expected SharedArrayBuffer object");
return false;
}
args.rval().setInt32(
obj->as<SharedArrayBufferObject>().rawBufferObject()->refcount());
return true;
}
#ifdef NIGHTLY_BUILD
static bool ObjectAddress(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Function unavailable in differential testing mode.");
return false;
}
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
if (!args[0].isObject()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected object");
return false;
}
void* ptr = js::UncheckedUnwrap(&args[0].toObject(), true);
char buffer[64];
SprintfLiteral(buffer, "%p", ptr);
return ReturnStringCopy(cx, args, buffer);
}
static bool ScriptAddressForFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Function unavailable in differential testing mode.");
return false;
}
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected function");
return false;
}
RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
if (!function->hasBytecode()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected non-lazy scripted function");
return false;
}
void* ptr = function->nonLazyScript();
args.rval().setPrivate(ptr);
return true;
}
static bool SharedAddress(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (js::SupportDifferentialTesting()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"Function unavailable in differential testing mode.");
return false;
}
if (args.length() != 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
return false;
}
if (!args[0].isObject()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected object");
return false;
}
RootedObject obj(cx, CheckedUnwrapStatic(&args[0].toObject()));
if (!obj) {
ReportAccessDenied(cx);
return false;
}
if (!obj->is<SharedArrayBufferObject>()) {
JS_ReportErrorASCII(cx, "Argument must be a SharedArrayBuffer");
return false;
}
char buffer[64];
uint32_t nchar = SprintfLiteral(
buffer, "%p",
obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(
/*safeish*/));
JSString* str = JS_NewStringCopyN(cx, buffer, nchar);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
#endif
static bool HasInvalidatedTeleporting(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isObject()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Expected single object argument");
return false;
}
args.rval().setBoolean(args[0].toObject().hasInvalidatedTeleporting());
return true;
}
static bool DumpBacktrace(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
DumpBacktrace(cx);
args.rval().setUndefined();
return true;
}
static bool GetBacktrace(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool showArgs = false;
bool showLocals = false;
bool showThisProps = false;
if (args.length() > 1) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee, "Too many arguments");
return false;
}
if (args.length() == 1) {
RootedObject cfg(cx, ToObject(cx, args[0]));
if (!cfg) {
return false;
}
RootedValue v(cx);
if (!JS_GetProperty(cx, cfg, "args", &v)) {
return false;
}
showArgs = ToBoolean(v);
if (!JS_GetProperty(cx, cfg, "locals", &v)) {
return false;
}
showLocals = ToBoolean(v);
if (!JS_GetProperty(cx, cfg, "thisprops", &v)) {
return false;
}
showThisProps = ToBoolean(v);
}
JS::UniqueChars buf =
JS::FormatStackDump(cx, showArgs, showLocals, showThisProps);
if (!buf) {
return false;
}
size_t len;
UniqueTwoByteChars ucbuf(JS::LossyUTF8CharsToNewTwoByteCharsZ(
cx, JS::UTF8Chars(buf.get(), strlen(buf.get())),
&len, js::MallocArena)
.get());
if (!ucbuf) {
return false;
}
JSString* str = JS_NewUCStringCopyN(cx, ucbuf.get(), len);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool ReportOutOfMemory(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JS_ReportOutOfMemory(cx);
cx->clearPendingException();
args.rval().setUndefined();
return true;
}
static bool ThrowOutOfMemory(JSContext* cx, unsigned argc, Value* vp) {
JS_ReportOutOfMemory(cx);
return false;
}
static bool ReportLargeAllocationFailure(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
size_t bytes = JSRuntime::LARGE_ALLOCATION;
if (args.length() >= 1) {
if (!args[0].isInt32()) {
RootedObject callee(cx, &args.callee());
ReportUsageErrorASCII(cx, callee,
"First argument must be an integer if specified.");
return false;
}
bytes = args[0].toInt32();
}
void* buf = cx->runtime()->onOutOfMemoryCanGC(AllocFunction::Malloc,
js::MallocArena, bytes);
js_free(buf);
args.rval().setUndefined();
return true;
}
namespace heaptools {
using EdgeName = UniqueTwoByteChars;
// An edge to a node from its predecessor in a path through the graph.
class BackEdge {
// The node from which this edge starts.
JS::ubi::Node predecessor_;
// The name of this edge.
EdgeName name_;
public:
BackEdge() : name_(nullptr) {}
// Construct an initialized back edge, taking ownership of |name|.
BackEdge(JS::ubi::Node predecessor, EdgeName name)
: predecessor_(predecessor), name_(std::move(name)) {}
BackEdge(BackEdge&& rhs)
: predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) {}
BackEdge& operator=(BackEdge&& rhs) {
MOZ_ASSERT(&rhs != this);
this->~BackEdge();
new (this) BackEdge(std::move(rhs));
<