/* -*- 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(), ¶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 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
// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
enum class NaNFlavor {
// A canonical NaN value.
// - the sign bit is unspecified,
// - the 8-bit exponent is set to all 1s
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
Canonical,
// An arithmetic NaN. This is the same as a canonical NaN including that the
// payload MSB is set to 1, but one or more of the remaining payload bits MAY
// BE set to 1 (a canonical NaN specifies all 0s).
Arithmetic,
};
static bool IsNaNFlavor(uint32_t bits, NaNFlavor flavor) {
switch (flavor) {
case NaNFlavor::Canonical: {
return (bits & 0x7fffffff) == 0x7fc00000;
}
case NaNFlavor::Arithmetic: {
const uint32_t ArithmeticNaN = 0x7f800000;
const uint32_t ArithmeticPayloadMSB = 0x00400000;
bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
bool isMSBSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
return isNaN && isMSBSet;
}
default:
MOZ_CRASH();
}
}
static bool IsNaNFlavor(uint64_t bits, NaNFlavor flavor) {
switch (flavor) {
case NaNFlavor::Canonical: {
return (bits & 0x7fffffffffffffff) == 0x7ff8000000000000;
}
case NaNFlavor::Arithmetic: {
uint64_t ArithmeticNaN = 0x7ff0000000000000;
uint64_t ArithmeticPayloadMSB = 0x0008000000000000;
bool isNaN = (bits & ArithmeticNaN) == ArithmeticNaN;
bool isMsbSet = (bits & ArithmeticPayloadMSB) == ArithmeticPayloadMSB;
return isNaN && isMsbSet;
}
default:
MOZ_CRASH();
}
}
static bool ToNaNFlavor(JSContext* cx, HandleValue v, NaNFlavor* out) {
RootedString flavorStr(cx, ToString(cx, v));
if (!flavorStr) {
return false;
}
Rooted<JSLinearString*> flavorLinearStr(cx, flavorStr->ensureLinear(cx));
if (!flavorLinearStr) {
return false;
}
if (StringEqualsLiteral(flavorLinearStr,
"canonical_nan")) {
*out = NaNFlavor::Canonical;
return true;
}
else if (StringEqualsLiteral(flavorLinearStr,
"arithmetic_nan")) {
*out = NaNFlavor::Arithmetic;
return true;
}
JS_ReportErrorASCII(cx,
"invalid nan flavor");
return false;
}
static bool WasmGlobalIsNaN(JSContext* cx,
unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx,
"wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx,
"not enough arguments");
return false;
}
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx,
"argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> global(
cx, &args.get(0).toObject().as<WasmGlobalObject>());
NaNFlavor flavor;
if (!ToNaNFlavor(cx, args.get(1), &flavor)) {
return false;
}
bool result;
const wasm::Val& val = global->val().get();
switch (global->type().kind()) {
case wasm::ValType::F32: {
result = IsNaNFlavor(mozilla::BitwiseCast<uint32_t>(val.f32()), flavor);
break;
}
case wasm::ValType::F64: {
result = IsNaNFlavor(mozilla::BitwiseCast<uint64_t>(val.f64()), flavor);
break;
}
default:
JS_ReportErrorASCII(cx,
"global is not a floating point value");
return false;
}
args.rval().setBoolean(result);
return true;
}
static bool WasmGlobalToString(JSContext* cx,
unsigned argc, Value* vp) {
if (!wasm::HasSupport(cx)) {
JS_ReportErrorASCII(cx,
"wasm support unavailable");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx,
"not enough arguments");
return false;
}
if (!args.get(0).isObject() ||
!args.get(0).toObject().is<WasmGlobalObject>()) {
JS_ReportErrorASCII(cx,
"argument is not wasm value");
return false;
}
Rooted<WasmGlobalObject*> global(
cx, &args.get(0).toObject().as<WasmGlobalObject>());
const wasm::Val& globalVal = global->val().get();
UniqueChars result;
switch (globalVal.type().kind()) {
case wasm::ValType::I32: {
result = JS_smprintf(
"i32:%" PRIx32, globalVal.i32());
break;
}
case wasm::ValType::I64: {
result = JS_smprintf(
"i64:%" PRIx64, globalVal.i64());
break;
}
case wasm::ValType::F32: {
result = JS_smprintf(
"f32:%f", globalVal.f32());
break;
}
case wasm::ValType::F64: {
result = JS_smprintf(
"f64:%lf", globalVal.f64());
break;
}
case wasm::ValType::V128: {
wasm::V128 v128 = globalVal.v128();
result = JS_smprintf(
"v128:%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", v128.bytes[0],
v128.bytes[1], v128.bytes[2], v128.bytes[3], v128.bytes[4],
v128.bytes[5], v128.bytes[6], v128.bytes[7], v128.bytes[8],
v128.bytes[9], v128.bytes[10], v128.bytes[11], v128.bytes[12],
v128.bytes[13], v128.bytes[14], v128.bytes[15]);
break;
}
case wasm::ValType::Ref: {
result = JS_smprintf(
"ref:%" 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::CodeSegment& segment = *codeBlock.segment;
const js::wasm::FuncExport& func = codeBlock.lookupFuncExport(funcIndex);
const js::wasm::CodeRange& codeRange = codeBlock.codeRange(func);
jit_begin = segment.base() + codeRange.begin();
jit_end = segment.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
// handle constant pools correctly (bug 1875363).
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
// handle constant pools correctly (bug 1875363).
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 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(args.get(0).toObjectOrNull(), &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);
--> --------------------
--> maximum size reached
--> --------------------