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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "builtin/TestingFunctions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#ifdef JS_HAS_INTL_API
# include "mozilla/intl/ICU4CLibrary.h"
# include "mozilla/intl/Locale.h"
# include "mozilla/intl/String.h"
# include "mozilla/intl/TimeZone.h"
#endif
#include "mozilla/Maybe.h"
#include "mozilla/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/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/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_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 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(), &param, &writable)) {
JS_ReportErrorASCII(
cx, "the first argument must be one of:" GC_PARAMETER_ARGS_LIST);
return false;
}
// Request mode.
if (args.length() == 1) {
uint32_t value = JS_GetGCParameter(cx, param);
args.rval().setNumber(value);
return true;
}
if (!writable) {
JS_ReportErrorASCII(cx, "Attempt to change read-only parameter %s",
name.get());
return false;
}
if (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 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 = (