/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmJS.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/Maybe.h"
#include <algorithm>
#include <cstdint>
#include "jsapi.h"
#include "jsexn.h"
#include "ds/IdValuePair.h" // js::IdValuePair
#include "frontend/FrontendContext.h" // AutoReportFrontendContext
#include "gc/GCContext.h"
#include "jit/AtomicOperations.h"
#include "jit/FlushICache.h"
#include "jit/JitContext.h"
#include "jit/JitOptions.h"
#include "jit/Simulator.h"
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
#include "js/ForOfIterator.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
#include "js/PropertySpec.h" // JS_{PS,FN}{,_END}
#include "js/Stack.h" // BuildStackString
#include "js/StreamConsumer.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/ErrorObject.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GlobalObject.h" // js::GlobalObject
#include "vm/HelperThreadState.h" // js::PromiseHelperTask
#include "vm/Interpreter.h"
#include "vm/JSFunction.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/PromiseObject.h" // js::PromiseObject
#include "vm/SharedArrayObject.h"
#include "vm/StringType.h"
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmBuiltinModule.h"
#include "wasm/WasmBuiltins.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmDebug.h"
#include "wasm/WasmFeatures.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmMemory.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmPI.h"
#include "wasm/WasmProcess.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h"
#include "gc/GCContext-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "wasm/WasmInstance-inl.h"
/*
* [SMDOC] WebAssembly code rules (evolving)
*
* TlsContext.get() is only to be invoked from functions that have been invoked
* _directly_ by generated code as cold(!) Builtin calls, from code that is
* only used by signal handlers, or from helper functions that have been
* called _directly_ from a simulator. All other code shall pass in a
* JSContext* to functions that need it, or an Instance* or Instance* since
* the context is available through them.
*
* Code that uses TlsContext.get() shall annotate each such call with the
* reason why the call is OK.
*/
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::Some;
using mozilla::Span;
static bool ThrowCompileOutOfMemory(JSContext* cx) {
// Most OOMs during compilation are due to large contiguous allocations,
// and future allocations are likely to succeed. Throwing a proper error
// object is nicer for users in these circumstances.
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OUT_OF_MEMORY);
return false;
}
// ============================================================================
// Imports
static bool ThrowBadImportArg(JSContext* cx) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_ARG);
return false;
}
static bool ThrowBadImportType(JSContext* cx,
const CacheableName& field,
const char* str) {
UniqueChars fieldQuoted = field.toQuotedString(cx);
if (!fieldQuoted) {
ReportOutOfMemory(cx);
return false;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_TYPE, fieldQuoted.get(), str);
return false;
}
// For now reject cross-compartment wrappers. These have more complicated realm
// semantics (we use nonCCWRealm in a few places) and may require unwrapping to
// test for specific function types.
static bool IsCallableNonCCW(
const Value& v) {
return IsCallable(v) && !IsCrossCompartmentWrapper(&v.toObject());
}
static bool IsWasmSuspendingWrapper(
const Value& v) {
return v.isObject() && js::IsWasmSuspendingObject(&v.toObject());
}
bool js::wasm::GetImports(JSContext* cx,
const Module& module,
HandleObject importObj, ImportValues* imports) {
const ModuleMetadata& moduleMeta = module.moduleMeta();
const CodeMetadata& codeMeta = module.codeMeta();
const BuiltinModuleIds& builtinModules = codeMeta.features().builtinModules;
if (!moduleMeta.imports.empty() && !importObj) {
return ThrowBadImportArg(cx);
}
BuiltinModuleInstances builtinInstances(cx);
RootedValue importModuleValue(cx);
RootedObject importModuleObject(cx);
bool isImportedStringModule =
false;
RootedValue importFieldValue(cx);
uint32_t tagIndex = 0;
const TagDescVector& tags = codeMeta.tags;
uint32_t globalIndex = 0;
const GlobalDescVector& globals = codeMeta.globals;
uint32_t tableIndex = 0;
const TableDescVector& tables = codeMeta.tables;
for (
const Import& import : moduleMeta.imports) {
Maybe<BuiltinModuleId> builtinModule =
ImportMatchesBuiltinModule(import.module.utf8Bytes(), builtinModules);
if (builtinModule) {
if (*builtinModule == BuiltinModuleId::JSStringConstants) {
isImportedStringModule =
true;
importModuleObject = nullptr;
}
else {
MutableHandle<JSObject*> builtinInstance =
builtinInstances[*builtinModule];
if (!builtinInstance && !wasm::InstantiateBuiltinModule(
cx, *builtinModule, builtinInstance)) {
return false;
}
isImportedStringModule =
false;
importModuleObject = builtinInstance;
}
}
else {
RootedId moduleName(cx);
if (!import.module.toPropertyKey(cx, &moduleName)) {
return false;
}
if (!GetProperty(cx, importObj, importObj, moduleName,
&importModuleValue)) {
return false;
}
if (!importModuleValue.isObject()) {
UniqueChars moduleQuoted = import.module.toQuotedString(cx);
if (!moduleQuoted) {
ReportOutOfMemory(cx);
return false;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_FIELD,
moduleQuoted.get());
return false;
}
isImportedStringModule =
false;
importModuleObject = &importModuleValue.toObject();
}
MOZ_RELEASE_ASSERT(!isImportedStringModule ||
import.kind == DefinitionKind::Global);
if (isImportedStringModule) {
RootedString stringConstant(cx, import.field.toJSString(cx));
if (!stringConstant) {
ReportOutOfMemory(cx);
return false;
}
importFieldValue = StringValue(stringConstant);
}
else {
RootedId fieldName(cx);
if (!import.field.toPropertyKey(cx, &fieldName)) {
return false;
}
if (!GetProperty(cx, importModuleObject, importModuleObject, fieldName,
&importFieldValue)) {
return false;
}
}
switch (import.kind) {
case DefinitionKind::Function: {
if (!IsCallableNonCCW(importFieldValue) &&
!IsWasmSuspendingWrapper(importFieldValue)) {
return ThrowBadImportType(cx, import.field,
"Function");
}
if (!imports->funcs.append(&importFieldValue.toObject())) {
ReportOutOfMemory(cx);
return false;
}
break;
}
case DefinitionKind::Table: {
const uint32_t index = tableIndex++;
if (!importFieldValue.isObject() ||
!importFieldValue.toObject().is<WasmTableObject>()) {
return ThrowBadImportType(cx, import.field,
"Table");
}
Rooted<WasmTableObject*> obj(
cx, &importFieldValue.toObject().as<WasmTableObject>());
if (obj->table().elemType() != tables[index].elemType) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_TBL_TYPE_LINK);
return false;
}
if (!imports->tables.append(obj)) {
ReportOutOfMemory(cx);
return false;
}
break;
}
case DefinitionKind::Memory: {
if (!importFieldValue.isObject() ||
!importFieldValue.toObject().is<WasmMemoryObject>()) {
return ThrowBadImportType(cx, import.field,
"Memory");
}
if (!imports->memories.append(
&importFieldValue.toObject().as<WasmMemoryObject>())) {
ReportOutOfMemory(cx);
return false;
}
break;
}
case DefinitionKind::Tag: {
const uint32_t index = tagIndex++;
if (!importFieldValue.isObject() ||
!importFieldValue.toObject().is<WasmTagObject>()) {
return ThrowBadImportType(cx, import.field,
"Tag");
}
Rooted<WasmTagObject*> obj(
cx, &importFieldValue.toObject().as<WasmTagObject>());
// Checks whether the signature of the imported exception object matches
// the signature declared in the exception import's TagDesc.
if (!TagType::matches(*obj->tagType(), *tags[index].type)) {
UniqueChars fieldQuoted = import.field.toQuotedString(cx);
UniqueChars moduleQuoted = import.module.toQuotedString(cx);
if (!fieldQuoted || !moduleQuoted) {
ReportOutOfMemory(cx);
return false;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_TAG_SIG, moduleQuoted.get(),
fieldQuoted.get());
return false;
}
if (!imports->tagObjs.append(obj)) {
ReportOutOfMemory(cx);
return false;
}
break;
}
case DefinitionKind::Global: {
const uint32_t index = globalIndex++;
const GlobalDesc& global = globals[index];
MOZ_ASSERT(global.importIndex() == index);
RootedVal val(cx);
if (importFieldValue.isObject() &&
importFieldValue.toObject().is<WasmGlobalObject>()) {
Rooted<WasmGlobalObject*> obj(
cx, &importFieldValue.toObject().as<WasmGlobalObject>());
if (obj->isMutable() != global.isMutable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_MUT_LINK);
return false;
}
bool matches = global.isMutable()
? obj->type() == global.type()
: ValType::isSubTypeOf(obj->type(), global.type());
if (!matches) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_TYPE_LINK);
return false;
}
if (imports->globalObjs.length() <= index &&
!imports->globalObjs.resize(index + 1)) {
ReportOutOfMemory(cx);
return false;
}
imports->globalObjs[index] = obj;
val = obj->val();
}
else {
if (!global.type().isRefType()) {
if (global.type() == ValType::I64 && !importFieldValue.isBigInt()) {
return ThrowBadImportType(cx, import.field,
"BigInt");
}
if (global.type() != ValType::I64 && !importFieldValue.isNumber()) {
return ThrowBadImportType(cx, import.field,
"Number");
}
}
if (global.isMutable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_MUT_LINK);
return false;
}
if (!Val::fromJSValue(cx, global.type(), importFieldValue, &val)) {
return false;
}
}
if (!imports->globalValues.append(val)) {
ReportOutOfMemory(cx);
return false;
}
break;
}
}
}
MOZ_ASSERT(globalIndex == globals.length() ||
!globals[globalIndex].isImport());
return true;
}
static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller,
const char* introducer) {
// Note: JS::DescribeScriptedCaller returns whether a scripted caller was
// found, not whether an error was thrown. This wrapper function converts
// back to the more ordinary false-if-error form.
JS::AutoFilename af;
if (JS::DescribeScriptedCaller(&af, cx, &caller->line)) {
caller->filename =
FormatIntroducedFilename(af.get(), caller->line, introducer);
if (!caller->filename) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
static SharedCompileArgs InitCompileArgs(JSContext* cx, FeatureOptions options,
const char* introducer) {
ScriptedCaller scriptedCaller;
if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) {
return nullptr;
}
return CompileArgs::buildAndReport(cx, std::move(scriptedCaller), options);
}
// ============================================================================
// Testing / Fuzzing support
bool wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code,
HandleObject importObj,
MutableHandle<WasmInstanceObject*> instanceObj) {
if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
return false;
}
MutableBytes bytecode = cx->new_<ShareableBytes>();
if (!bytecode) {
return false;
}
if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(),
code->byteLength().valueOr(0))) {
ReportOutOfMemory(cx);
return false;
}
FeatureOptions options;
SharedCompileArgs compileArgs = InitCompileArgs(cx, options,
"wasm_eval");
if (!compileArgs) {
return false;
}
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
if (!module) {
if (error) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_COMPILE_ERROR, error.get());
return false;
}
return ThrowCompileOutOfMemory(cx);
}
Rooted<ImportValues> imports(cx);
if (!GetImports(cx, *module, importObj, imports.address())) {
return false;
}
return module->instantiate(cx, imports.get(), nullptr, instanceObj);
}
struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener {
// MOZ_STACK_CLASS means these can be nops.
MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override {
return 0; }
MozExternalRefCountType MOZ_XPCOM_ABI Release() override {
return 0; }
mozilla::DebugOnly<
bool> called =
false;
Bytes* serialized;
explicit SerializeListener(Bytes* serialized) : serialized(serialized) {}
void storeOptimizedEncoding(
const uint8_t* bytes, size_t length) override {
MOZ_ASSERT(!called);
called =
true;
if (serialized->resizeUninitialized(length)) {
memcpy(serialized->begin(), bytes, length);
}
}
};
bool wasm::CompileAndSerialize(JSContext* cx,
const ShareableBytes& bytecode,
Bytes* serialized) {
// The caller must check that code caching is available
MOZ_ASSERT(CodeCachingAvailable(cx));
// Create and manually fill in compile args for code caching
MutableCompileArgs compileArgs = js_new<CompileArgs>();
if (!compileArgs) {
return false;
}
// The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure
// we go straight to tier-2 so that we synchronously call
// JS::OptimizedEncodingListener::storeOptimizedEncoding().
compileArgs->baselineEnabled =
false;
compileArgs->forceTiering =
false;
// We always pick Ion here, and we depend on CodeCachingAvailable() having
// determined that Ion is available, see comments at CodeCachingAvailable().
// To do better, we need to pass information about which compiler that should
// be used into CompileAndSerialize().
compileArgs->ionEnabled =
true;
// Select features that are enabled. This is guaranteed to be consistent with
// our compiler selection, as code caching is only available if ion is
// available, and ion is only available if it's not disabled by enabled
// features.
compileArgs->features = FeatureArgs::build(cx, FeatureOptions());
SerializeListener listener(serialized);
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener);
if (!module) {
fprintf(stderr,
"Compilation error: %s\n", error ? error.get() :
"oom");
return false;
}
MOZ_ASSERT(module->code().hasCompleteTier(Tier::Serialized));
MOZ_ASSERT(listener.called);
return !listener.serialized->empty();
}
bool wasm::DeserializeModule(JSContext* cx,
const Bytes& serialized,
MutableHandleObject moduleObj) {
MutableModule module =
Module::deserialize(serialized.begin(), serialized.length());
if (!module) {
ReportOutOfMemory(cx);
return false;
}
moduleObj.set(module->createObject(cx));
return !!moduleObj;
}
// ============================================================================
// Common functions
// '[EnforceRange] unsigned long' types are coerced with
// ConvertToInt(v, 32, 'unsigned')
// defined in Web IDL Section 3.2.4.9.
//
// This just generalizes that to an arbitrary limit that is representable as an
// integer in double form.
static bool EnforceRange(JSContext* cx, HandleValue v,
const char* kind,
const char* noun, uint64_t max, uint64_t* val) {
// Step 4.
double x;
if (!ToNumber(cx, v, &x)) {
return false;
}
// Step 5.
if (mozilla::IsNegativeZero(x)) {
x = 0.0;
}
// Step 6.1.
if (!std::isfinite(x)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
return false;
}
// Step 6.2.
x = JS::ToInteger(x);
// Step 6.3.
if (x < 0 || x >
double(max)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
return false;
}
*val = uint64_t(x);
MOZ_ASSERT(
double(*val) == x);
return true;
}
static bool EnforceRangeU32(JSContext* cx, HandleValue v,
const char* kind,
const char* noun, uint32_t* u32) {
uint64_t u64 = 0;
if (!EnforceRange(cx, v, kind, noun, uint64_t(UINT32_MAX), &u64)) {
return false;
}
*u32 = uint32_t(u64);
return true;
}
static bool EnforceRangeU64(JSContext* cx, HandleValue v,
const char* kind,
const char* noun, uint64_t* u64) {
// The max is Number.MAX_SAFE_INTEGER
return EnforceRange(cx, v, kind, noun, (1LL << 53) - 1, u64);
}
static bool EnforceRangeBigInt64(JSContext* cx, HandleValue v,
const char* kind,
const char* noun, uint64_t* u64) {
RootedBigInt bi(cx, ToBigInt(cx, v));
if (!bi) {
return false;
}
if (!BigInt::isUint64(bi, u64)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_ENFORCE_RANGE, kind, noun);
return false;
}
return true;
}
static bool EnforceAddressValue(JSContext* cx, HandleValue v,
AddressType addressType,
const char* kind,
const char* noun, uint64_t* result) {
switch (addressType) {
case AddressType::I32: {
uint32_t result32;
if (!EnforceRangeU32(cx, v, kind, noun, &result32)) {
return false;
}
*result = uint64_t(result32);
return true;
}
case AddressType::I64:
return EnforceRangeBigInt64(cx, v, kind, noun, result);
default:
MOZ_CRASH(
"unknown address type");
}
}
// The AddressValue typedef, a union of number and bigint, is used in the JS API
// spec for memory and table arguments, where number is used for memory32 and
// bigint is used for memory64.
[[nodiscard]]
static bool CreateAddressValue(JSContext* cx, uint64_t value,
AddressType addressType,
MutableHandleValue addressValue) {
switch (addressType) {
case AddressType::I32:
MOZ_ASSERT(value <= UINT32_MAX);
addressValue.set(NumberValue(value));
return true;
case AddressType::I64: {
BigInt* bi = BigInt::createFromUint64(cx, value);
if (!bi) {
return false;
}
addressValue.set(BigIntValue(bi));
return true;
}
default:
MOZ_CRASH(
"unknown address type");
}
}
// Gets an AddressValue property ("initial" or "maximum") from a
// MemoryDescriptor or TableDescriptor. The values returned by this should be
// run through CheckLimits to enforce the validation limits prescribed by the
// spec.
static bool GetDescriptorAddressValue(JSContext* cx, HandleObject obj,
const char* name,
const char* noun,
const char* msg, AddressType addressType,
bool* found, uint64_t* value) {
JSAtom* atom = Atomize(cx, name, strlen(name));
if (!atom) {
return false;
}
RootedId id(cx, AtomToId(atom));
RootedValue val(cx);
if (!GetProperty(cx, obj, obj, id, &val)) {
return false;
}
if (val.isUndefined()) {
*found =
false;
return true;
}
*found =
true;
return EnforceAddressValue(cx, val, addressType, noun, msg, value);
}
static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
Limits* limits) {
limits->addressType = AddressType::I32;
// Limits may specify an alternate address type, and we need this to check the
// ranges for initial and maximum, so look for the address type first.
#ifdef ENABLE_WASM_MEMORY64
// Get the address type field
JSAtom* addressTypeAtom = Atomize(cx,
"address", strlen(
"address"));
if (!addressTypeAtom) {
return false;
}
RootedId addressTypeId(cx, AtomToId(addressTypeAtom));
RootedValue addressTypeVal(cx);
if (!GetProperty(cx, obj, obj, addressTypeId, &addressTypeVal)) {
return false;
}
// The address type has a default value
if (!addressTypeVal.isUndefined()) {
if (!ToAddressType(cx, addressTypeVal, &limits->addressType)) {
return false;
}
if (limits->addressType == AddressType::I64 && !Memory64Available(cx)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_MEM64_LINK);
return false;
}
}
#endif
const char* noun = ToString(kind);
uint64_t limit = 0;
bool haveInitial =
false;
if (!GetDescriptorAddressValue(cx, obj,
"initial", noun,
"initial size",
limits->addressType, &haveInitial, &limit)) {
return false;
}
if (haveInitial) {
limits->initial = limit;
}
bool haveMinimum =
false;
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
if (!GetDescriptorAddressValue(cx, obj,
"minimum", noun,
"initial size",
limits->addressType, &haveMinimum, &limit)) {
return false;
}
if (haveMinimum) {
limits->initial = limit;
}
#endif
if (!(haveInitial || haveMinimum)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_REQUIRED,
"initial");
return false;
}
if (haveInitial && haveMinimum) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_SUPPLY_ONLY_ONE,
"minimum",
"initial");
return false;
}
bool haveMaximum =
false;
if (!GetDescriptorAddressValue(cx, obj,
"maximum", noun,
"maximum size",
limits->addressType, &haveMaximum, &limit)) {
return false;
}
if (haveMaximum) {
limits->maximum = Some(limit);
}
limits->shared = Shareable::
False;
// Memory limits may be shared.
if (kind == LimitsKind::Memory) {
// Get the shared field
JSAtom* sharedAtom = Atomize(cx,
"shared", strlen(
"shared"));
if (!sharedAtom) {
return false;
}
RootedId sharedId(cx, AtomToId(sharedAtom));
RootedValue sharedVal(cx);
if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
return false;
}
// shared's default value is false, which is already the value set above.
if (!sharedVal.isUndefined()) {
limits->shared =
ToBoolean(sharedVal) ? Shareable::
True : Shareable::
False;
if (limits->shared == Shareable::
True) {
if (!haveMaximum) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_MAXIMUM, noun);
return false;
}
if (!cx->realm()
->creationOptions()
.getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK);
return false;
}
}
}
}
return true;
}
static bool CheckLimits(JSContext* cx, uint64_t validationMax, LimitsKind kind,
Limits* limits) {
const char* noun = ToString(kind);
// There are several layers of validation and error-throwing here, including
// one which is currently not defined by the JS API spec:
//
// - [EnforceRange] on parameters (must be TypeError)
// - A check that initial <= maximum (must be RangeError)
// - Either a mem_alloc or table_alloc operation, which has two components:
// - A pre-condition that the given memory or table type is valid
// (not specified, RangeError in practice)
// - The actual allocation (should report OOM if it fails)
//
// There are two questions currently left open by the spec: when is the memory
// or table type validated, and if it is invalid, what type of exception does
// it throw? In practice, all browsers throw RangeError, and by the time you
// read this the spec will hopefully have been updated to reflect this. See
// the following issue: https://github.com/WebAssembly/spec/issues/1792
// Check that initial <= maximum
if (limits->maximum.isSome() && *limits->maximum < limits->initial) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MAX_LT_INITIAL, noun);
return false;
}
// Check wasm validation limits
if (limits->initial > validationMax) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
noun,
"initial size");
return false;
}
if (limits->maximum.isSome() && *limits->maximum > validationMax) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
noun,
"maximum size");
return false;
}
return true;
}
template <
class Class,
const char* name>
static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) {
Rooted<JSAtom*> className(cx, Atomize(cx, name, strlen(name)));
if (!className) {
return nullptr;
}
return NewNativeConstructor(cx,
Class::construct, 1, className);
}
static JSObject* GetWasmConstructorPrototype(JSContext* cx,
const CallArgs& callArgs,
JSProtoKey key) {
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, key, &proto)) {
return nullptr;
}
if (!proto) {
proto = GlobalObject::getOrCreatePrototype(cx, key);
}
return proto;
}
[[nodiscard]]
static bool ParseValTypes(JSContext* cx, HandleValue src,
ValTypeVector& dest) {
JS::ForOfIterator iterator(cx);
if (!iterator.init(src, JS::ForOfIterator::ThrowOnNonIterable)) {
return false;
}
RootedValue nextParam(cx);
while (
true) {
bool done;
if (!iterator.next(&nextParam, &done)) {
return false;
}
if (done) {
break;
}
ValType valType;
if (!ToValType(cx, nextParam, &valType) || !dest.append(valType)) {
return false;
}
}
return true;
}
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
template <
typename T>
static JSString* TypeToString(JSContext* cx, T type) {
UniqueChars chars = ToString(type, nullptr);
if (!chars) {
return nullptr;
}
return NewStringCopyUTF8Z(
cx, JS::ConstUTF8CharsZ(chars.get(), strlen(chars.get())));
}
# ifdef ENABLE_WASM_MEMORY64
static JSString* AddressTypeToString(JSContext* cx, AddressType type) {
return JS_NewStringCopyZ(cx, ToString(type));
}
# endif
[[nodiscard]]
static JSObject* ValTypesToArray(JSContext* cx,
const ValTypeVector& valTypes) {
Rooted<ArrayObject*> arrayObj(cx, NewDenseEmptyArray(cx));
if (!arrayObj) {
return nullptr;
}
for (ValType valType : valTypes) {
RootedString type(cx, TypeToString(cx, valType));
if (!type) {
return nullptr;
}
if (!NewbornArrayPush(cx, arrayObj, StringValue(type))) {
return nullptr;
}
}
return arrayObj;
}
static JSObject* FuncTypeToObject(JSContext* cx,
const FuncType& type) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
RootedObject parametersObj(cx, ValTypesToArray(cx, type.args()));
if (!parametersObj ||
!props.append(IdValuePair(NameToId(cx->names().parameters),
ObjectValue(*parametersObj)))) {
ReportOutOfMemory(cx);
return nullptr;
}
RootedObject resultsObj(cx, ValTypesToArray(cx, type.results()));
if (!resultsObj || !props.append(IdValuePair(NameToId(cx->names().results),
ObjectValue(*resultsObj)))) {
ReportOutOfMemory(cx);
return nullptr;
}
return NewPlainObjectWithUniqueNames(cx, props);
}
static JSObject* TableTypeToObject(JSContext* cx, AddressType addressType,
RefType type, uint64_t initial,
Maybe<uint64_t> maximum) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
RootedString elementType(cx, TypeToString(cx, type));
if (!elementType || !props.append(IdValuePair(NameToId(cx->names().element),
StringValue(elementType)))) {
ReportOutOfMemory(cx);
return nullptr;
}
if (maximum.isSome()) {
RootedId maximumId(cx, NameToId(cx->names().maximum));
RootedValue maximumValue(cx);
if (!CreateAddressValue(cx, maximum.value(), addressType, &maximumValue)) {
ReportOutOfMemory(cx);
return nullptr;
}
if (!props.append(IdValuePair(maximumId, maximumValue))) {
ReportOutOfMemory(cx);
return nullptr;
}
}
RootedId minimumId(cx, NameToId(cx->names().minimum));
RootedValue minimumValue(cx);
if (!CreateAddressValue(cx, initial, addressType, &minimumValue)) {
ReportOutOfMemory(cx);
return nullptr;
}
if (!props.append(IdValuePair(minimumId, minimumValue))) {
ReportOutOfMemory(cx);
return nullptr;
}
# ifdef ENABLE_WASM_MEMORY64
RootedString at(cx, AddressTypeToString(cx, addressType));
if (!at) {
return nullptr;
}
if (!props.append(
IdValuePair(NameToId(cx->names().address), StringValue(at)))) {
ReportOutOfMemory(cx);
return nullptr;
}
# endif
return NewPlainObjectWithUniqueNames(cx, props);
}
static JSObject* MemoryTypeToObject(JSContext* cx,
bool shared,
wasm::AddressType addressType,
wasm::Pages minPages,
Maybe<wasm::Pages> maxPages) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (maxPages) {
RootedId maximumId(cx, NameToId(cx->names().maximum));
RootedValue maximumValue(cx);
if (!CreateAddressValue(cx, maxPages.value().value(), addressType,
&maximumValue)) {
ReportOutOfMemory(cx);
return nullptr;
}
if (!props.append(IdValuePair(maximumId, maximumValue))) {
ReportOutOfMemory(cx);
return nullptr;
}
}
RootedId minimumId(cx, NameToId(cx->names().minimum));
RootedValue minimumValue(cx);
if (!CreateAddressValue(cx, minPages.value(), addressType, &minimumValue)) {
ReportOutOfMemory(cx);
return nullptr;
}
if (!props.append(IdValuePair(minimumId, minimumValue))) {
ReportOutOfMemory(cx);
return nullptr;
}
# ifdef ENABLE_WASM_MEMORY64
RootedString at(cx, AddressTypeToString(cx, addressType));
if (!at) {
return nullptr;
}
if (!props.append(
IdValuePair(NameToId(cx->names().address), StringValue(at)))) {
ReportOutOfMemory(cx);
return nullptr;
}
# endif
if (!props.append(
IdValuePair(NameToId(cx->names().shared), BooleanValue(shared)))) {
ReportOutOfMemory(cx);
return nullptr;
}
return NewPlainObjectWithUniqueNames(cx, props);
}
static JSObject* GlobalTypeToObject(JSContext* cx, ValType type,
bool isMutable) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (!props.append(IdValuePair(NameToId(cx->names().mutable_),
BooleanValue(isMutable)))) {
ReportOutOfMemory(cx);
return nullptr;
}
RootedString valueType(cx, TypeToString(cx, type));
if (!valueType || !props.append(IdValuePair(NameToId(cx->names().value),
StringValue(valueType)))) {
ReportOutOfMemory(cx);
return nullptr;
}
return NewPlainObjectWithUniqueNames(cx, props);
}
static JSObject* TagTypeToObject(JSContext* cx,
const wasm::ValTypeVector& params) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
RootedObject parametersObj(cx, ValTypesToArray(cx, params));
if (!parametersObj ||
!props.append(IdValuePair(NameToId(cx->names().parameters),
ObjectValue(*parametersObj)))) {
ReportOutOfMemory(cx);
return nullptr;
}
return NewPlainObjectWithUniqueNames(cx, props);
}
#endif // ENABLE_WASM_TYPE_REFLECTIONS
// ============================================================================
// WebAssembly.Module class and methods
const JSClassOps WasmModuleObject::classOps_ = {
nullptr,
// addProperty
nullptr,
// delProperty
nullptr,
// enumerate
nullptr,
// newEnumerate
nullptr,
// resolve
nullptr,
// mayResolve
WasmModuleObject::finalize,
// finalize
nullptr,
// call
nullptr,
// construct
nullptr,
// trace
};
const JSClass WasmModuleObject::class_ = {
"WebAssembly.Module",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmModuleObject::classOps_,
&WasmModuleObject::classSpec_,
};
const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;
static constexpr
char WasmModuleName[] =
"Module";
const ClassSpec WasmModuleObject::classSpec_ = {
CreateWasmConstructor<WasmModuleObject, WasmModuleName>,
GenericCreatePrototype<WasmModuleObject>,
WasmModuleObject::static_methods,
nullptr,
WasmModuleObject::methods,
WasmModuleObject::properties,
nullptr,
ClassSpec::DontDefineConstructor,
};
const JSPropertySpec WasmModuleObject::properties[] = {
JS_STRING_SYM_PS(toStringTag,
"WebAssembly.Module", JSPROP_READONLY),
JS_PS_END,
};
const JSFunctionSpec WasmModuleObject::methods[] = {
JS_FS_END,
};
const JSFunctionSpec WasmModuleObject::static_methods[] = {
JS_FN(
"imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE),
JS_FN(
"exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE),
JS_FN(
"customSections", WasmModuleObject::customSections, 2,
JSPROP_ENUMERATE),
JS_FS_END,
};
/* static */
void WasmModuleObject::finalize(JS::GCContext* gcx, JSObject* obj) {
const Module& module = obj->as<WasmModuleObject>().module();
size_t codeMemory = module.tier1CodeMemoryUsed();
if (codeMemory) {
obj->zone()->decJitMemory(codeMemory);
}
gcx->release(obj, &module, module.gcMallocBytesExcludingCode(),
MemoryUse::WasmModule);
}
static bool IsModuleObject(JSObject* obj,
const Module** module) {
WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>();
if (!mobj) {
return false;
}
*module = &mobj->module();
return true;
}
static bool GetModuleArg(JSContext* cx,
const CallArgs& args,
uint32_t numRequired,
const char* name,
const Module** module) {
if (!args.requireAtLeast(cx, name, numRequired)) {
return false;
}
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_MOD_ARG);
return false;
}
return true;
}
struct KindNames {
Rooted<PropertyName*> kind;
Rooted<PropertyName*> table;
Rooted<PropertyName*> memory;
Rooted<PropertyName*> tag;
Rooted<PropertyName*> type;
explicit KindNames(JSContext* cx)
: kind(cx), table(cx), memory(cx), tag(cx), type(cx) {}
};
static bool InitKindNames(JSContext* cx, KindNames* names) {
JSAtom* kind = Atomize(cx,
"kind", strlen(
"kind"));
if (!kind) {
return false;
}
names->kind = kind->asPropertyName();
JSAtom* table = Atomize(cx,
"table", strlen(
"table"));
if (!table) {
return false;
}
names->table = table->asPropertyName();
JSAtom* memory = Atomize(cx,
"memory", strlen(
"memory"));
if (!memory) {
return false;
}
names->memory = memory->asPropertyName();
JSAtom* tag = Atomize(cx,
"tag", strlen(
"tag"));
if (!tag) {
return false;
}
names->tag = tag->asPropertyName();
JSAtom* type = Atomize(cx,
"type", strlen(
"type"));
if (!type) {
return false;
}
names->type = type->asPropertyName();
return true;
}
static JSString* KindToString(JSContext* cx,
const KindNames& names,
DefinitionKind kind) {
switch (kind) {
case DefinitionKind::Function:
return cx->names().function;
case DefinitionKind::Table:
return names.table;
case DefinitionKind::Memory:
return names.memory;
case DefinitionKind::Global:
return cx->names().global;
case DefinitionKind::Tag:
return names.tag;
}
MOZ_CRASH(
"invalid kind");
}
/* static */
bool WasmModuleObject::imports(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
const Module* module;
if (!GetModuleArg(cx, args, 1,
"WebAssembly.Module.imports", &module)) {
return false;
}
KindNames names(cx);
if (!InitKindNames(cx, &names)) {
return false;
}
const ModuleMetadata& moduleMeta = module->moduleMeta();
RootedValueVector elems(cx);
if (!elems.reserve(moduleMeta.imports.length())) {
return false;
}
#if defined(ENABLE_WASM_JS_STRING_BUILTINS) || \
defined(ENABLE_WASM_TYPE_REFLECTIONS)
const CodeMetadata& codeMeta = module->codeMeta();
#endif
#if defined(ENABLE_WASM_TYPE_REFLECTIONS)
size_t numFuncImport = 0;
size_t numMemoryImport = 0;
size_t numGlobalImport = 0;
size_t numTableImport = 0;
size_t numTagImport = 0;
#endif // ENABLE_WASM_TYPE_REFLECTIONS
for (
const Import& import : moduleMeta.imports) {
#ifdef ENABLE_WASM_JS_STRING_BUILTINS
Maybe<BuiltinModuleId> builtinModule = ImportMatchesBuiltinModule(
import.module.utf8Bytes(), codeMeta.features().builtinModules);
if (builtinModule) {
continue;
}
#endif
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (!props.reserve(3)) {
return false;
}
JSString* moduleStr = import.module.toAtom(cx);
if (!moduleStr) {
return false;
}
props.infallibleAppend(
IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));
JSString* nameStr = import.field.toAtom(cx);
if (!nameStr) {
return false;
}
props.infallibleAppend(
IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
JSString* kindStr = KindToString(cx, names, import.kind);
if (!kindStr) {
return false;
}
props.infallibleAppend(
IdValuePair(NameToId(names.kind), StringValue(kindStr)));
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
RootedObject typeObj(cx);
switch (import.kind) {
case DefinitionKind::Function: {
size_t funcIndex = numFuncImport++;
const FuncType& funcType = codeMeta.getFuncType(funcIndex);
typeObj = FuncTypeToObject(cx, funcType);
break;
}
case DefinitionKind::Table: {
size_t tableIndex = numTableImport++;
const TableDesc& table = codeMeta.tables[tableIndex];
typeObj =
TableTypeToObject(cx, table.addressType(), table.elemType,
table.initialLength(), table.maximumLength());
break;
}
case DefinitionKind::Memory: {
size_t memoryIndex = numMemoryImport++;
const MemoryDesc& memory = codeMeta.memories[memoryIndex];
typeObj =
MemoryTypeToObject(cx, memory.isShared(), memory.addressType(),
memory.initialPages(), memory.maximumPages());
break;
}
case DefinitionKind::Global: {
size_t globalIndex = numGlobalImport++;
const GlobalDesc& global = codeMeta.globals[globalIndex];
typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable());
break;
}
case DefinitionKind::Tag: {
size_t tagIndex = numTagImport++;
const TagDesc& tag = codeMeta.tags[tagIndex];
typeObj = TagTypeToObject(cx, tag.type->argTypes());
break;
}
}
if (!typeObj || !props.append(IdValuePair(NameToId(names.type),
ObjectValue(*typeObj)))) {
ReportOutOfMemory(cx);
return false;
}
#endif // ENABLE_WASM_TYPE_REFLECTIONS
JSObject* obj = NewPlainObjectWithUniqueNames(cx, props);
if (!obj) {
return false;
}
elems.infallibleAppend(ObjectValue(*obj));
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr) {
return false;
}
args.rval().setObject(*arr);
return true;
}
/* static */
bool WasmModuleObject::exports(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
const Module* module;
if (!GetModuleArg(cx, args, 1,
"WebAssembly.Module.exports", &module)) {
return false;
}
KindNames names(cx);
if (!InitKindNames(cx, &names)) {
return false;
}
const ModuleMetadata& moduleMeta = module->moduleMeta();
RootedValueVector elems(cx);
if (!elems.reserve(moduleMeta.exports.length())) {
return false;
}
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
const CodeMetadata& codeMeta = module->codeMeta();
#endif // ENABLE_WASM_TYPE_REFLECTIONS
for (
const Export& exp : moduleMeta.exports) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (!props.reserve(2)) {
return false;
}
JSString* nameStr = exp.fieldName().toAtom(cx);
if (!nameStr) {
return false;
}
props.infallibleAppend(
IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
JSString* kindStr = KindToString(cx, names, exp.kind());
if (!kindStr) {
return false;
}
props.infallibleAppend(
IdValuePair(NameToId(names.kind), StringValue(kindStr)));
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
RootedObject typeObj(cx);
switch (exp.kind()) {
case DefinitionKind::Function: {
const FuncType& funcType =
module->codeMeta().getFuncType(exp.funcIndex());
typeObj = FuncTypeToObject(cx, funcType);
break;
}
case DefinitionKind::Table: {
const TableDesc& table = codeMeta.tables[exp.tableIndex()];
typeObj =
TableTypeToObject(cx, table.addressType(), table.elemType,
table.initialLength(), table.maximumLength());
break;
}
case DefinitionKind::Memory: {
const MemoryDesc& memory = codeMeta.memories[exp.memoryIndex()];
typeObj =
MemoryTypeToObject(cx, memory.isShared(), memory.addressType(),
memory.initialPages(), memory.maximumPages());
break;
}
case DefinitionKind::Global: {
const GlobalDesc& global = codeMeta.globals[exp.globalIndex()];
typeObj = GlobalTypeToObject(cx, global.type(), global.isMutable());
break;
}
case DefinitionKind::Tag: {
const TagDesc& tag = codeMeta.tags[exp.tagIndex()];
typeObj = TagTypeToObject(cx, tag.type->argTypes());
break;
}
}
if (!typeObj || !props.append(IdValuePair(NameToId(names.type),
ObjectValue(*typeObj)))) {
ReportOutOfMemory(cx);
return false;
}
#endif // ENABLE_WASM_TYPE_REFLECTIONS
JSObject* obj = NewPlainObjectWithUniqueNames(cx, props);
if (!obj) {
return false;
}
elems.infallibleAppend(ObjectValue(*obj));
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr) {
return false;
}
args.rval().setObject(*arr);
return true;
}
/* static */
bool WasmModuleObject::customSections(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
const Module* module;
if (!GetModuleArg(cx, args, 2,
"WebAssembly.Module.customSections",
&module)) {
return false;
}
Vector<
char, 8> name(cx);
{
RootedString str(cx, ToString(cx, args.get(1)));
if (!str) {
return false;
}
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
if (!name.initLengthUninitialized(
JS::GetDeflatedUTF8StringLength(linear))) {
return false;
}
(
void)JS::DeflateStringToUTF8Buffer(linear,
Span(name.begin(), name.length()));
}
RootedValueVector elems(cx);
Rooted<ArrayBufferObject*> buf(cx);
for (
const CustomSection& cs : module->moduleMeta().customSections) {
if (name.length() != cs.name.length()) {
continue;
}
if (memcmp(name.begin(), cs.name.begin(), name.length()) != 0) {
continue;
}
buf = ArrayBufferObject::createZeroed(cx, cs.payload->length());
if (!buf) {
return false;
}
memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length());
if (!elems.append(ObjectValue(*buf))) {
return false;
}
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr) {
return false;
}
args.rval().setObject(*arr);
return true;
}
/* static */
WasmModuleObject* WasmModuleObject::create(JSContext* cx,
const Module& module,
HandleObject proto) {
AutoSetNewObjectMetadata metadata(cx);
auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
if (!obj) {
return nullptr;
}
// The pipeline state on some architectures may retain stale instructions
// even after we invalidate the instruction cache. There is no generally
// available method to broadcast this pipeline flush to all threads after
// we've compiled new code, so conservatively perform one here when we're
// receiving a module that may have been compiled from another thread.
//
// The cost of this flush is expected to minimal enough to not be worth
// optimizing away in the case the module was compiled on this thread.
jit::FlushExecutionContext();
// This accounts for module allocation size (excluding code which is handled
// separately - see below). This assumes that the size of associated data
// doesn't change for the life of the WasmModuleObject. The size is counted
// once per WasmModuleObject referencing a Module.
InitReservedSlot(obj, MODULE_SLOT,
const_cast<Module*>(&module),
module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule);
module.AddRef();
// Bug 1569888: We account for the first tier here; the second tier, if
// different, also needs to be accounted for.
size_t codeMemory = module.tier1CodeMemoryUsed();
if (codeMemory) {
cx->zone()->incJitMemory(codeMemory);
}
return obj;
}
static bool GetBufferSource(JSContext* cx, JSObject* obj,
unsigned errorNumber,
MutableBytes* bytecode) {
*bytecode = cx->new_<ShareableBytes>();
if (!*bytecode) {
return false;
}
JSObject* unwrapped = CheckedUnwrapStatic(obj);
SharedMem<uint8_t*> dataPointer;
size_t byteLength;
if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
return false;
}
if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
static bool ReportCompileWarnings(JSContext* cx,
const UniqueCharsVector& warnings) {
// Avoid spamming the console.
size_t numWarnings = std::min<size_t>(warnings.length(), 3);
for (size_t i = 0; i < numWarnings; i++) {
if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) {
return false;
}
}
if (warnings.length() > numWarnings) {
if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING,
"other warnings suppressed")) {
return false;
}
}
return true;
}
/* static */
bool WasmModuleObject::construct(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs callArgs = CallArgsFromVp(argc, vp);
Log(cx,
"sync new Module() started");
if (!ThrowIfNotConstructing(cx, callArgs,
"Module")) {
return false;
}
JS::RootedVector<JSString*> parameterStrings(cx);
JS::RootedVector<Value> parameterArgs(cx);
bool canCompileStrings =
false;
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
JS::CompilationType::Undefined,
parameterStrings, nullptr, parameterArgs,
NullHandleValue, &canCompileStrings)) {
return false;
}
if (!canCompileStrings) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CSP_BLOCKED_WASM,
"WebAssembly.Module");
return false;
}
if (!callArgs.requireAtLeast(cx,
"WebAssembly.Module", 1)) {
return false;
}
if (!callArgs[0].isObject()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_BUF_ARG);
return false;
}
MutableBytes bytecode;
if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
&bytecode)) {
return false;
}
FeatureOptions options;
if (!options.init(cx, callArgs.get(1))) {
return false;
}
SharedCompileArgs compileArgs =
InitCompileArgs(cx, options,
"WebAssembly.Module");
if (!compileArgs) {
return false;
}
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
if (!ReportCompileWarnings(cx, warnings)) {
return false;
}
if (!module) {
if (error) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_COMPILE_ERROR, error.get());
return false;
}
return ThrowCompileOutOfMemory(cx);
}
RootedObject proto(
cx, GetWasmConstructorPrototype(cx, callArgs, JSProto_WasmModule));
if (!proto) {
ReportOutOfMemory(cx);
return false;
}
RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
if (!moduleObj) {
return false;
}
Log(cx,
"sync new Module() succeded");
callArgs.rval().setObject(*moduleObj);
return true;
}
const Module& WasmModuleObject::module()
const {
MOZ_ASSERT(is<WasmModuleObject>());
return *(
const Module*)getReservedSlot(MODULE_SLOT).toPrivate();
}
// ============================================================================
// WebAssembly.Instance class and methods
const JSClassOps WasmInstanceObject::classOps_ = {
nullptr,
// addProperty
nullptr,
// delProperty
nullptr,
// enumerate
nullptr,
// newEnumerate
nullptr,
// resolve
nullptr,
// mayResolve
WasmInstanceObject::finalize,
// finalize
nullptr,
// call
nullptr,
// construct
WasmInstanceObject::trace,
// trace
};
const JSClass WasmInstanceObject::class_ = {
"WebAssembly.Instance",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmInstanceObject::classOps_,
&WasmInstanceObject::classSpec_,
};
const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;
static constexpr
char WasmInstanceName[] =
"Instance";
const ClassSpec WasmInstanceObject::classSpec_ = {
CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>,
GenericCreatePrototype<WasmInstanceObject>,
WasmInstanceObject::static_methods,
nullptr,
WasmInstanceObject::methods,
WasmInstanceObject::properties,
nullptr,
ClassSpec::DontDefineConstructor,
};
static bool IsInstance(HandleValue v) {
return v.isObject() && v.toObject().is<WasmInstanceObject>();
}
/* static */
bool WasmInstanceObject::exportsGetterImpl(JSContext* cx,
const CallArgs& args) {
args.rval().setObject(
args.thisv().toObject().as<WasmInstanceObject>().exportsObj());
return true;
}
/* static */
bool WasmInstanceObject::exportsGetter(JSContext* cx,
unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args);
}
const JSPropertySpec WasmInstanceObject::properties[] = {
JS_PSG(
"exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE),
JS_STRING_SYM_PS(toStringTag,
"WebAssembly.Instance", JSPROP_READONLY),
JS_PS_END,
};
const JSFunctionSpec WasmInstanceObject::methods[] = {
JS_FS_END,
};
const JSFunctionSpec WasmInstanceObject::static_methods[] = {
JS_FS_END,
};
bool WasmInstanceObject::isNewborn()
const {
MOZ_ASSERT(is<WasmInstanceObject>());
return getReservedSlot(INSTANCE_SLOT).isUndefined();
}
// WeakScopeMap maps from function index to js::Scope. This maps is weak
// to avoid holding scope objects alive. The scopes are normally created
// during debugging.
//
// This is defined here in order to avoid recursive dependency between
// WasmJS.h and Scope.h.
using WasmFunctionScopeMap =
JS::WeakCache<GCHashMap<uint32_t, WeakHeapPtr<WasmFunctionScope*>,
DefaultHasher<uint32_t>, CellAllocPolicy>>;
class WasmInstanceObject::UnspecifiedScopeMap {
public:
WasmFunctionScopeMap& asWasmFunctionScopeMap() {
return *(WasmFunctionScopeMap*)
this;
}
};
/* static */
void WasmInstanceObject::finalize(JS::GCContext* gcx, JSObject* obj) {
WasmInstanceObject& instance = obj->as<WasmInstanceObject>();
gcx->delete_(obj, &instance.scopes().asWasmFunctionScopeMap(),
MemoryUse::WasmInstanceScopes);
gcx->delete_(obj, &instance.indirectGlobals(),
MemoryUse::WasmInstanceGlobals);
if (!instance.isNewborn()) {
if (instance.instance().debugEnabled()) {
instance.instance().debug().finalize(gcx);
}
Instance::destroy(&instance.instance());
gcx->removeCellMemory(obj,
sizeof(Instance),
MemoryUse::WasmInstanceInstance);
}
}
/* static */
void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) {
WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
instanceObj.indirectGlobals().trace(trc);
if (!instanceObj.isNewborn()) {
instanceObj.instance().tracePrivate(trc);
}
}
/* static */
WasmInstanceObject* WasmInstanceObject::create(
JSContext* cx,
const SharedCode& code,
const DataSegmentVector& dataSegments,
const ModuleElemSegmentVector& elemSegments, uint32_t instanceDataLength,
Handle<WasmMemoryObjectVector> memories, SharedTableVector&& tables,
const JSObjectVector& funcImports,
const GlobalDescVector& globals,
const ValVector& globalImportValues,
const WasmGlobalObjectVector& globalObjs,
const WasmTagObjectVector& tagObjs, HandleObject proto,
UniqueDebugState maybeDebug) {
UniquePtr<WasmFunctionScopeMap> scopes =
js::MakeUnique<WasmFunctionScopeMap>(cx->zone(), cx->zone());
if (!scopes) {
ReportOutOfMemory(cx);
return nullptr;
}
// Note that `scopes` is a WeakCache, auto-linked into a sweep list on the
// Zone, and so does not require rooting.
uint32_t indirectGlobals = 0;
for (uint32_t i = 0; i < globalObjs.length(); i++) {
if (globalObjs[i] && globals[i].isIndirect()) {
indirectGlobals++;
}
}
Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs(
cx, js::MakeUnique<GlobalObjectVector>(cx->zone()));
if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) {
ReportOutOfMemory(cx);
return nullptr;
}
{
uint32_t next = 0;
for (uint32_t i = 0; i < globalObjs.length(); i++) {
if (globalObjs[i] && globals[i].isIndirect()) {
(*indirectGlobalObjs)[next++] = globalObjs[i];
}
}
}
Instance* instance = nullptr;
Rooted<WasmInstanceObject*> obj(cx);
{
// We must delay creating metadata for this object until after all its
// slots have been initialized. We must also create the metadata before
// calling Instance::init as that may allocate new objects.
AutoSetNewObjectMetadata metadata(cx);
obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
if (!obj) {
return nullptr;
}
MOZ_ASSERT(obj->isTenured(),
"assumed by WasmTableObject write barriers");
InitReservedSlot(obj, SCOPES_SLOT, scopes.release(),
MemoryUse::WasmInstanceScopes);
InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(),
MemoryUse::WasmInstanceGlobals);
obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
// The INSTANCE_SLOT may not be initialized if Instance allocation fails,
// leading to an observable "newborn" state in tracing/finalization.
MOZ_ASSERT(obj->isNewborn());
// Create this just before constructing Instance to avoid rooting hazards.
instance = Instance::create(cx, obj, code, instanceDataLength,
std::move(tables), std::move(maybeDebug));
if (!instance) {
return nullptr;
}
InitReservedSlot(obj, INSTANCE_SLOT, instance,
MemoryUse::WasmInstanceInstance);
MOZ_ASSERT(!obj->isNewborn());
}
if (!instance->init(cx, funcImports, globalImportValues, memories, globalObjs,
tagObjs, dataSegments, elemSegments)) {
return nullptr;
}
return obj;
}
void WasmInstanceObject::initExportsObj(JSObject& exportsObj) {
MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined());
setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj));
}
static bool GetImportArg(JSContext* cx, HandleValue importArg,
MutableHandleObject importObj) {
if (!importArg.isUndefined()) {
if (!importArg.isObject()) {
return ThrowBadImportArg(cx);
}
importObj.set(&importArg.toObject());
}
return true;
}
/* static */
bool WasmInstanceObject::construct(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Log(cx,
"sync new Instance() started");
if (!ThrowIfNotConstructing(cx, args,
"Instance")) {
return false;
}
if (!args.requireAtLeast(cx,
"WebAssembly.Instance", 1)) {
return false;
}
const Module* module;
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_MOD_ARG);
return false;
}
RootedObject importObj(cx);
if (!GetImportArg(cx, args.get(1), &importObj)) {
return false;
}
RootedObject proto(
cx, GetWasmConstructorPrototype(cx, args, JSProto_WasmInstance));
if (!proto) {
ReportOutOfMemory(cx);
return false;
}
Rooted<ImportValues> imports(cx);
if (!GetImports(cx, *module, importObj, imports.address())) {
return false;
}
Rooted<WasmInstanceObject*> instanceObj(cx);
if (!module->instantiate(cx, imports.get(), proto, &instanceObj)) {
return false;
}
Log(cx,
"sync new Instance() succeeded");
args.rval().setObject(*instanceObj);
return true;
}
Instance& WasmInstanceObject::instance()
const {
MOZ_ASSERT(!isNewborn());
return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
}
JSObject& WasmInstanceObject::exportsObj()
const {
return getReservedSlot(EXPORTS_OBJ_SLOT).toObject();
}
WasmInstanceObject::UnspecifiedScopeMap& WasmInstanceObject::scopes()
const {
return *(UnspecifiedScopeMap*)(getReservedSlot(SCOPES_SLOT).toPrivate());
}
WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals()
const {
return *(GlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate();
}
/* static */
bool WasmInstanceObject::getExportedFunction(
JSContext* cx, Handle<WasmInstanceObject*> instanceObj, uint32_t funcIndex,
MutableHandleFunction fun) {
Instance& instance = instanceObj->instance();
return instance.getExportedFunction(cx, funcIndex, fun);
}
/* static */
WasmInstanceScope* WasmInstanceObject::getScope(
JSContext* cx, Handle<WasmInstanceObject*> instanceObj) {
if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) {
return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT)
.toGCThing();
}
Rooted<WasmInstanceScope*> instanceScope(
cx, WasmInstanceScope::create(cx, instanceObj));
if (!instanceScope) {
return nullptr;
}
instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT,
PrivateGCThingValue(instanceScope));
return instanceScope;
}
/* static */
WasmFunctionScope* WasmInstanceObject::getFunctionScope(
JSContext* cx, Handle<WasmInstanceObject*> instanceObj,
uint32_t funcIndex) {
if (
auto p =
instanceObj->scopes().asWasmFunctionScopeMap().lookup(funcIndex)) {
return p->value();
}
Rooted<WasmInstanceScope*> instanceScope(
cx, WasmInstanceObject::getScope(cx, instanceObj));
if (!instanceScope) {
return nullptr;
}
Rooted<WasmFunctionScope*> funcScope(
cx, WasmFunctionScope::create(cx, instanceScope, funcIndex));
if (!funcScope) {
return nullptr;
}
if (!instanceObj->scopes().asWasmFunctionScopeMap().putNew(funcIndex,
funcScope)) {
ReportOutOfMemory(cx);
return nullptr;
}
return funcScope;
}
// ============================================================================
// WebAssembly.Memory class and methods
const JSClassOps WasmMemoryObject::classOps_ = {
nullptr,
// addProperty
nullptr,
// delProperty
nullptr,
// enumerate
nullptr,
// newEnumerate
nullptr,
// resolve
nullptr,
// mayResolve
WasmMemoryObject::finalize,
// finalize
nullptr,
// call
nullptr,
// construct
nullptr,
// trace
};
const JSClass WasmMemoryObject::class_ = {
"WebAssembly.Memory",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
--> --------------------
--> maximum size reached
--> --------------------