Source code

Revision control

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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "builtin/TestingFunctions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#ifdef JS_HAS_INTL_API
# include "mozilla/intl/Locale.h"
# include "mozilla/intl/TimeZone.h"
#endif
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Span.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextUtils.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/Tuple.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 "jsapi.h"
#include "jsfriendapi.h"
#include "jsmath.h"
#ifdef JS_HAS_INTL_API
# include "builtin/intl/CommonFunctions.h"
# include "builtin/intl/FormatBuffer.h"
# include "builtin/intl/SharedIntlData.h"
#endif
#include "builtin/Promise.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/TestingUtility.h" // js::ParseCompileOptions
#ifdef DEBUG
# include "frontend/TokenStream.h"
#endif
#include "frontend/BytecodeCompilation.h" // frontend::CanLazilyParse
#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToExtensibleStencil
#include "frontend/CompilationStencil.h" // frontend::CompilationStencil
#include "gc/Allocator.h"
#include "gc/Zone.h"
#include "jit/BaselineJIT.h"
#include "jit/Disassemble.h"
#include "jit/InlinableNatives.h"
#include "jit/Invalidation.h"
#include "jit/Ion.h"
#include "jit/JitOptions.h"
#include "jit/JitRuntime.h"
#include "jit/TrialInlining.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/ArrayBuffer.h" // JS::{DetachArrayBuffer,GetArrayBufferLengthAndData,NewArrayBufferWithContents}
#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable, JS::IsConstructor, JS_CallFunction
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
#include "js/Date.h"
#include "js/Debug.h"
#include "js/experimental/CodeCoverage.h" // js::GetCodeCoverageSummary
#include "js/experimental/JSStencil.h" // JS::Stencil
#include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
#include "js/experimental/TypedData.h" // JS_GetObjectAsUint8Array
#include "js/friend/DumpFunctions.h" // js::Dump{Backtrace,Heap,Object}, JS::FormatStackDump, js::IgnoreNurseryObjects
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/WindowProxy.h" // js::ToWindowProxyIfWindow
#include "js/GlobalObject.h"
#include "js/HashTable.h"
#include "js/Interrupt.h"
#include "js/LocaleSensitive.h"
#include "js/OffThreadScriptCompilation.h" // js::UseOffThreadParseGlobal
#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/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Stack.h"
#include "js/String.h" // JS::GetLinearStringLength, JS::StringToLinearString
#include "js/StructuredClone.h"
#include "js/UbiNode.h"
#include "js/UbiNodeBreadthFirst.h"
#include "js/UbiNodeShortestPaths.h"
#include "js/UniquePtr.h"
#include "js/Vector.h"
#include "js/Wrapper.h"
#include "threading/CpuCount.h"
#ifdef JS_HAS_INTL_API
# include "unicode/ucal.h"
# include "unicode/uchar.h"
# include "unicode/utypes.h"
# include "unicode/uversion.h"
#endif
#include "util/DifferentialTesting.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/ErrorObject.h"
#include "vm/GlobalObject.h"
#include "vm/HelperThreads.h"
#include "vm/Interpreter.h"
#include "vm/Iteration.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseSlot_*
#include "vm/ProxyObject.h"
#include "vm/SavedStacks.h"
#include "vm/ScopeKind.h"
#include "vm/Stack.h"
#include "vm/StencilObject.h" // StencilObject, StencilXDRBufferObject
#include "vm/StringType.h"
#include "vm/TraceLogging.h"
#include "wasm/AsmJS.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmCraneliftCompile.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIntrinsic.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmSignalHandlers.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"
using namespace js;
using mozilla::AssertedCast;
using mozilla::AsWritableChars;
using mozilla::Maybe;
using mozilla::Span;
using mozilla::Tie;
using mozilla::Tuple;
using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::RegExpFlag;
using JS::RegExpFlags;
using JS::SourceOwnership;
using JS::SourceText;
// If fuzzingSafe is set, remove functionality that could cause problems with
// fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
mozilla::Atomic<bool> js::fuzzingSafe(false);
// If disableOOMFunctions is set, disable functionality that causes artificial
// OOM conditions.
static mozilla::Atomic<bool> disableOOMFunctions(false);
static bool EnvVarIsDefined(const char* name) {
const char* value = getenv(name);
return value && *value;
}
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
static bool EnvVarAsInt(const char* name, int* valueOut) {
if (!EnvVarIsDefined(name)) {
return false;
}
*valueOut = atoi(getenv(name));
return true;
}
#endif
static bool GetRealmConfiguration(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return false;
}
bool privateFields = cx->options().privateClassFields();
if (!JS_SetProperty(cx, info, "privateFields",
privateFields ? TrueHandleValue : FalseHandleValue)) {
return false;
}
bool privateMethods = cx->options().privateClassMethods();
if (!JS_SetProperty(cx, info, "privateMethods",
privateFields && privateMethods ? TrueHandleValue
: FalseHandleValue)) {
return false;
}
bool offThreadParseGlobal = js::UseOffThreadParseGlobal();
if (!JS_SetProperty(
cx, info, "offThreadParseGlobal",
offThreadParseGlobal ? TrueHandleValue : FalseHandleValue)) {
return false;
}
args.rval().setObject(*info);
return true;
}
static bool GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return false;
}
if (!JS_SetProperty(cx, info, "rooting-analysis", FalseHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "exact-rooting", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "trace-jscalls-api", FalseHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "incremental-gc", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "generational-gc", TrueHandleValue)) {
return false;
}
if (!JS_SetProperty(cx, info, "oom-backtraces", FalseHandleValue)) {
return false;
}
RootedValue value(cx);
#ifdef DEBUG
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "debug", value)) {
return false;
}
#ifdef RELEASE_OR_BETA
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "release_or_beta", value)) {
return false;
}
#ifdef EARLY_BETA_OR_EARLIER
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "early_beta_or_earlier", value)) {
return false;
}
#ifdef MOZ_CODE_COVERAGE
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "coverage", value)) {
return false;
}
#ifdef JS_HAS_CTYPES
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "has-ctypes", value)) {
return false;
}
#if defined(_M_IX86) || defined(__i386__)
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "x86", value)) {
return false;
}
#if defined(_M_X64) || defined(__x86_64__)
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "x64", value)) {
return false;
}
#ifdef JS_CODEGEN_ARM
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm", value)) {
return false;
}
#ifdef JS_SIMULATOR_ARM
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm-simulator", value)) {
return false;
}
#ifdef ANDROID
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "android", value)) {
return false;
}
#ifdef XP_WIN
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "windows", value)) {
return false;
}
#ifdef XP_MACOSX
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "osx", value)) {
return false;
}
#ifdef JS_CODEGEN_ARM64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm64", value)) {
return false;
}
#ifdef JS_SIMULATOR_ARM64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "arm64-simulator", value)) {
return false;
}
#ifdef JS_CODEGEN_MIPS32
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips32", value)) {
return false;
}
#ifdef JS_CODEGEN_MIPS64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips64", value)) {
return false;
}
#ifdef JS_SIMULATOR_MIPS32
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips32-simulator", value)) {
return false;
}
#ifdef JS_SIMULATOR_MIPS64
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "mips64-simulator", value)) {
return false;
}
#ifdef JS_SIMULATOR
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "simulator", value)) {
return false;
}
#ifdef __wasi__
value = BooleanValue(true);
#else
value = BooleanValue(false);
#endif
if (!JS_SetProperty(cx, info, "wasi", value)) {
return false;
}
#ifdef 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;
}
args.rval().setObject(*info);
return true;
}
static bool IsLCovEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(coverage::IsLCovEnabled());
return true;
}
static bool TrialInline(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
FrameIter iter(cx);
if (iter.done() || !iter.isBaseline() || iter.realm() != cx->realm()) {
return true;
}
jit::BaselineFrame* frame = iter.abstractFramePtr().asBaselineFrame();
if (!jit::CanIonCompileScript(cx, frame->script())) {
return true;
}
return jit::DoTrialInlining(cx, frame);
}
static bool ReturnStringCopy(JSContext* cx, CallArgs& args,
const char* message) {
JSString* str = JS_NewStringCopyZ(cx, message);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool MaybeGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JS_MaybeGC(cx);
args.rval().setUndefined();
return true;
}
static bool GC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
/*
* If the first argument is 'zone', we collect any zones previously
* scheduled for GC via schedulegc. If the first argument is an object, we
* collect the object's zone (and any other zones scheduled for
* GC). Otherwise, we collect all zones.
*/
bool zone = false;
if (args.length() >= 1) {
Value arg = args[0];
if (arg.isString()) {
if (!JS_StringEqualsLiteral(cx, arg.toString(), "zone", &zone)) {
return false;
}
} else if (arg.isObject()) {
PrepareZoneForGC(cx, UncheckedUnwrap(&arg.toObject())->zone());
zone = true;
}
}
JS::GCOptions options = JS::GCOptions::Normal;
JS::GCReason reason = JS::GCReason::API;
if (args.length() >= 2) {
Value arg = args[1];
if (arg.isString()) {
bool shrinking = false;
bool last_ditch = false;
if (!JS_StringEqualsLiteral(cx, arg.toString(), "shrinking",
&shrinking)) {
return false;
}
if (!JS_StringEqualsLiteral(cx, arg.toString(), "last-ditch",
&last_ditch)) {
return false;
}
if (shrinking) {
options = JS::GCOptions::Shrink;
} else if (last_ditch) {
options = JS::GCOptions::Shrink;
reason = JS::GCReason::LAST_DITCH;
}
}
}
size_t preBytes = cx->runtime()->gc.heapSize.bytes();
if (zone) {
PrepareForDebugGC(cx->runtime());
} else {
JS::PrepareForFullGC(cx);
}
JS::NonIncrementalGC(cx, options, reason);
char buf[256] = {'\0'};
if (!js::SupportDifferentialTesting()) {
SprintfLiteral(buf, "before %zu, after %zu\n", preBytes,
cx->runtime()->gc.heapSize.bytes());
}
return ReturnStringCopy(cx, args, buf);
}
static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.get(0) == BooleanValue(true)) {
cx->runtime()->gc.storeBuffer().setAboutToOverflow(
JS::GCReason::FULL_GENERIC_BUFFER);
}
cx->minorGC(JS::GCReason::API);
args.rval().setUndefined();
return true;
}
#define FOR_EACH_GC_PARAM(_) \
_("maxBytes", JSGC_MAX_BYTES, true) \
_("minNurseryBytes", JSGC_MIN_NURSERY_BYTES, true) \
_("maxNurseryBytes", JSGC_MAX_NURSERY_BYTES, true) \
_("gcBytes", JSGC_BYTES, false) \
_("nurseryBytes", JSGC_NURSERY_BYTES, false) \
_("gcNumber", JSGC_NUMBER, false) \
_("majorGCNumber", JSGC_MAJOR_GC_NUMBER, false) \
_("minorGCNumber", JSGC_MINOR_GC_NUMBER, false) \
_("incrementalGCEnabled", JSGC_INCREMENTAL_GC_ENABLED, true) \
_("perZoneGCEnabled", JSGC_PER_ZONE_GC_ENABLED, true) \
_("unusedChunks", JSGC_UNUSED_CHUNKS, false) \
_("totalChunks", JSGC_TOTAL_CHUNKS, false) \
_("sliceTimeBudgetMS", JSGC_SLICE_TIME_BUDGET_MS, true) \
_("markStackLimit", JSGC_MARK_STACK_LIMIT, true) \
_("highFrequencyTimeLimit", JSGC_HIGH_FREQUENCY_TIME_LIMIT, true) \
_("smallHeapSizeMax", JSGC_SMALL_HEAP_SIZE_MAX, true) \
_("largeHeapSizeMin", JSGC_LARGE_HEAP_SIZE_MIN, true) \
_("highFrequencySmallHeapGrowth", JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH, \
true) \
_("highFrequencyLargeHeapGrowth", JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH, \
true) \
_("lowFrequencyHeapGrowth", JSGC_LOW_FREQUENCY_HEAP_GROWTH, true) \
_("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true) \
_("smallHeapIncrementalLimit", JSGC_SMALL_HEAP_INCREMENTAL_LIMIT, true) \
_("largeHeapIncrementalLimit", JSGC_LARGE_HEAP_INCREMENTAL_LIMIT, true) \
_("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true) \
_("maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT, true) \
_("compactingEnabled", JSGC_COMPACTING_ENABLED, true) \
_("minLastDitchGCPeriod", JSGC_MIN_LAST_DITCH_GC_PERIOD, true) \
_("nurseryFreeThresholdForIdleCollection", \
JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION, true) \
_("nurseryFreeThresholdForIdleCollectionPercent", \
JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT, true) \
_("nurseryTimeoutForIdleCollectionMS", \
JSGC_NURSERY_TIMEOUT_FOR_IDLE_COLLECTION_MS, true) \
_("pretenureThreshold", JSGC_PRETENURE_THRESHOLD, true) \
_("pretenureGroupThreshold", JSGC_PRETENURE_GROUP_THRESHOLD, true) \
_("zoneAllocDelayKB", JSGC_ZONE_ALLOC_DELAY_KB, true) \
_("mallocThresholdBase", JSGC_MALLOC_THRESHOLD_BASE, true) \
_("urgentThreshold", JSGC_URGENT_THRESHOLD_MB, true) \
_("chunkBytes", JSGC_CHUNK_BYTES, false) \
_("helperThreadRatio", JSGC_HELPER_THREAD_RATIO, true) \
_("maxHelperThreads", JSGC_MAX_HELPER_THREADS, true) \
_("helperThreadCount", JSGC_HELPER_THREAD_COUNT, false) \
_("systemPageSizeKB", JSGC_SYSTEM_PAGE_SIZE_KB, false)
static const struct ParamInfo {
const char* name;
JSGCParamKey param;
bool writable;
} paramMap[] = {
#define DEFINE_PARAM_INFO(name, key, writable) {name, key, writable},
FOR_EACH_GC_PARAM(DEFINE_PARAM_INFO)
#undef DEFINE_PARAM_INFO
};
#define PARAM_NAME_LIST_ENTRY(name, key, writable) " " name
#define GC_PARAMETER_ARGS_LIST FOR_EACH_GC_PARAM(PARAM_NAME_LIST_ENTRY)
static bool GCParameter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSString* str = ToString(cx, args.get(0));
if (!str) {
return false;
}
JSLinearString* linearStr = JS_EnsureLinearString(cx, str);
if (!linearStr) {
return false;
}
const auto* ptr = std::find_if(
std::begin(paramMap), std::end(paramMap), [&](const auto& param) {
return JS_LinearStringEqualsAscii(linearStr, param.name);
});
if (ptr == std::end(paramMap)) {
JS_ReportErrorASCII(
cx, "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
return false;
}
const ParamInfo& info = *ptr;
JSGCParamKey param = info.param;
// Request mode.
if (args.length() == 1) {
uint32_t value = JS_GetGCParameter(cx, param);
args.rval().setNumber(value);
return true;
}
if (!info.writable) {
JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s",
info.name);
return false;
}
if (disableOOMFunctions) {
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);
if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx)) {
JS_ReportErrorASCII(
cx, "attempt to set markStackLimit while a GC is in progress");
return false;
}
bool ok = cx->runtime()->gc.setParameter(param, value);
if (!ok) {
JS_ReportErrorASCII(cx, "Parameter value out of range");
return false;
}
args.rval().setUndefined();
return true;
}
static bool RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp) {
// Relazifying functions on GC is usually only done for compartments that are
// not active. To aid fuzzing, this testing function allows us to relazify
// even if the compartment is active.
CallArgs args = CallArgsFromVp(argc, vp);
// Disable relazification of all scripts on stack. It is a pervasive
// assumption in the engine that running scripts still have bytecode.
for (AllScriptFramesIter i(cx); !i.done(); ++i) {
i.script()->clearAllowRelazify();
}
cx->runtime()->allowRelazificationForTesting = true;
JS::PrepareForFullGC(cx);
JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
cx->runtime()->allowRelazificationForTesting = false;
args.rval().setUndefined();
return true;
}
static bool IsProxy(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "the function takes exactly one argument");
return false;
}
if (!args[0].isObject()) {
args.rval().setBoolean(false);
return true;
}
args.rval().setBoolean(args[0].toObject().is<ProxyObject>());
return true;
}
static bool WasmIsSupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasSupport(cx) &&
wasm::AnyCompilerAvailable(cx));
return true;
}
static bool WasmIsSupportedByHardware(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasPlatformSupport(cx));
return true;
}
static bool WasmDebuggingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::HasSupport(cx) && wasm::BaselineAvailable(cx));
return true;
}
static bool WasmStreamingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::StreamingCompilationAvailable(cx));
return true;
}
static bool WasmCachingEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::CodeCachingAvailable(cx));
return true;
}
static bool WasmHugeMemorySupported(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef WASM_SUPPORTS_HUGE_MEMORY
args.rval().setBoolean(true);
#else
args.rval().setBoolean(false);
#endif
return true;
}
static bool WasmThreadsEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::ThreadsAvailable(cx));
return true;
}
#define WASM_FEATURE(NAME, ...) \
static bool Wasm##NAME##Enabled(JSContext* cx, unsigned argc, Value* vp) { \
CallArgs args = CallArgsFromVp(argc, vp); \
args.rval().setBoolean(wasm::NAME##Available(cx)); \
return true; \
}
JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE);
#undef WASM_FEATURE
static bool WasmSimdWormholeEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(wasm::SimdWormholeAvailable(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");
}
#ifdef ENABLE_WASM_CRANELIFT
if (wasm::CraneliftPlatformSupport()) {
if (*buf) {
strcat(buf, ",");
}
strcat(buf, "cranelift");
}
#else
if (wasm::IonPlatformSupport()) {
if (*buf) {
strcat(buf, ",");
}
strcat(buf, "ion");
}
#endif
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 cranelift = wasm::CraneliftAvailable(cx);
bool none = !baseline && !ion && !cranelift;
bool tiered = baseline && (ion || cranelift);
MOZ_ASSERT(!(ion && cranelift));
JSStringBuilder result(cx);
if (none && !result.append("none", 4)) {
return false;
}
if (baseline && !result.append("baseline", 8)) {
return false;
}
if (tiered && !result.append("+", 1)) {
return false;
}
if (ion && !result.append("ion", 3)) {
return false;
}
if (cranelift && !result.append("cranelift", 9)) {
return false;
}
if (JSString* str = result.finishString()) {
args.rval().setString(str);
return true;
}
return false;
}
static bool WasmCraneliftDisabledByFeatures(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool isDisabled = false;
JSStringBuilder reason(cx);
if (!wasm::CraneliftDisabledByFeatures(cx, &isDisabled, &reason)) {
return false;
}
if (isDisabled) {
JSString* result = reason.finishString();
if (!result) {
return false;
}
args.rval().setString(result);
} else {
args.rval().setBoolean(false);
}
return true;
}
static bool WasmIonDisabledByFeatures(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool isDisabled = false;
JSStringBuilder reason(cx);
if (!wasm::IonDisabledByFeatures(cx, &isDisabled, &reason)) {
return false;
}
if (isDisabled) {
JSString* result = reason.finishString();
if (!result) {
return false;
}
args.rval().setString(result);
} else {
args.rval().setBoolean(false);
}
return true;
}
#ifdef ENABLE_WASM_SIMD
# ifdef DEBUG
static char lastAnalysisResult[1024];
namespace js {
namespace wasm {
void ReportSimdAnalysis(const char* data) {
strncpy(lastAnalysisResult, data, sizeof(lastAnalysisResult));
lastAnalysisResult[sizeof(lastAnalysisResult) - 1] = 0;
}
} // namespace wasm
} // namespace js
// Unstable API for white-box testing of SIMD optimizations.
//
// Current API: takes no arguments, returns a string describing the last Simd
// simplification applied.
static bool WasmSimdAnalysis(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSString* result =
JS_NewStringCopyZ(cx, *lastAnalysisResult ? lastAnalysisResult : "none");
if (!result) {
return false;
}
args.rval().setString(result);
*lastAnalysisResult = (char)0;
return true;
}
# endif
#endif
static bool WasmGlobalFromArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
// Get the type of the value
wasm::ValType valType;
if (!wasm::ToValType(cx, args.get(0), &valType)) {
return false;
}
// Get the array buffer for the value
if (!args.get(1).isObject() ||
!args.get(1).toObject().is<ArrayBufferObject>()) {
JS_ReportErrorASCII(cx, "argument is not an array buffer");
return false;
}
RootedArrayBufferObject buffer(
cx, &args.get(1).toObject().as<ArrayBufferObject>());
// Only allow POD to be created from bytes
switch (valType.kind()) {
case wasm::ValType::I32:
case wasm::ValType::I64:
case wasm::ValType::F32:
case wasm::ValType::F64:
case wasm::ValType::V128:
break;
default:
JS_ReportErrorASCII(
cx, "invalid valtype for creating WebAssembly.Global from bytes");
return false;
}
// Check we have all the bytes we need
if (valType.size() != buffer->byteLength()) {
JS_ReportErrorASCII(cx, "array buffer has incorrect size");
return false;
}
// Copy the bytes from buffer into a tagged val
wasm::RootedVal val(cx, valType);
val.get().readFromRootedLocation(buffer->dataPointer());
// Create the global object
RootedObject proto(
cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
RootedWasmGlobalObject result(
cx, WasmGlobalObject::create(cx, val, false, proto));
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;
}
RootedLinearString 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;
}
RootedWasmGlobalObject 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:
val.set(wasm::Val(v128.extractLane<uint32_t>(lane)));
break;
case LaneInterp::I64x2:
val.set(wasm::Val(v128.extractLane<uint64_t>(lane)));
break;
case LaneInterp::F32x4:
val.set(wasm::Val(v128.extractLane<float>(lane)));
break;
case LaneInterp::F64x2:
val.set(wasm::Val(v128.extractLane<double>(lane)));
break;
default:
MOZ_ASSERT_UNREACHABLE();
}
RootedObject proto(
cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal));
RootedWasmGlobalObject 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;
}
RootedWasmGlobalObject a(cx, &args.get(0).toObject().as<WasmGlobalObject>());
RootedWasmGlobalObject b(cx, &args.get(1).toObject().as<WasmGlobalObject>());
if (a->type() != b->type()) {
JS_ReportErrorASCII(cx, "globals are of different type");
return false;
}
bool result;
const wasm::Val& aVal = a->val().get();
const wasm::Val& bVal = b->val().get();
switch (a->type().kind()) {
case wasm::ValType::I32: {
result = aVal.i32() == bVal.i32();
break;
}
case wasm::ValType::I64: {
result = aVal.i64() == bVal.i64();
break;
}
case wasm::ValType::F32: {
result = mozilla::BitwiseCast<uint32_t>(aVal.f32()) ==
mozilla::BitwiseCast<uint32_t>(aVal.f32());
break;
}
case wasm::ValType::F64: {
result = mozilla::BitwiseCast<uint64_t>(aVal.f64()) ==
mozilla::BitwiseCast<uint64_t>(aVal.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;
}
RootedLinearString 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;
}
RootedWasmGlobalObject 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;
}
RootedWasmGlobalObject global(cx,
&args.get(0).toObject().as<WasmGlobalObject>());
const wasm::Val& globalVal = global->val().get();
UniqueChars result;
switch (globalVal.type().kind()) {
case wasm::ValType::I32: {
result = JS_smprintf("i32:%" PRIx32, globalVal.i32());
break;
}
case wasm::ValType::I64: {
result = JS_smprintf("i64:%" PRIx64, globalVal.i64());
break;
}
case wasm::ValType::F32: {
result = JS_smprintf("f32:%f", globalVal.f32());
break;
}
case wasm::ValType::F64: {
result = JS_smprintf("f64:%lf", globalVal.f64());
break;
}
case wasm::ValType::V128: {
wasm::V128 v128 = globalVal.v128();
result = JS_smprintf(
"v128:%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", v128.bytes[0],
v128.bytes[1], v128.bytes[2], v128.bytes[3], v128.bytes[4],
v128.bytes[5], v128.bytes[6], v128.bytes[7], v128.bytes[8],
v128.bytes[9], v128.bytes[10], v128.bytes[11], v128.bytes[12],
v128.bytes[13], v128.bytes[14], v128.bytes[15]);
break;
}
case wasm::ValType::Ref: {
result = JS_smprintf("ref:%p", globalVal.ref().asJSObject());
break;
}
default:
MOZ_ASSERT_UNREACHABLE();
}
args.rval().setString(JS_NewStringCopyZ(cx, result.get()));
return true;
}
static bool WasmLosslessInvoke(JSContext* cx, unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx, "wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "not enough arguments");
return false;
}
if (!args.get(0).isObject()) {
JS_ReportErrorASCII(cx, "argument is not an object");
return false;
}
RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
if (!func || !wasm::IsWasmExportedFunction(func)) {
JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
return false;
}
// Get the instance and funcIndex for calling the function
wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
// Set up a modified call frame following the standard JS
// [callee, this, arguments...] convention.
RootedValueVector wasmCallFrame(cx);
size_t len = 2 + args.length();
if (!wasmCallFrame.resize(len)) {
return false;
}
wasmCallFrame[0].set(args.calleev());
wasmCallFrame[1].set(args.thisv());
// Copy over the arguments needed to invoke the provided wasm function,
// skipping the wasm function we're calling that is at `args.get(0)`.
for (size_t i = 1; i < args.length(); i++) {
size_t wasmArg = i - 1;
wasmCallFrame[2 + wasmArg].set(args.get(i));
}
size_t wasmArgc = argc - 1;
CallArgs wasmCallArgs(CallArgsFromVp(wasmArgc, wasmCallFrame.begin()));
// Invoke the function with the new call frame
bool result = instance.callExport(cx, funcIndex, wasmCallArgs,
wasm::CoercionLevel::Lossless);
// Assign the wasm rval to our rval
args.rval().set(wasmCallArgs.rval());
return result;
}
static bool ConvertToTier(JSContext* cx, HandleValue value,
const wasm::Code& code, wasm::Tier* tier) {
RootedString option(cx, JS::ToString(cx, value));
if (!option) {
return false;
}
bool stableTier = false;
bool bestTier = false;
bool baselineTier = false;
bool ionTier = false;
if (!JS_StringEqualsLiteral(cx, option, "stable", &stableTier) ||
!JS_StringEqualsLiteral(cx, option, "best", &bestTier) ||
!JS_StringEqualsLiteral(cx, option, "baseline", &baselineTier) ||
!JS_StringEqualsLiteral(cx, option, "ion", &ionTier)) {
return false;