Files
2026-08-11 00:10:12 +08:00

2067 lines
66 KiB
Rust

use super::*;
use crate::{
definitions::define_non_enumerable_bool_property,
util::{
get_private_object, get_private_value, new_null_prototype_object, private_key,
set_private_value, throw_range_error, throw_type_error, v8_string, v8str,
},
};
use moli_webapi_declare::WebApiObject;
mod limits;
const WASM_RUNTIME_INSTALLED_SLOT: &str = "__moliWebAssemblyRuntimeExtensions";
const ORIGINAL_MODULE_CTOR_SLOT: &str = "__moliOriginalWebAssemblyModule";
const ORIGINAL_INSTANCE_CTOR_SLOT: &str = "__moliOriginalWebAssemblyInstance";
const ORIGINAL_VALIDATE_FN_SLOT: &str = "__moliOriginalWebAssemblyValidate";
const ORIGINAL_COMPILE_FN_SLOT: &str = "__moliOriginalWebAssemblyCompile";
const ORIGINAL_INSTANTIATE_FN_SLOT: &str = "__moliOriginalWebAssemblyInstantiate";
const ORIGINAL_MEMORY_CTOR_SLOT: &str = "__moliOriginalWebAssemblyMemory";
const ORIGINAL_TABLE_CTOR_SLOT: &str = "__moliOriginalWebAssemblyTable";
const ORIGINAL_GLOBAL_CTOR_SLOT: &str = "__moliOriginalWebAssemblyGlobal";
const ORIGINAL_TAG_CTOR_SLOT: &str = "__moliOriginalWebAssemblyTag";
const ORIGINAL_EXCEPTION_CTOR_SLOT: &str = "__moliOriginalWebAssemblyException";
const MEMORY_TYPE_SLOT: &str = "__moliWebAssemblyMemoryType";
const TABLE_TYPE_SLOT: &str = "__moliWebAssemblyTableType";
const GLOBAL_TYPE_SLOT: &str = "__moliWebAssemblyGlobalType";
const TAG_TYPE_SLOT: &str = "__moliWebAssemblyTagType";
const MEMORY_BUFFER_GETTER_SLOT: &str = "__moliWebAssemblyMemoryBufferGetter";
const TABLE_LENGTH_GETTER_SLOT: &str = "__moliWebAssemblyTableLengthGetter";
const GLOBAL_VALUE_SETTER_SLOT: &str = "__moliWebAssemblyGlobalValueSetter";
const GLOBAL_VALUE_SETTER_WRAPPED_SLOT: &str = "__moliAcceptsMissingArgument";
const EXCEPTION_GET_ARG_SLOT: &str = "__moliWebAssemblyExceptionGetArg";
const STREAMING_OPTIONS_SLOT: &str = "__moliWebAssemblyStreamingOptions";
const STREAMING_IMPORT_OBJECT_SLOT: &str = "__moliWebAssemblyStreamingImportObject";
const MODULE_INSTANTIATION_EXCEEDS_V8_LIMIT_SLOT: &str =
"__moliWebAssemblyInstantiationExceedsV8Limit";
const WEBASSEMBLY_DEFAULT_PROTOTYPES_SLOT: &str = "__moliWebAssemblyDefaultPrototypes";
const WEBASSEMBLY_DEFAULT_PROTOTYPE_NAMES: &[&str] = &[
"Module",
"Instance",
"Memory",
"Table",
"Global",
"Tag",
"Exception",
"Function",
"CompileError",
"LinkError",
"RuntimeError",
];
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyStreamingDataDeclaration<'scope> {
#[webapi(slot = STREAMING_OPTIONS_SLOT)]
options: v8::Local<'scope, v8::Value>,
#[webapi(slot = STREAMING_IMPORT_OBJECT_SLOT)]
import_object: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyInstantiateStreamingResultDeclaration<'scope> {
#[webapi(data_property, enumerable)]
module: v8::Local<'scope, v8::Object>,
#[webapi(data_property, enumerable)]
instance: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyMemoryNativeDescriptorDeclaration<'scope> {
#[webapi(data_property, enumerable)]
initial: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
shared: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyMemoryTypeDeclaration<'scope> {
#[webapi(data_property, enumerable)]
minimum: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: Option<v8::Local<'scope, v8::Value>>,
#[webapi(data_property, enumerable)]
shared: Option<v8::Local<'scope, v8::Value>>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyMemoryTypeCloneDeclaration<'scope> {
#[webapi(data_property, enumerable)]
minimum: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: Option<v8::Local<'scope, v8::Value>>,
#[webapi(data_property, enumerable)]
shared: Option<v8::Local<'scope, v8::Value>>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTableNativeDescriptorDeclaration<'scope> {
#[webapi(data_property, enumerable)]
element: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
initial: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTableTypeDeclaration<'scope> {
#[webapi(data_property, enumerable)]
minimum: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: Option<v8::Local<'scope, v8::Value>>,
#[webapi(data_property, enumerable)]
element: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTableTypeCloneDeclaration<'scope> {
#[webapi(data_property, enumerable)]
minimum: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
maximum: Option<v8::Local<'scope, v8::Value>>,
#[webapi(data_property, enumerable)]
element: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyGlobalNativeDescriptorDeclaration<'scope> {
#[webapi(data_property, enumerable)]
mutable: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
value: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyGlobalTypeDeclaration<'scope> {
#[webapi(data_property, enumerable)]
mutable: bool,
#[webapi(data_property, enumerable)]
value: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyGlobalTypeCloneDeclaration<'scope> {
#[webapi(data_property, enumerable)]
r#mutable: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
value: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTagTypeDeclaration<'scope> {
#[webapi(data_property, enumerable)]
parameters: v8::Local<'scope, v8::Array>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyMemoryFallbackTypeDeclaration {
#[webapi(data_property, enumerable)]
minimum: f64,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTableFallbackTypeDeclaration<'scope> {
#[webapi(data_property, enumerable)]
minimum: f64,
#[webapi(data_property, enumerable)]
element: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct WebAssemblyTagTypeCloneDeclaration<'scope> {
#[webapi(data_property, enumerable)]
parameters: Option<v8::Local<'scope, v8::Array>>,
}
pub(in crate::context_bootstrap) fn install_webassembly_runtime_extensions(
scope: &mut v8::PinScope<'_, '_>,
) -> Result<()> {
let Some(webassembly) = webassembly_namespace(scope) else {
return Ok(());
};
if runtime_already_installed(scope, webassembly) {
return Ok(());
}
install_streaming_functions(scope, webassembly)?;
install_v8_implementation_limit_compatibility(scope, webassembly)?;
install_memory_constructor(scope, webassembly)?;
install_table_constructor(scope, webassembly)?;
install_global_constructor(scope, webassembly)?;
install_tag_constructor(scope, webassembly)?;
install_function_shape(scope, webassembly);
install_exception_shape(scope, webassembly)?;
define_non_enumerable_bool_property(scope, webassembly, WASM_RUNTIME_INSTALLED_SLOT, true);
Ok(())
}
pub(in crate::context_bootstrap) fn capture_webassembly_default_prototypes<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
webassembly: v8::Local<'s, v8::Object>,
) {
let prototypes = new_null_prototype_object(scope);
for name in WEBASSEMBLY_DEFAULT_PROTOTYPE_NAMES {
let Some(prototype) = webassembly_constructor(scope, webassembly, name)
.and_then(|constructor| constructor.get(scope, v8str(scope, "prototype").into()))
.and_then(|prototype| v8::Local::<v8::Object>::try_from(prototype).ok())
else {
continue;
};
let _ = prototypes.create_data_property(scope, v8str(scope, name).into(), prototype.into());
}
set_private_value(
scope,
global,
WEBASSEMBLY_DEFAULT_PROTOTYPES_SLOT,
prototypes.into(),
);
}
pub(crate) fn set_current_context_webassembly_default_prototype(
scope: &mut v8::PinScope<'_, '_>,
name: &str,
prototype: v8::Local<'_, v8::Object>,
) {
let Some(name) = v8_string(scope, name) else {
return;
};
let global = scope.get_current_context().global(scope);
let prototypes = get_private_object(scope, global, WEBASSEMBLY_DEFAULT_PROTOTYPES_SLOT)
.unwrap_or_else(|| {
let prototypes = new_null_prototype_object(scope);
set_private_value(
scope,
global,
WEBASSEMBLY_DEFAULT_PROTOTYPES_SLOT,
prototypes.into(),
);
prototypes
});
let _ = prototypes.create_data_property(scope, name.into(), prototype.into());
}
pub(crate) fn webassembly_default_prototype_for_context<'s>(
scope: &mut v8::PinScope<'s, '_>,
context: v8::Local<'s, v8::Context>,
name: &str,
) -> Option<v8::Local<'s, v8::Object>> {
let prototype = {
let context_scope = &mut v8::ContextScope::new(scope, context);
let name = v8_string(context_scope, name)?;
let global = context.global(context_scope);
let prototypes =
get_private_object(context_scope, global, WEBASSEMBLY_DEFAULT_PROTOTYPES_SLOT)?;
let prototype = prototypes
.get(context_scope, name.into())
.and_then(|prototype| v8::Local::<v8::Object>::try_from(prototype).ok())?;
v8::Global::new(context_scope, prototype)
};
Some(v8::Local::new(scope, &prototype))
}
fn webassembly_namespace<'s>(
scope: &mut v8::PinScope<'s, '_>,
) -> Option<v8::Local<'s, v8::Object>> {
scope
.get_current_context()
.global(scope)
.get(scope, v8str(scope, "WebAssembly").into())
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
}
fn runtime_already_installed(
scope: &mut v8::PinScope<'_, '_>,
webassembly: v8::Local<'_, v8::Object>,
) -> bool {
webassembly
.get(scope, v8str(scope, WASM_RUNTIME_INSTALLED_SLOT).into())
.is_some_and(|value| value.boolean_value(scope))
}
fn webassembly_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
name: &'static str,
) -> Option<v8::Local<'s, v8::Function>> {
webassembly
.get(scope, v8str(scope, name).into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
}
fn original_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
slot: &'static str,
fallback_name: &'static str,
) -> Option<v8::Local<'s, v8::Function>> {
let global = scope.get_current_context().global(scope);
get_private_value(scope, global, slot)
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
.or_else(|| {
webassembly_namespace(scope)
.and_then(|wa| webassembly_constructor(scope, wa, fallback_name))
})
}
fn store_original_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
name: &'static str,
slot: &'static str,
) -> Option<v8::Local<'s, v8::Function>> {
let constructor = webassembly_constructor(scope, webassembly, name)?;
let global = scope.get_current_context().global(scope);
set_private_value(scope, global, slot, constructor.into());
Some(constructor)
}
fn original_namespace_function<'s>(
scope: &mut v8::PinScope<'s, '_>,
slot: &'static str,
) -> Option<v8::Local<'s, v8::Function>> {
let global = scope.get_current_context().global(scope);
get_private_value(scope, global, slot)
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
}
fn store_original_namespace_function<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
name: &'static str,
slot: &'static str,
) -> Option<v8::Local<'s, v8::Function>> {
let function = webassembly
.get(scope, v8str(scope, name).into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
let global = scope.get_current_context().global(scope);
set_private_value(scope, global, slot, function.into());
Some(function)
}
fn define_value_property(
scope: &mut v8::PinScope<'_, '_>,
object: v8::Local<'_, v8::Object>,
name: &'static str,
value: v8::Local<'_, v8::Value>,
writable: bool,
enumerable: bool,
configurable: bool,
) -> Result<()> {
let mut descriptor = v8::PropertyDescriptor::new_from_value_writable(value, writable);
descriptor.set_enumerable(enumerable);
descriptor.set_configurable(configurable);
object
.define_property(scope, v8str(scope, name).into(), &descriptor)
.unwrap_or(false)
.then_some(())
.ok_or_else(|| anyhow!("failed to define WebAssembly property `{name}`"))
}
fn define_accessor_property(
scope: &mut v8::PinScope<'_, '_>,
object: v8::Local<'_, v8::Object>,
name: &'static str,
getter: v8::Local<'_, v8::Value>,
setter: v8::Local<'_, v8::Value>,
enumerable: bool,
configurable: bool,
) -> Result<()> {
let mut descriptor = v8::PropertyDescriptor::new_from_get_set(getter, setter);
descriptor.set_enumerable(enumerable);
descriptor.set_configurable(configurable);
object
.define_property(scope, v8str(scope, name).into(), &descriptor)
.unwrap_or(false)
.then_some(())
.ok_or_else(|| anyhow!("failed to define WebAssembly accessor `{name}`"))
}
fn install_namespace_function(
scope: &mut v8::PinScope<'_, '_>,
webassembly: v8::Local<'_, v8::Object>,
name: &'static str,
length: i32,
callback: impl v8::MapFnTo<v8::FunctionCallback>,
) -> Result<()> {
let function = v8::Function::builder(callback)
.length(length)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly.{name}"))?;
function.set_name(v8str(scope, name));
define_value_property(scope, webassembly, name, function.into(), true, true, true)
}
fn install_namespace_function_wrapper(
scope: &mut v8::PinScope<'_, '_>,
webassembly: v8::Local<'_, v8::Object>,
name: &'static str,
length: i32,
callback: impl v8::MapFnTo<v8::FunctionCallback>,
) -> Result<()> {
let function = v8::Function::builder(callback)
.length(length)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly.{name} wrapper"))?;
function.set_name(v8str(scope, name));
define_value_property(scope, webassembly, name, function.into(), true, true, true)
}
fn copy_constructor_static_function(
scope: &mut v8::PinScope<'_, '_>,
source: v8::Local<'_, v8::Function>,
target: v8::Local<'_, v8::Function>,
name: &'static str,
) -> Result<()> {
let Some(value) = source
.get(scope, v8str(scope, name).into())
.filter(|value| value.is_function())
else {
return Ok(());
};
define_value_property(scope, target.into(), name, value, true, true, true)
}
fn install_prototype_method<'s>(
scope: &mut v8::PinScope<'s, '_>,
prototype: v8::Local<'s, v8::Object>,
name: &'static str,
length: i32,
callback: impl v8::MapFnTo<v8::FunctionCallback>,
) -> Result<v8::Local<'s, v8::Function>> {
let function = v8::Function::builder(callback)
.length(length)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly prototype method `{name}`"))?;
function.set_name(v8str(scope, name));
define_value_property(scope, prototype, name, function.into(), true, false, true)?;
Ok(function)
}
fn install_constructor_wrapper<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
name: &'static str,
length: i32,
native_constructor: v8::Local<'s, v8::Function>,
callback: impl v8::MapFnTo<v8::FunctionCallback>,
) -> Result<v8::Local<'s, v8::Function>> {
let wrapper = v8::Function::builder(callback)
.length(length)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly.{name} wrapper"))?;
wrapper.set_name(v8str(scope, name));
if let Some(function_prototype) = native_constructor.get_prototype(scope) {
let _ = wrapper.set_prototype(scope, function_prototype);
}
let Some(native_prototype) = native_constructor
.get(scope, v8str(scope, "prototype").into())
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
define_value_property(scope, webassembly, name, wrapper.into(), true, false, true)?;
return Ok(wrapper);
};
define_value_property(
scope,
wrapper.into(),
"prototype",
native_prototype.into(),
false,
false,
false,
)?;
define_value_property(
scope,
native_prototype,
"constructor",
wrapper.into(),
true,
false,
true,
)?;
define_value_property(scope, webassembly, name, wrapper.into(), true, false, true)?;
Ok(wrapper)
}
fn constructor_prototype<'s>(
scope: &mut v8::PinScope<'s, '_>,
constructor: v8::Local<'s, v8::Function>,
) -> Option<v8::Local<'s, v8::Object>> {
constructor
.get(scope, v8str(scope, "prototype").into())
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
}
fn is_dictionary_like(value: v8::Local<'_, v8::Value>) -> bool {
!value.is_null_or_undefined() && (value.is_object() || value.is_function())
}
fn object_from_dictionary_like<'s>(
value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Object>> {
if !is_dictionary_like(value) {
return None;
}
v8::Local::<v8::Object>::try_from(value).ok()
}
fn object_property<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
name: &'static str,
) -> v8::Local<'s, v8::Value> {
object
.get(scope, v8str(scope, name).into())
.unwrap_or_else(|| v8::undefined(scope).into())
}
fn has_defined_property(
scope: &mut v8::PinScope<'_, '_>,
object: v8::Local<'_, v8::Object>,
name: &'static str,
) -> bool {
let key = v8str(scope, name);
object.has(scope, key.into()).unwrap_or(false)
&& object
.get(scope, key.into())
.is_some_and(|value| !value.is_undefined())
}
fn type_error_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
message: &'static str,
) -> Option<v8::Local<'s, v8::Value>> {
let message = v8_string(scope, message)?;
Some(v8::Exception::type_error(scope, message))
}
fn promise_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
) -> Option<v8::Local<'s, v8::Function>> {
scope
.get_current_context()
.global(scope)
.get(scope, v8str(scope, "Promise").into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
}
fn promise_resolve<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Object>> {
let promise = promise_constructor(scope)?;
let resolve = promise
.get(scope, v8str(scope, "resolve").into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
resolve
.call(scope, promise.into(), &[value])
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
}
fn promise_reject<'s>(
scope: &mut v8::PinScope<'s, '_>,
reason: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Value>> {
let promise = promise_constructor(scope)?;
let reject = promise
.get(scope, v8str(scope, "reject").into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
reject.call(scope, promise.into(), &[reason])
}
fn promise_then<'s>(
scope: &mut v8::PinScope<'s, '_>,
promise: v8::Local<'s, v8::Object>,
on_fulfilled: v8::Local<'s, v8::Function>,
) -> Option<v8::Local<'s, v8::Value>> {
let then = promise
.get(scope, v8str(scope, "then").into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
then.call(scope, promise.into(), &[on_fulfilled.into()])
}
fn promise_resolve_then<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
on_fulfilled: v8::Local<'s, v8::Function>,
) -> Option<v8::Local<'s, v8::Value>> {
let promise = promise_resolve(scope, value)?;
promise_then(scope, promise, on_fulfilled)
}
struct NormalizedWasmArgument<'s> {
value: v8::Local<'s, v8::Value>,
instantiation_exceeds_v8_limit: bool,
}
fn normalized_wasm_argument<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<NormalizedWasmArgument<'s>> {
if value.is_shared_array_buffer()
|| v8::Local::<v8::ArrayBufferView>::try_from(value)
.ok()
.and_then(|view| view.get_backing_store())
.is_some_and(|store| store.is_shared())
{
return None;
}
let bytes = crate::blob::buffer_source_bytes_from_value(scope, value)?;
let normalized = limits::normalize_v8_implementation_limits(&bytes)?;
let byte_length = normalized.bytes.len();
let buffer = crate::blob::array_buffer_from_bytes(scope, normalized.bytes)?;
let view = v8::Uint8Array::new(scope, buffer, 0, byte_length)?;
Some(NormalizedWasmArgument {
value: view.into(),
instantiation_exceeds_v8_limit: normalized.instantiation_exceeds_v8_limit,
})
}
fn forwarded_namespace_arguments<'s>(
args: &v8::FunctionCallbackArguments<'s>,
first: v8::Local<'s, v8::Value>,
) -> Vec<v8::Local<'s, v8::Value>> {
let mut forwarded = Vec::with_capacity(args.length().max(1) as usize);
forwarded.push(first);
for index in 1..args.length() {
forwarded.push(args.get(index));
}
forwarded
}
fn call_original_namespace_function<'s>(
scope: &mut v8::PinScope<'s, '_>,
slot: &'static str,
forwarded: &[v8::Local<'s, v8::Value>],
) -> Option<v8::Local<'s, v8::Value>> {
let function = original_namespace_function(scope, slot)?;
let receiver = webassembly_namespace(scope)?;
function.call(scope, receiver.into(), forwarded)
}
pub(crate) fn module_instantiation_exceeds_v8_limit<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'_, v8::Value>,
) -> bool {
let Ok(module) = v8::Local::<v8::Object>::try_from(value) else {
return false;
};
let Some(key) = private_key(scope, MODULE_INSTANTIATION_EXCEEDS_V8_LIMIT_SLOT) else {
return false;
};
module
.get_private(scope, key)
.is_some_and(|value| !value.is_undefined() && value.boolean_value(scope))
}
pub(crate) fn mark_module_instantiation_exceeds_v8_limit<'s>(
scope: &mut v8::PinScope<'s, '_>,
module: v8::Local<'s, v8::Object>,
) {
let marker = v8::Boolean::new(scope, true);
set_private_value(
scope,
module,
MODULE_INSTANTIATION_EXCEEDS_V8_LIMIT_SLOT,
marker.into(),
);
}
fn reject_instantiation_exceeding_v8_limit<'s>(
scope: &mut v8::PinScope<'s, '_>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(message) = v8_string(
scope,
"WebAssembly resource declaration exceeds the implementation limit",
) else {
return;
};
let error = v8::Exception::range_error(scope, message);
let Some(resolver) = v8::PromiseResolver::new(scope) else {
return;
};
let _ = resolver.reject(scope, error);
rv.set(resolver.get_promise(scope).into());
}
fn install_v8_implementation_limit_compatibility<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let validate = store_original_namespace_function(
scope,
webassembly,
"validate",
ORIGINAL_VALIDATE_FN_SLOT,
);
let compile =
store_original_namespace_function(scope, webassembly, "compile", ORIGINAL_COMPILE_FN_SLOT);
let instantiate = store_original_namespace_function(
scope,
webassembly,
"instantiate",
ORIGINAL_INSTANTIATE_FN_SLOT,
);
if validate.is_some() {
install_namespace_function_wrapper(
scope,
webassembly,
"validate",
1,
validate_with_v8_limit_compatibility_callback,
)?;
}
if compile.is_some() {
install_namespace_function_wrapper(
scope,
webassembly,
"compile",
1,
compile_with_v8_limit_compatibility_callback,
)?;
}
if instantiate.is_some() {
install_namespace_function_wrapper(
scope,
webassembly,
"instantiate",
1,
instantiate_with_v8_limit_compatibility_callback,
)?;
}
if let Some(native_constructor) =
original_constructor(scope, ORIGINAL_MODULE_CTOR_SLOT, "Module")
{
let wrapper = install_constructor_wrapper(
scope,
webassembly,
"Module",
1,
native_constructor,
module_with_v8_limit_compatibility_callback,
)?;
for name in ["exports", "imports", "customSections"] {
copy_constructor_static_function(scope, native_constructor, wrapper, name)?;
}
}
if let Some(native_constructor) =
original_constructor(scope, ORIGINAL_INSTANCE_CTOR_SLOT, "Instance")
{
install_constructor_wrapper(
scope,
webassembly,
"Instance",
1,
native_constructor,
instance_with_v8_limit_guard_callback,
)?;
}
Ok(())
}
fn module_with_v8_limit_compatibility_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Module must be invoked with 'new'");
return;
}
let normalized = normalized_wasm_argument(scope, args.get(0));
let forwarded = if let Some(normalized) = &normalized {
forwarded_namespace_arguments(&args, normalized.value)
} else {
(0..args.length())
.map(|index| args.get(index))
.collect::<Vec<_>>()
};
let Some(module) = construct_native(
scope,
ORIGINAL_MODULE_CTOR_SLOT,
"Module",
&forwarded,
args.new_target(),
) else {
return;
};
if normalized.is_some_and(|normalized| normalized.instantiation_exceeds_v8_limit) {
mark_module_instantiation_exceeds_v8_limit(scope, module);
}
rv.set(module.into());
}
fn validate_with_v8_limit_compatibility_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let first = normalized_wasm_argument(scope, args.get(0))
.map_or_else(|| args.get(0), |normalized| normalized.value);
let forwarded = forwarded_namespace_arguments(&args, first);
if let Some(value) =
call_original_namespace_function(scope, ORIGINAL_VALIDATE_FN_SLOT, &forwarded)
{
rv.set(value);
}
}
fn compile_with_v8_limit_compatibility_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let normalized = normalized_wasm_argument(scope, args.get(0));
let first = normalized
.as_ref()
.map_or_else(|| args.get(0), |normalized| normalized.value);
let forwarded = forwarded_namespace_arguments(&args, first);
let Some(value) = call_original_namespace_function(scope, ORIGINAL_COMPILE_FN_SLOT, &forwarded)
else {
return;
};
if !normalized.is_some_and(|normalized| normalized.instantiation_exceeds_v8_limit) {
rv.set(value);
return;
}
let Ok(promise) = v8::Local::<v8::Promise>::try_from(value) else {
rv.set(value);
return;
};
let Some(on_fulfilled) =
v8::Function::builder(mark_compiled_module_v8_limit_callback).build(scope)
else {
return;
};
if let Some(marked) = promise.then(scope, on_fulfilled) {
rv.set(marked.into());
}
}
fn mark_compiled_module_v8_limit_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let module = args.get(0);
if let Ok(module_object) = v8::Local::<v8::Object>::try_from(module) {
mark_module_instantiation_exceeds_v8_limit(scope, module_object);
}
rv.set(module);
}
fn instantiate_with_v8_limit_compatibility_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
rv: v8::ReturnValue<'_, v8::Value>,
) {
let source = args.get(0);
if module_instantiation_exceeds_v8_limit(scope, source) {
reject_instantiation_exceeding_v8_limit(scope, rv);
return;
}
let normalized = normalized_wasm_argument(scope, source);
if normalized
.as_ref()
.is_some_and(|normalized| normalized.instantiation_exceeds_v8_limit)
{
reject_instantiation_exceeding_v8_limit(scope, rv);
return;
}
let first = normalized
.as_ref()
.map_or(source, |normalized| normalized.value);
let forwarded = forwarded_namespace_arguments(&args, first);
if let Some(value) =
call_original_namespace_function(scope, ORIGINAL_INSTANTIATE_FN_SLOT, &forwarded)
{
let mut rv = rv;
rv.set(value);
}
}
fn instance_with_v8_limit_guard_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Instance must be invoked with 'new'");
return;
}
if module_instantiation_exceeds_v8_limit(scope, args.get(0)) {
throw_range_error(
scope,
"WebAssembly resource declaration exceeds the implementation limit",
);
return;
}
let forwarded = (0..args.length())
.map(|index| args.get(index))
.collect::<Vec<_>>();
if let Some(instance) = construct_native(
scope,
ORIGINAL_INSTANCE_CTOR_SLOT,
"Instance",
&forwarded,
args.new_target(),
) {
rv.set(instance.into());
}
}
fn install_streaming_functions<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let module_exists =
store_original_constructor(scope, webassembly, "Module", ORIGINAL_MODULE_CTOR_SLOT)
.is_some();
if module_exists
&& webassembly
.get(scope, v8str(scope, "compileStreaming").into())
.is_none_or(|value| !value.is_function())
{
install_namespace_function(
scope,
webassembly,
"compileStreaming",
1,
compile_streaming_callback,
)?;
}
let instance_exists =
store_original_constructor(scope, webassembly, "Instance", ORIGINAL_INSTANCE_CTOR_SLOT)
.is_some();
if module_exists
&& instance_exists
&& webassembly
.get(scope, v8str(scope, "instantiateStreaming").into())
.is_none_or(|value| !value.is_function())
{
install_namespace_function(
scope,
webassembly,
"instantiateStreaming",
1,
instantiate_streaming_callback,
)?;
}
Ok(())
}
fn streaming_data<'s>(
scope: &mut v8::PinScope<'s, '_>,
options: v8::Local<'s, v8::Value>,
import_object: Option<v8::Local<'s, v8::Value>>,
) -> v8::Local<'s, v8::Object> {
WebAssemblyStreamingDataDeclaration::new(
options,
import_object.unwrap_or_else(|| v8::undefined(scope).into()),
)
.bind(scope)
.expect("WebAssembly streaming data declaration should bind")
}
fn compile_streaming_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let source = args.get(0);
let options = args.get(1);
let data = streaming_data(scope, options, None);
let Some(on_response) = v8::Function::builder(compile_streaming_response_callback)
.data(data.into())
.build(scope)
else {
return;
};
if let Some(promise) = promise_resolve_then(scope, source, on_response) {
rv.set(promise);
}
}
fn instantiate_streaming_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let import_object = args.get(1);
if !import_object.is_undefined() && !is_dictionary_like(import_object) {
if let Some(error) = type_error_value(scope, "Argument 1 must be an object")
&& let Some(promise) = promise_reject(scope, error)
{
rv.set(promise);
}
return;
}
let data = streaming_data(scope, args.get(2), Some(import_object));
let Some(on_response) = v8::Function::builder(instantiate_streaming_response_callback)
.data(data.into())
.build(scope)
else {
return;
};
if let Some(promise) = promise_resolve_then(scope, args.get(0), on_response) {
rv.set(promise);
}
}
fn compile_streaming_response_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(bytes_or_promise) =
crate::network_host::consume_webassembly_streaming_response_value(scope, args.get(0))
else {
return;
};
let Some(on_bytes) = v8::Function::builder(compile_streaming_bytes_callback)
.data(args.data())
.build(scope)
else {
return;
};
if let Some(promise) = promise_resolve_then(scope, bytes_or_promise, on_bytes) {
rv.set(promise);
}
}
fn instantiate_streaming_response_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(bytes_or_promise) =
crate::network_host::consume_webassembly_streaming_response_value(scope, args.get(0))
else {
return;
};
let Some(on_bytes) = v8::Function::builder(instantiate_streaming_bytes_callback)
.data(args.data())
.build(scope)
else {
return;
};
if let Some(promise) = promise_resolve_then(scope, bytes_or_promise, on_bytes) {
rv.set(promise);
}
}
fn streaming_callback_data<'s>(
value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Object>> {
v8::Local::<v8::Object>::try_from(value).ok()
}
fn streaming_options<'s>(
scope: &mut v8::PinScope<'s, '_>,
data: v8::Local<'s, v8::Object>,
) -> v8::Local<'s, v8::Value> {
get_private_value(scope, data, STREAMING_OPTIONS_SLOT)
.unwrap_or_else(|| v8::undefined(scope).into())
}
fn streaming_import_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
data: v8::Local<'s, v8::Object>,
) -> v8::Local<'s, v8::Value> {
get_private_value(scope, data, STREAMING_IMPORT_OBJECT_SLOT)
.unwrap_or_else(|| v8::undefined(scope).into())
}
fn compile_streaming_bytes_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(data) = streaming_callback_data(args.data()) else {
return;
};
let Some(module_ctor) =
webassembly_namespace(scope).and_then(|wa| webassembly_constructor(scope, wa, "Module"))
else {
return;
};
let options = streaming_options(scope, data);
let Some(module) = module_ctor.new_instance(scope, &[args.get(0), options]) else {
return;
};
rv.set(module.into());
}
fn instantiate_streaming_bytes_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(data) = streaming_callback_data(args.data()) else {
return;
};
let Some(module_ctor) =
webassembly_namespace(scope).and_then(|wa| webassembly_constructor(scope, wa, "Module"))
else {
return;
};
let Some(instance_ctor) =
webassembly_namespace(scope).and_then(|wa| webassembly_constructor(scope, wa, "Instance"))
else {
return;
};
let options = streaming_options(scope, data);
let Some(module) = module_ctor.new_instance(scope, &[args.get(0), options]) else {
return;
};
let import_object = streaming_import_object(scope, data);
let Some(instance) = instance_ctor.new_instance(scope, &[module.into(), import_object]) else {
return;
};
let result = WebAssemblyInstantiateStreamingResultDeclaration::new(module, instance)
.bind(scope)
.expect("WebAssembly instantiateStreaming result declaration should bind");
rv.set(result.into());
}
fn install_memory_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let Some(native_constructor) =
store_original_constructor(scope, webassembly, "Memory", ORIGINAL_MEMORY_CTOR_SLOT)
else {
return Ok(());
};
let prototype = constructor_prototype(scope, native_constructor);
if let Some(prototype) = prototype {
store_accessor_function(scope, prototype, "buffer", "get", MEMORY_BUFFER_GETTER_SLOT);
}
install_constructor_wrapper(
scope,
webassembly,
"Memory",
1,
native_constructor,
memory_constructor_callback,
)?;
if let Some(prototype) = prototype
&& prototype
.get(scope, v8str(scope, "type").into())
.is_none_or(|value| !value.is_function())
{
install_prototype_method(scope, prototype, "type", 0, memory_type_callback)?;
}
Ok(())
}
fn install_table_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let Some(native_constructor) =
store_original_constructor(scope, webassembly, "Table", ORIGINAL_TABLE_CTOR_SLOT)
else {
return Ok(());
};
let prototype = constructor_prototype(scope, native_constructor);
if let Some(prototype) = prototype {
store_accessor_function(scope, prototype, "length", "get", TABLE_LENGTH_GETTER_SLOT);
}
install_constructor_wrapper(
scope,
webassembly,
"Table",
1,
native_constructor,
table_constructor_callback,
)?;
if let Some(prototype) = prototype
&& prototype
.get(scope, v8str(scope, "type").into())
.is_none_or(|value| !value.is_function())
{
install_prototype_method(scope, prototype, "type", 0, table_type_callback)?;
}
Ok(())
}
fn install_global_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let Some(native_constructor) =
store_original_constructor(scope, webassembly, "Global", ORIGINAL_GLOBAL_CTOR_SLOT)
else {
return Ok(());
};
let prototype = constructor_prototype(scope, native_constructor);
install_constructor_wrapper(
scope,
webassembly,
"Global",
1,
native_constructor,
global_constructor_callback,
)?;
if let Some(prototype) = prototype {
if prototype
.get(scope, v8str(scope, "type").into())
.is_none_or(|value| !value.is_function())
{
install_prototype_method(scope, prototype, "type", 0, global_type_callback)?;
}
install_global_value_setter_compatibility(scope, prototype)?;
}
Ok(())
}
fn install_tag_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let Some(native_constructor) =
store_original_constructor(scope, webassembly, "Tag", ORIGINAL_TAG_CTOR_SLOT)
else {
return Ok(());
};
let prototype = constructor_prototype(scope, native_constructor);
install_constructor_wrapper(
scope,
webassembly,
"Tag",
1,
native_constructor,
tag_constructor_callback,
)?;
if let Some(prototype) = prototype
&& prototype
.get(scope, v8str(scope, "type").into())
.is_none_or(|value| !value.is_function())
{
install_prototype_method(scope, prototype, "type", 0, tag_type_callback)?;
}
Ok(())
}
fn store_accessor_function(
scope: &mut v8::PinScope<'_, '_>,
prototype: v8::Local<'_, v8::Object>,
property_name: &'static str,
accessor_name: &'static str,
slot: &'static str,
) {
let Some(descriptor) =
prototype.get_own_property_descriptor(scope, v8str(scope, property_name).into())
else {
return;
};
let Some(accessor) = v8::Local::<v8::Object>::try_from(descriptor)
.ok()
.and_then(|descriptor| descriptor.get(scope, v8str(scope, accessor_name).into()))
.filter(|value| value.is_function())
else {
return;
};
let global = scope.get_current_context().global(scope);
set_private_value(scope, global, slot, accessor);
}
fn apply_new_target_prototype<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
new_target: v8::Local<'s, v8::Value>,
) {
let Some(prototype) = v8::Local::<v8::Object>::try_from(new_target)
.ok()
.and_then(|new_target| new_target.get(scope, v8str(scope, "prototype").into()))
.and_then(|prototype| v8::Local::<v8::Object>::try_from(prototype).ok())
else {
return;
};
let _ = object.set_prototype(scope, prototype.into());
}
fn construct_native<'s>(
scope: &mut v8::PinScope<'s, '_>,
constructor_slot: &'static str,
fallback_name: &'static str,
args: &[v8::Local<'s, v8::Value>],
new_target: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Object>> {
let constructor = original_constructor(scope, constructor_slot, fallback_name)?;
let object = constructor.new_instance(scope, args)?;
apply_new_target_prototype(scope, object, new_target);
Some(object)
}
fn memory_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Memory must be invoked with 'new'");
return;
}
let descriptor = args.get(0);
let Some(descriptor_object) = object_from_dictionary_like(descriptor) else {
if let Some(memory) = construct_native(
scope,
ORIGINAL_MEMORY_CTOR_SLOT,
"Memory",
&[descriptor],
args.new_target(),
) {
rv.set(memory.into());
}
return;
};
let minimum = object_property(scope, descriptor_object, "minimum");
if minimum.is_undefined() {
if let Some(memory) = construct_native(
scope,
ORIGINAL_MEMORY_CTOR_SLOT,
"Memory",
&[descriptor],
args.new_target(),
) {
rv.set(memory.into());
}
return;
}
if has_defined_property(scope, descriptor_object, "initial") {
throw_type_error(
scope,
"WebAssembly.Memory descriptor cannot specify both initial and minimum",
);
return;
}
let maximum = object_property(scope, descriptor_object, "maximum");
let shared = object_property(scope, descriptor_object, "shared");
let normalized = WebAssemblyMemoryNativeDescriptorDeclaration {
initial: minimum,
maximum,
shared,
}
.bind(scope)
.expect("WebAssembly Memory native descriptor declaration should bind");
let Some(memory) = construct_native(
scope,
ORIGINAL_MEMORY_CTOR_SLOT,
"Memory",
&[normalized.into()],
args.new_target(),
) else {
return;
};
let type_object = WebAssemblyMemoryTypeDeclaration {
minimum,
maximum: (!maximum.is_undefined()).then_some(maximum),
shared: (!shared.is_undefined()).then_some(shared),
}
.bind(scope)
.expect("WebAssembly Memory type declaration should bind");
set_private_value(scope, memory, MEMORY_TYPE_SLOT, type_object.into());
rv.set(memory.into());
}
fn table_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Table must be invoked with 'new'");
return;
}
let descriptor = args.get(0);
let Some(descriptor_object) = object_from_dictionary_like(descriptor) else {
if let Some(table) = construct_native(
scope,
ORIGINAL_TABLE_CTOR_SLOT,
"Table",
&[descriptor, args.get(1)],
args.new_target(),
) {
rv.set(table.into());
}
return;
};
let minimum = object_property(scope, descriptor_object, "minimum");
if minimum.is_undefined() {
if let Some(table) = construct_native(
scope,
ORIGINAL_TABLE_CTOR_SLOT,
"Table",
&[descriptor, args.get(1)],
args.new_target(),
) {
rv.set(table.into());
}
return;
}
if has_defined_property(scope, descriptor_object, "initial") {
throw_type_error(
scope,
"WebAssembly.Table descriptor cannot specify both initial and minimum",
);
return;
}
let public_element = object_property(scope, descriptor_object, "element");
let Some(native_element) = value_type_for_native_constructor(scope, public_element) else {
return;
};
let maximum = object_property(scope, descriptor_object, "maximum");
let native_descriptor = WebAssemblyTableNativeDescriptorDeclaration {
element: native_element,
initial: minimum,
maximum,
}
.bind(scope)
.expect("WebAssembly Table native descriptor declaration should bind");
let Some(table) = construct_native(
scope,
ORIGINAL_TABLE_CTOR_SLOT,
"Table",
&[native_descriptor.into(), args.get(1)],
args.new_target(),
) else {
return;
};
let type_object = WebAssemblyTableTypeDeclaration {
minimum,
maximum: (!maximum.is_undefined()).then_some(maximum),
element: public_element,
}
.bind(scope)
.expect("WebAssembly Table type declaration should bind");
set_private_value(scope, table, TABLE_TYPE_SLOT, type_object.into());
rv.set(table.into());
}
fn global_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Global must be invoked with 'new'");
return;
}
let descriptor = args.get(0);
let mut native_descriptor = descriptor;
let mut public_type = None;
if let Some(descriptor_object) = object_from_dictionary_like(descriptor) {
let mutable = object_property(scope, descriptor_object, "mutable");
let value_type = object_property(scope, descriptor_object, "value");
let Some(normalized_value) = value_type_for_native_constructor(scope, value_type) else {
return;
};
let object = WebAssemblyGlobalNativeDescriptorDeclaration {
mutable,
value: normalized_value,
}
.bind(scope)
.expect("WebAssembly Global native descriptor declaration should bind");
native_descriptor = object.into();
let type_object = WebAssemblyGlobalTypeDeclaration {
mutable: mutable.boolean_value(scope),
value: value_type,
}
.bind(scope)
.expect("WebAssembly Global type declaration should bind");
public_type = Some(type_object);
}
let native_value = if args.length() >= 2 {
args.get(1)
} else if descriptor_value_is_anyfunc(scope, native_descriptor) {
v8::null(scope).into()
} else {
v8::undefined(scope).into()
};
let Some(global) = construct_native(
scope,
ORIGINAL_GLOBAL_CTOR_SLOT,
"Global",
&[native_descriptor, native_value],
args.new_target(),
) else {
return;
};
if let Some(public_type) = public_type {
set_private_value(scope, global, GLOBAL_TYPE_SLOT, public_type.into());
}
rv.set(global.into());
}
fn tag_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(scope, "WebAssembly.Tag must be invoked with 'new'");
return;
}
let descriptor = args.get(0);
let Some(tag) = construct_native(
scope,
ORIGINAL_TAG_CTOR_SLOT,
"Tag",
&[descriptor],
args.new_target(),
) else {
return;
};
if let Some(descriptor_object) = object_from_dictionary_like(descriptor) {
let parameters = object_property(scope, descriptor_object, "parameters");
if let Some(parameters) = array_from_value_or_empty(scope, parameters) {
let type_object = WebAssemblyTagTypeDeclaration { parameters }
.bind(scope)
.expect("WebAssembly Tag type declaration should bind");
set_private_value(scope, tag, TAG_TYPE_SLOT, type_object.into());
}
}
rv.set(tag.into());
}
fn string_value<'s>(scope: &mut v8::PinScope<'s, '_>, value: &str) -> v8::Local<'s, v8::Value> {
v8_string(scope, value)
.map(Into::into)
.unwrap_or_else(|| v8::undefined(scope).into())
}
fn value_type_for_native_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
public_value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Value>> {
let value_string = public_value.to_string(scope)?;
if value_string.to_rust_string_lossy(scope) == "funcref" {
Some(string_value(scope, "anyfunc"))
} else {
Some(value_string.into())
}
}
fn string_value_equals(
scope: &mut v8::PinScope<'_, '_>,
value: v8::Local<'_, v8::Value>,
expected: &str,
) -> bool {
value
.to_string(scope)
.map(|value| value.to_rust_string_lossy(scope) == expected)
.unwrap_or(false)
}
fn descriptor_value_is_anyfunc<'s>(
scope: &mut v8::PinScope<'s, '_>,
descriptor: v8::Local<'s, v8::Value>,
) -> bool {
v8::Local::<v8::Object>::try_from(descriptor)
.ok()
.map(|descriptor| object_property(scope, descriptor, "value"))
.is_some_and(|value| string_value_equals(scope, value, "anyfunc"))
}
fn array_from_value_or_empty<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Array>> {
if value.is_null_or_undefined() {
return Some(v8::Array::new(scope, 0));
}
// V8's WebAssembly.Tag constructor consumes `parameters` as an
// array-like object (length plus indexed values), not a WebIDL sequence.
// Mirror that native interpretation without consulting public Array.from.
let object = v8::Local::<v8::Object>::try_from(value).ok()?;
let length = object
.get(scope, v8str(scope, "length").into())?
.uint32_value(scope)?;
let mut values = Vec::with_capacity(length as usize);
for index in 0..length {
values.push(object.get_index(scope, index)?);
}
Some(v8::Array::new_with_elements(scope, &values))
}
fn clone_memory_type_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
stored: v8::Local<'s, v8::Object>,
) -> Option<v8::Local<'s, v8::Object>> {
let minimum = stored.get(scope, v8str(scope, "minimum").into())?;
if minimum.is_undefined() {
return None;
}
let maximum = stored
.get(scope, v8str(scope, "maximum").into())
.filter(|value| !value.is_undefined());
let shared = stored
.get(scope, v8str(scope, "shared").into())
.filter(|value| !value.is_undefined());
WebAssemblyMemoryTypeCloneDeclaration::new(minimum, maximum, shared)
.bind(scope)
.ok()
}
fn clone_table_type_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
stored: v8::Local<'s, v8::Object>,
) -> Option<v8::Local<'s, v8::Object>> {
let minimum = stored.get(scope, v8str(scope, "minimum").into())?;
if minimum.is_undefined() {
return None;
}
let element = stored.get(scope, v8str(scope, "element").into())?;
if element.is_undefined() {
return None;
}
let maximum = stored
.get(scope, v8str(scope, "maximum").into())
.filter(|value| !value.is_undefined());
WebAssemblyTableTypeCloneDeclaration::new(minimum, maximum, element)
.bind(scope)
.ok()
}
fn clone_global_type_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
stored: v8::Local<'s, v8::Object>,
) -> Option<v8::Local<'s, v8::Object>> {
let r#mutable = stored.get(scope, v8str(scope, "mutable").into())?;
if r#mutable.is_undefined() {
return None;
}
let value = stored.get(scope, v8str(scope, "value").into())?;
if value.is_undefined() {
return None;
}
WebAssemblyGlobalTypeCloneDeclaration::new(r#mutable, value)
.bind(scope)
.ok()
}
fn memory_type_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let this = args.this();
if let Some(stored) = get_private_value(scope, this, MEMORY_TYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
if let Some(clone) = clone_memory_type_object(scope, stored) {
rv.set(clone.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Memory.prototype.type called on incompatible receiver",
);
return;
}
if let Some(getter) = get_private_value(
scope,
scope.get_current_context().global(scope),
MEMORY_BUFFER_GETTER_SLOT,
)
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
&& let Some(buffer) = getter.call(scope, this.into(), &[])
&& let Ok(buffer) = v8::Local::<v8::Object>::try_from(buffer)
&& let Some(byte_length) = buffer
.get(scope, v8str(scope, "byteLength").into())
.and_then(|value| value.number_value(scope))
&& byte_length.is_finite()
{
let object = WebAssemblyMemoryFallbackTypeDeclaration {
minimum: byte_length / 65536.0,
}
.bind(scope)
.expect("WebAssembly Memory fallback type declaration should bind");
rv.set(object.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Memory.prototype.type called on incompatible receiver",
);
}
fn table_type_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let this = args.this();
if let Some(stored) = get_private_value(scope, this, TABLE_TYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
if let Some(clone) = clone_table_type_object(scope, stored) {
rv.set(clone.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Table.prototype.type called on incompatible receiver",
);
return;
}
if let Some(getter) = get_private_value(
scope,
scope.get_current_context().global(scope),
TABLE_LENGTH_GETTER_SLOT,
)
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
&& let Some(length) = getter.call(scope, this.into(), &[])
&& let Some(length) = length.number_value(scope)
{
let element = string_value(scope, "anyfunc");
let object = WebAssemblyTableFallbackTypeDeclaration {
minimum: length,
element,
}
.bind(scope)
.expect("WebAssembly Table fallback type declaration should bind");
rv.set(object.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Table.prototype.type called on incompatible receiver",
);
}
fn global_type_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let this = args.this();
if let Some(stored) = get_private_value(scope, this, GLOBAL_TYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
if let Some(clone) = clone_global_type_object(scope, stored) {
rv.set(clone.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Global.prototype.type called on incompatible receiver",
);
return;
}
throw_type_error(
scope,
"WebAssembly.Global.prototype.type called on incompatible receiver",
);
}
fn tag_type_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let this = args.this();
if let Some(stored) = get_private_value(scope, this, TAG_TYPE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
let parameters = stored
.get(scope, v8str(scope, "parameters").into())
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
.map(|parameters| clone_array(scope, parameters));
let object = WebAssemblyTagTypeCloneDeclaration { parameters }
.bind(scope)
.expect("WebAssembly Tag type clone declaration should bind");
rv.set(object.into());
return;
}
throw_type_error(
scope,
"WebAssembly.Tag.prototype.type called on incompatible receiver",
);
}
fn clone_array<'s>(
scope: &mut v8::PinScope<'s, '_>,
source: v8::Local<'s, v8::Array>,
) -> v8::Local<'s, v8::Array> {
let len = source.length();
let clone = v8::Array::new(scope, len as i32);
for index in 0..len {
if let Some(value) = source.get_index(scope, index) {
let _ = clone.set_index(scope, index, value);
}
}
clone
}
fn install_global_value_setter_compatibility(
scope: &mut v8::PinScope<'_, '_>,
prototype: v8::Local<'_, v8::Object>,
) -> Result<()> {
let Some(descriptor) =
prototype.get_own_property_descriptor(scope, v8str(scope, "value").into())
else {
return Ok(());
};
let Some(descriptor) = v8::Local::<v8::Object>::try_from(descriptor).ok() else {
return Ok(());
};
let getter = descriptor
.get(scope, v8str(scope, "get").into())
.unwrap_or_else(|| v8::undefined(scope).into());
let setter = descriptor
.get(scope, v8str(scope, "set").into())
.unwrap_or_else(|| v8::undefined(scope).into());
if !setter.is_function() {
return Ok(());
}
if v8::Local::<v8::Object>::try_from(setter)
.ok()
.and_then(|setter| get_private_value(scope, setter, GLOBAL_VALUE_SETTER_WRAPPED_SLOT))
.is_some_and(|value| value.boolean_value(scope))
{
return Ok(());
}
let global = scope.get_current_context().global(scope);
set_private_value(scope, global, GLOBAL_VALUE_SETTER_SLOT, setter);
let value_setter = v8::Function::builder(global_value_setter_callback)
.length(1)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly.Global value setter"))?;
value_setter.set_name(v8str(scope, "set value"));
set_private_value(
scope,
value_setter.into(),
GLOBAL_VALUE_SETTER_WRAPPED_SLOT,
v8::Boolean::new(scope, true).into(),
);
let enumerable = descriptor
.get(scope, v8str(scope, "enumerable").into())
.is_some_and(|value| value.boolean_value(scope));
let configurable = descriptor
.get(scope, v8str(scope, "configurable").into())
.is_some_and(|value| value.boolean_value(scope));
define_accessor_property(
scope,
prototype,
"value",
getter,
value_setter.into(),
enumerable,
configurable,
)
}
fn global_value_setter_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(setter) = get_private_value(
scope,
scope.get_current_context().global(scope),
GLOBAL_VALUE_SETTER_SLOT,
)
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok()) else {
return;
};
let value = if args.length() == 0 {
v8::undefined(scope).into()
} else {
args.get(0)
};
let _ = setter.call(scope, args.this().into(), &[value]);
}
fn install_function_shape<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) {
if let Some(function) = webassembly_constructor(scope, webassembly, "Function") {
let _ = define_value_property(
scope,
function.into(),
"length",
v8::Integer::new(scope, 2).into(),
false,
false,
true,
);
}
}
fn install_exception_shape<'s>(
scope: &mut v8::PinScope<'s, '_>,
webassembly: v8::Local<'s, v8::Object>,
) -> Result<()> {
let Some(exception_ctor) = store_original_constructor(
scope,
webassembly,
"Exception",
ORIGINAL_EXCEPTION_CTOR_SLOT,
) else {
return Ok(());
};
define_value_property(
scope,
exception_ctor.into(),
"length",
v8::Integer::new(scope, 2).into(),
false,
false,
true,
)?;
let Some(prototype) = constructor_prototype(scope, exception_ctor) else {
return Ok(());
};
if !prototype
.has_own_property(scope, v8str(scope, "stack").into())
.unwrap_or(false)
{
let getter = v8::Function::builder(exception_stack_getter_callback)
.build(scope)
.ok_or_else(|| anyhow!("failed to build WebAssembly.Exception stack getter"))?;
define_accessor_property(
scope,
prototype,
"stack",
getter.into(),
v8::undefined(scope).into(),
true,
true,
)?;
}
if let Some(native_get_arg) = prototype
.get(scope, v8str(scope, "getArg").into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
{
set_private_value(
scope,
prototype,
EXCEPTION_GET_ARG_SLOT,
native_get_arg.into(),
);
install_prototype_method(scope, prototype, "getArg", 2, exception_get_arg_callback)?;
}
Ok(())
}
fn exception_stack_getter_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(exception_ctor) =
original_constructor(scope, ORIGINAL_EXCEPTION_CTOR_SLOT, "Exception")
else {
return;
};
if !args
.this()
.instance_of(scope, exception_ctor.into())
.unwrap_or(false)
{
throw_type_error(
scope,
"WebAssembly.Exception.prototype.stack called on incompatible receiver",
);
return;
}
rv.set(v8str(scope, "").into());
}
fn exception_get_arg_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let native_get_arg = args
.this()
.get_prototype(scope)
.and_then(|prototype| v8::Local::<v8::Object>::try_from(prototype).ok())
.and_then(|prototype| get_private_value(scope, prototype, EXCEPTION_GET_ARG_SLOT))
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok());
let Some(native_get_arg) = native_get_arg else {
return;
};
if args.length() < 2 {
let forwarded = (0..args.length())
.map(|index| args.get(index))
.collect::<Vec<_>>();
if let Some(value) = native_get_arg.call(scope, args.this().into(), &forwarded) {
rv.set(value);
}
return;
}
let tag = args.get(0);
let Some(tag_type) = v8::Local::<v8::Object>::try_from(tag)
.ok()
.and_then(|tag| get_private_value(scope, tag, TAG_TYPE_SLOT))
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
if let Some(value) = native_get_arg.call(scope, args.this().into(), &[tag, args.get(1)]) {
rv.set(value);
}
return;
};
let Some(parameters) = tag_type
.get(scope, v8str(scope, "parameters").into())
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
else {
if let Some(value) = native_get_arg.call(scope, args.this().into(), &[tag, args.get(1)]) {
rv.set(value);
}
return;
};
let Some(index) = args.get(1).number_value(scope) else {
return;
};
let out_of_range = !index.is_finite()
|| index.trunc() != index
|| index < 0.0
|| index >= parameters.length() as f64;
if out_of_range {
throw_range_error(scope, "WebAssembly.Exception index is out of range");
return;
}
let index_value = v8::Number::new(scope, index);
if let Some(value) = native_get_arg.call(scope, args.this().into(), &[tag, index_value.into()])
{
rv.set(value);
}
}