summaryrefslogtreecommitdiff
path: root/core/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'core/runtime')
-rw-r--r--core/runtime/bindings.js51
-rw-r--r--core/runtime/bindings.rs598
-rw-r--r--core/runtime/encode_decode_test.js69
-rw-r--r--core/runtime/error_builder_test.js32
-rw-r--r--core/runtime/icudtl.datbin0 -> 10541264 bytes
-rw-r--r--core/runtime/jsrealm.rs367
-rw-r--r--core/runtime/jsruntime.rs2416
-rw-r--r--core/runtime/mod.rs35
-rw-r--r--core/runtime/ops.rs156
-rw-r--r--core/runtime/serialize_deserialize_test.js74
-rw-r--r--core/runtime/snapshot_util.rs252
-rw-r--r--core/runtime/tests.rs2306
12 files changed, 6356 insertions, 0 deletions
diff --git a/core/runtime/bindings.js b/core/runtime/bindings.js
new file mode 100644
index 000000000..21d27a2c3
--- /dev/null
+++ b/core/runtime/bindings.js
@@ -0,0 +1,51 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+if (!globalThis.Deno) {
+ globalThis.Deno = {
+ core: {
+ ops: {},
+ asyncOps: {},
+ },
+ };
+}
+
+Deno.__op__console = function (callConsole, console) {
+ Deno.core.callConsole = callConsole;
+ Deno.core.console = console;
+};
+
+Deno.__op__registerOp = function (isAsync, op, opName) {
+ const core = Deno.core;
+ if (isAsync) {
+ if (core.ops[opName] !== undefined) {
+ return;
+ }
+ core.asyncOps[opName] = op;
+ const fn = function (...args) {
+ if (this !== core.ops) {
+ // deno-lint-ignore prefer-primordials
+ throw new Error(
+ "An async stub cannot be separated from Deno.core.ops. Use ???",
+ );
+ }
+ return core.asyncStub(opName, args);
+ };
+ fn.name = opName;
+ core.ops[opName] = fn;
+ } else {
+ core.ops[opName] = op;
+ }
+};
+
+Deno.__op__unregisterOp = function (isAsync, opName) {
+ if (isAsync) {
+ delete Deno.core.asyncOps[opName];
+ }
+ delete Deno.core.ops[opName];
+};
+
+Deno.__op__cleanup = function () {
+ delete Deno.__op__console;
+ delete Deno.__op__registerOp;
+ delete Deno.__op__unregisterOp;
+ delete Deno.__op__cleanup;
+};
diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs
new file mode 100644
index 000000000..4cc27592f
--- /dev/null
+++ b/core/runtime/bindings.rs
@@ -0,0 +1,598 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use log::debug;
+use std::fmt::Write;
+use std::option::Option;
+use std::os::raw::c_void;
+use v8::MapFnTo;
+
+use crate::error::is_instance_of_error;
+use crate::error::JsStackFrame;
+use crate::modules::get_asserted_module_type_from_assertions;
+use crate::modules::parse_import_assertions;
+use crate::modules::validate_import_assertions;
+use crate::modules::ImportAssertionsKind;
+use crate::modules::ModuleMap;
+use crate::modules::ResolutionKind;
+use crate::ops::OpCtx;
+use crate::runtime::InitMode;
+use crate::JsRealm;
+use crate::JsRuntime;
+
+pub(crate) fn external_references(ops: &[OpCtx]) -> v8::ExternalReferences {
+ // Overallocate a bit, it's better than having to resize the vector.
+ let mut references = Vec::with_capacity(4 + ops.len() * 4);
+
+ references.push(v8::ExternalReference {
+ function: call_console.map_fn_to(),
+ });
+ references.push(v8::ExternalReference {
+ function: import_meta_resolve.map_fn_to(),
+ });
+ references.push(v8::ExternalReference {
+ function: catch_dynamic_import_promise_error.map_fn_to(),
+ });
+ references.push(v8::ExternalReference {
+ function: empty_fn.map_fn_to(),
+ });
+
+ for ctx in ops {
+ let ctx_ptr = ctx as *const OpCtx as _;
+ references.push(v8::ExternalReference { pointer: ctx_ptr });
+ references.push(v8::ExternalReference {
+ function: ctx.decl.v8_fn_ptr,
+ });
+ if let Some(fast_fn) = &ctx.decl.fast_fn {
+ references.push(v8::ExternalReference {
+ pointer: fast_fn.function as _,
+ });
+ references.push(v8::ExternalReference {
+ pointer: ctx.fast_fn_c_info.unwrap().as_ptr() as _,
+ });
+ }
+ }
+
+ let refs = v8::ExternalReferences::new(&references);
+ // Leak, V8 takes ownership of the references.
+ std::mem::forget(references);
+ refs
+}
+
+// TODO(nayeemrmn): Move to runtime and/or make `pub(crate)`.
+pub fn script_origin<'a>(
+ s: &mut v8::HandleScope<'a>,
+ resource_name: v8::Local<'a, v8::String>,
+) -> v8::ScriptOrigin<'a> {
+ let source_map_url = v8::String::empty(s);
+ v8::ScriptOrigin::new(
+ s,
+ resource_name.into(),
+ 0,
+ 0,
+ false,
+ 123,
+ source_map_url.into(),
+ true,
+ false,
+ false,
+ )
+}
+
+fn get<'s, T>(
+ scope: &mut v8::HandleScope<'s>,
+ from: v8::Local<v8::Object>,
+ key: &'static [u8],
+ path: &'static str,
+) -> T
+where
+ v8::Local<'s, v8::Value>: TryInto<T>,
+{
+ let key = v8::String::new_external_onebyte_static(scope, key).unwrap();
+ from
+ .get(scope, key.into())
+ .unwrap_or_else(|| panic!("{path} exists"))
+ .try_into()
+ .unwrap_or_else(|_| panic!("unable to convert"))
+}
+
+pub(crate) fn initialize_context<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ context: v8::Local<'s, v8::Context>,
+ op_ctxs: &[OpCtx],
+ init_mode: InitMode,
+) -> v8::Local<'s, v8::Context> {
+ let global = context.global(scope);
+
+ let mut codegen = String::with_capacity(op_ctxs.len() * 200);
+ codegen.push_str(include_str!("bindings.js"));
+ _ = writeln!(
+ codegen,
+ "Deno.__op__ = function(opFns, callConsole, console) {{"
+ );
+ if init_mode == InitMode::New {
+ _ = writeln!(codegen, "Deno.__op__console(callConsole, console);");
+ }
+ for op_ctx in op_ctxs {
+ if op_ctx.decl.enabled {
+ _ = writeln!(
+ codegen,
+ "Deno.__op__registerOp({}, opFns[{}], \"{}\");",
+ op_ctx.decl.is_async, op_ctx.id, op_ctx.decl.name
+ );
+ } else {
+ _ = writeln!(
+ codegen,
+ "Deno.__op__unregisterOp({}, \"{}\");",
+ op_ctx.decl.is_async, op_ctx.decl.name
+ );
+ }
+ }
+ codegen.push_str("Deno.__op__cleanup();");
+ _ = writeln!(codegen, "}}");
+
+ let script = v8::String::new_from_one_byte(
+ scope,
+ codegen.as_bytes(),
+ v8::NewStringType::Normal,
+ )
+ .unwrap();
+ let script = v8::Script::compile(scope, script, None).unwrap();
+ script.run(scope);
+
+ let deno = get(scope, global, b"Deno", "Deno");
+ let op_fn: v8::Local<v8::Function> =
+ get(scope, deno, b"__op__", "Deno.__op__");
+ let recv = v8::undefined(scope);
+ let op_fns = v8::Array::new(scope, op_ctxs.len() as i32);
+ for op_ctx in op_ctxs {
+ let op_fn = op_ctx_function(scope, op_ctx);
+ op_fns.set_index(scope, op_ctx.id as u32, op_fn.into());
+ }
+ if init_mode == InitMode::FromSnapshot {
+ op_fn.call(scope, recv.into(), &[op_fns.into()]);
+ } else {
+ // Bind functions to Deno.core.*
+ let call_console_fn = v8::Function::new(scope, call_console).unwrap();
+
+ // Bind v8 console object to Deno.core.console
+ let extra_binding_obj = context.get_extras_binding_object(scope);
+ let console_obj: v8::Local<v8::Object> = get(
+ scope,
+ extra_binding_obj,
+ b"console",
+ "ExtrasBindingObject.console",
+ );
+
+ op_fn.call(
+ scope,
+ recv.into(),
+ &[op_fns.into(), call_console_fn.into(), console_obj.into()],
+ );
+ }
+
+ context
+}
+
+fn op_ctx_function<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ op_ctx: &OpCtx,
+) -> v8::Local<'s, v8::Function> {
+ let op_ctx_ptr = op_ctx as *const OpCtx as *const c_void;
+ let external = v8::External::new(scope, op_ctx_ptr as *mut c_void);
+ let v8name =
+ v8::String::new_external_onebyte_static(scope, op_ctx.decl.name.as_bytes())
+ .unwrap();
+ let builder: v8::FunctionBuilder<v8::FunctionTemplate> =
+ v8::FunctionTemplate::builder_raw(op_ctx.decl.v8_fn_ptr)
+ .data(external.into())
+ .length(op_ctx.decl.arg_count as i32);
+
+ let template = if let Some(fast_function) = &op_ctx.decl.fast_fn {
+ builder.build_fast(
+ scope,
+ fast_function,
+ Some(op_ctx.fast_fn_c_info.unwrap().as_ptr()),
+ None,
+ None,
+ )
+ } else {
+ builder.build(scope)
+ };
+
+ let v8fn = template.get_function(scope).unwrap();
+ v8fn.set_name(v8name);
+ v8fn
+}
+
+pub extern "C" fn wasm_async_resolve_promise_callback(
+ _isolate: *mut v8::Isolate,
+ context: v8::Local<v8::Context>,
+ resolver: v8::Local<v8::PromiseResolver>,
+ compilation_result: v8::Local<v8::Value>,
+ success: v8::WasmAsyncSuccess,
+) {
+ // SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
+ let scope = &mut unsafe { v8::CallbackScope::new(context) };
+ if success == v8::WasmAsyncSuccess::Success {
+ resolver.resolve(scope, compilation_result).unwrap();
+ } else {
+ resolver.reject(scope, compilation_result).unwrap();
+ }
+}
+
+pub fn host_import_module_dynamically_callback<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ _host_defined_options: v8::Local<'s, v8::Data>,
+ resource_name: v8::Local<'s, v8::Value>,
+ specifier: v8::Local<'s, v8::String>,
+ import_assertions: v8::Local<'s, v8::FixedArray>,
+) -> Option<v8::Local<'s, v8::Promise>> {
+ // NOTE(bartlomieju): will crash for non-UTF-8 specifier
+ let specifier_str = specifier
+ .to_string(scope)
+ .unwrap()
+ .to_rust_string_lossy(scope);
+ let referrer_name_str = resource_name
+ .to_string(scope)
+ .unwrap()
+ .to_rust_string_lossy(scope);
+
+ let resolver = v8::PromiseResolver::new(scope).unwrap();
+ let promise = resolver.get_promise(scope);
+
+ let assertions = parse_import_assertions(
+ scope,
+ import_assertions,
+ ImportAssertionsKind::DynamicImport,
+ );
+
+ {
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ validate_import_assertions(tc_scope, &assertions);
+ if tc_scope.has_caught() {
+ let e = tc_scope.exception().unwrap();
+ resolver.reject(tc_scope, e);
+ }
+ }
+ let asserted_module_type =
+ get_asserted_module_type_from_assertions(&assertions);
+
+ let resolver_handle = v8::Global::new(scope, resolver);
+ {
+ let state_rc = JsRuntime::state_from(scope);
+ let module_map_rc = JsRuntime::module_map_from(scope);
+
+ debug!(
+ "dyn_import specifier {} referrer {} ",
+ specifier_str, referrer_name_str
+ );
+ ModuleMap::load_dynamic_import(
+ module_map_rc,
+ &specifier_str,
+ &referrer_name_str,
+ asserted_module_type,
+ resolver_handle,
+ );
+ state_rc.borrow_mut().notify_new_dynamic_import();
+ }
+ // Map errors from module resolution (not JS errors from module execution) to
+ // ones rethrown from this scope, so they include the call stack of the
+ // dynamic import site. Error objects without any stack frames are assumed to
+ // be module resolution errors, other exception values are left as they are.
+ let builder = v8::FunctionBuilder::new(catch_dynamic_import_promise_error);
+
+ let map_err =
+ v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
+
+ let promise = promise.catch(scope, map_err).unwrap();
+
+ Some(promise)
+}
+
+pub extern "C" fn host_initialize_import_meta_object_callback(
+ context: v8::Local<v8::Context>,
+ module: v8::Local<v8::Module>,
+ meta: v8::Local<v8::Object>,
+) {
+ // SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
+ let scope = &mut unsafe { v8::CallbackScope::new(context) };
+ let module_map_rc = JsRuntime::module_map_from(scope);
+ let module_map = module_map_rc.borrow();
+
+ let module_global = v8::Global::new(scope, module);
+ let info = module_map
+ .get_info(&module_global)
+ .expect("Module not found");
+
+ let url_key = v8::String::new_external_onebyte_static(scope, b"url").unwrap();
+ let url_val = info.name.v8(scope);
+ meta.create_data_property(scope, url_key.into(), url_val.into());
+
+ let main_key =
+ v8::String::new_external_onebyte_static(scope, b"main").unwrap();
+ let main_val = v8::Boolean::new(scope, info.main);
+ meta.create_data_property(scope, main_key.into(), main_val.into());
+
+ let builder =
+ v8::FunctionBuilder::new(import_meta_resolve).data(url_val.into());
+ let val = v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
+ let resolve_key =
+ v8::String::new_external_onebyte_static(scope, b"resolve").unwrap();
+ meta.set(scope, resolve_key.into(), val.into());
+}
+
+fn import_meta_resolve(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ mut rv: v8::ReturnValue,
+) {
+ if args.length() > 1 {
+ return throw_type_error(scope, "Invalid arguments");
+ }
+
+ let maybe_arg_str = args.get(0).to_string(scope);
+ if maybe_arg_str.is_none() {
+ return throw_type_error(scope, "Invalid arguments");
+ }
+ let specifier = maybe_arg_str.unwrap();
+ let referrer = {
+ let url_prop = args.data();
+ url_prop.to_rust_string_lossy(scope)
+ };
+ let module_map_rc = JsRuntime::module_map_from(scope);
+ let loader = module_map_rc.borrow().loader.clone();
+ let specifier_str = specifier.to_rust_string_lossy(scope);
+
+ if specifier_str.starts_with("npm:") {
+ throw_type_error(scope, "\"npm:\" specifiers are currently not supported in import.meta.resolve()");
+ return;
+ }
+
+ match loader.resolve(&specifier_str, &referrer, ResolutionKind::DynamicImport)
+ {
+ Ok(resolved) => {
+ let resolved_val = serde_v8::to_v8(scope, resolved.as_str()).unwrap();
+ rv.set(resolved_val);
+ }
+ Err(err) => {
+ throw_type_error(scope, &err.to_string());
+ }
+ };
+}
+
+fn empty_fn(
+ _scope: &mut v8::HandleScope,
+ _args: v8::FunctionCallbackArguments,
+ _rv: v8::ReturnValue,
+) {
+ //Do Nothing
+}
+
+//It creates a reference to an empty function which can be mantained after the snapshots
+pub fn create_empty_fn<'s>(
+ scope: &mut v8::HandleScope<'s>,
+) -> Option<v8::Local<'s, v8::Function>> {
+ let empty_fn = v8::FunctionTemplate::new(scope, empty_fn);
+ empty_fn.get_function(scope)
+}
+
+fn catch_dynamic_import_promise_error(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ _rv: v8::ReturnValue,
+) {
+ let arg = args.get(0);
+ if is_instance_of_error(scope, arg) {
+ let e: crate::error::NativeJsError = serde_v8::from_v8(scope, arg).unwrap();
+ let name = e.name.unwrap_or_else(|| "Error".to_string());
+ let msg = v8::Exception::create_message(scope, arg);
+ if msg.get_stack_trace(scope).unwrap().get_frame_count() == 0 {
+ let arg: v8::Local<v8::Object> = arg.try_into().unwrap();
+ let message_key =
+ v8::String::new_external_onebyte_static(scope, b"message").unwrap();
+ let message = arg.get(scope, message_key.into()).unwrap();
+ let mut message: v8::Local<v8::String> = message.try_into().unwrap();
+ if let Some(stack_frame) = JsStackFrame::from_v8_message(scope, msg) {
+ if let Some(location) = stack_frame.maybe_format_location() {
+ let str =
+ format!("{} at {location}", message.to_rust_string_lossy(scope));
+ message = v8::String::new(scope, &str).unwrap();
+ }
+ }
+ let exception = match name.as_str() {
+ "RangeError" => v8::Exception::range_error(scope, message),
+ "TypeError" => v8::Exception::type_error(scope, message),
+ "SyntaxError" => v8::Exception::syntax_error(scope, message),
+ "ReferenceError" => v8::Exception::reference_error(scope, message),
+ _ => v8::Exception::error(scope, message),
+ };
+ let code_key =
+ v8::String::new_external_onebyte_static(scope, b"code").unwrap();
+ let code_value =
+ v8::String::new_external_onebyte_static(scope, b"ERR_MODULE_NOT_FOUND")
+ .unwrap();
+ let exception_obj = exception.to_object(scope).unwrap();
+ exception_obj.set(scope, code_key.into(), code_value.into());
+ scope.throw_exception(exception);
+ return;
+ }
+ }
+ scope.throw_exception(arg);
+}
+
+pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
+ use v8::PromiseRejectEvent::*;
+
+ // SAFETY: `CallbackScope` can be safely constructed from `&PromiseRejectMessage`
+ let scope = &mut unsafe { v8::CallbackScope::new(&message) };
+
+ let context_state_rc = JsRealm::state_from_scope(scope);
+ let mut context_state = context_state_rc.borrow_mut();
+
+ if let Some(js_promise_reject_cb) = context_state.js_promise_reject_cb.clone()
+ {
+ drop(context_state);
+
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into();
+ let type_ = v8::Integer::new(tc_scope, message.get_event() as i32);
+ let promise = message.get_promise();
+
+ let reason = match message.get_event() {
+ PromiseRejectWithNoHandler
+ | PromiseRejectAfterResolved
+ | PromiseResolveAfterResolved => message.get_value().unwrap_or(undefined),
+ PromiseHandlerAddedAfterReject => undefined,
+ };
+
+ let promise_global = v8::Global::new(tc_scope, promise);
+ let args = &[type_.into(), promise.into(), reason];
+ let maybe_has_unhandled_rejection_handler = js_promise_reject_cb
+ .open(tc_scope)
+ .call(tc_scope, undefined, args);
+
+ let has_unhandled_rejection_handler =
+ if let Some(value) = maybe_has_unhandled_rejection_handler {
+ value.is_true()
+ } else {
+ false
+ };
+
+ if has_unhandled_rejection_handler {
+ let state_rc = JsRuntime::state_from(tc_scope);
+ let mut state = state_rc.borrow_mut();
+ if let Some(pending_mod_evaluate) = state.pending_mod_evaluate.as_mut() {
+ if !pending_mod_evaluate.has_evaluated {
+ pending_mod_evaluate
+ .handled_promise_rejections
+ .push(promise_global);
+ }
+ }
+ }
+ } else {
+ let promise = message.get_promise();
+ let promise_global = v8::Global::new(scope, promise);
+ match message.get_event() {
+ PromiseRejectWithNoHandler => {
+ let error = message.get_value().unwrap();
+ let error_global = v8::Global::new(scope, error);
+ context_state
+ .pending_promise_rejections
+ .push_back((promise_global, error_global));
+ }
+ PromiseHandlerAddedAfterReject => {
+ context_state
+ .pending_promise_rejections
+ .retain(|(key, _)| key != &promise_global);
+ }
+ PromiseRejectAfterResolved => {}
+ PromiseResolveAfterResolved => {
+ // Should not warn. See #1272
+ }
+ }
+ }
+}
+
+/// This binding should be used if there's a custom console implementation
+/// available. Using it will make sure that proper stack frames are displayed
+/// in the inspector console.
+///
+/// Each method on console object should be bound to this function, eg:
+/// ```ignore
+/// function wrapConsole(consoleFromDeno, consoleFromV8) {
+/// const callConsole = core.callConsole;
+///
+/// for (const key of Object.keys(consoleFromV8)) {
+/// if (consoleFromDeno.hasOwnProperty(key)) {
+/// consoleFromDeno[key] = callConsole.bind(
+/// consoleFromDeno,
+/// consoleFromV8[key],
+/// consoleFromDeno[key],
+/// );
+/// }
+/// }
+/// }
+/// ```
+///
+/// Inspired by:
+/// https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/src/inspector_js_api.cc#L194-L222
+fn call_console(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ _rv: v8::ReturnValue,
+) {
+ if args.length() < 2
+ || !args.get(0).is_function()
+ || !args.get(1).is_function()
+ {
+ return throw_type_error(scope, "Invalid arguments");
+ }
+
+ let mut call_args = vec![];
+ for i in 2..args.length() {
+ call_args.push(args.get(i));
+ }
+
+ let receiver = args.this();
+ let inspector_console_method =
+ v8::Local::<v8::Function>::try_from(args.get(0)).unwrap();
+ let deno_console_method =
+ v8::Local::<v8::Function>::try_from(args.get(1)).unwrap();
+
+ inspector_console_method.call(scope, receiver.into(), &call_args);
+ deno_console_method.call(scope, receiver.into(), &call_args);
+}
+
+/// Called by V8 during `JsRuntime::instantiate_module`.
+///
+/// This function borrows `ModuleMap` from the isolate slot,
+/// so it is crucial to ensure there are no existing borrows
+/// of `ModuleMap` when `JsRuntime::instantiate_module` is called.
+pub fn module_resolve_callback<'s>(
+ context: v8::Local<'s, v8::Context>,
+ specifier: v8::Local<'s, v8::String>,
+ import_assertions: v8::Local<'s, v8::FixedArray>,
+ referrer: v8::Local<'s, v8::Module>,
+) -> Option<v8::Local<'s, v8::Module>> {
+ // SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
+ let scope = &mut unsafe { v8::CallbackScope::new(context) };
+
+ let module_map_rc = JsRuntime::module_map_from(scope);
+ let module_map = module_map_rc.borrow();
+
+ let referrer_global = v8::Global::new(scope, referrer);
+
+ let referrer_info = module_map
+ .get_info(&referrer_global)
+ .expect("ModuleInfo not found");
+ let referrer_name = referrer_info.name.as_str();
+
+ let specifier_str = specifier.to_rust_string_lossy(scope);
+
+ let assertions = parse_import_assertions(
+ scope,
+ import_assertions,
+ ImportAssertionsKind::StaticImport,
+ );
+ let maybe_module = module_map.resolve_callback(
+ scope,
+ &specifier_str,
+ referrer_name,
+ assertions,
+ );
+ if let Some(module) = maybe_module {
+ return Some(module);
+ }
+
+ let msg = format!(
+ r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""#
+ );
+ throw_type_error(scope, msg);
+ None
+}
+
+pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
+ let message = v8::String::new(scope, message.as_ref()).unwrap();
+ let exception = v8::Exception::type_error(scope, message);
+ scope.throw_exception(exception);
+}
diff --git a/core/runtime/encode_decode_test.js b/core/runtime/encode_decode_test.js
new file mode 100644
index 000000000..0f4668765
--- /dev/null
+++ b/core/runtime/encode_decode_test.js
@@ -0,0 +1,69 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+"use strict";
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+
+function assertArrayEquals(a1, a2) {
+ if (a1.length !== a2.length) throw Error("assert");
+
+ for (const index in a1) {
+ if (a1[index] !== a2[index]) {
+ throw Error("assert");
+ }
+ }
+}
+
+function main() {
+ // deno-fmt-ignore
+ const fixture1 = [
+ 0xf0, 0x9d, 0x93, 0xbd,
+ 0xf0, 0x9d, 0x93, 0xae,
+ 0xf0, 0x9d, 0x94, 0x81,
+ 0xf0, 0x9d, 0x93, 0xbd
+ ];
+ // deno-fmt-ignore
+ const fixture2 = [
+ 72, 101, 108, 108,
+ 111, 32, 239, 191,
+ 189, 239, 191, 189,
+ 32, 87, 111, 114,
+ 108, 100
+ ];
+
+ const empty = Deno.core.ops.op_encode("");
+ if (empty.length !== 0) throw new Error("assert");
+
+ assertArrayEquals(
+ Array.from(Deno.core.ops.op_encode("𝓽𝓮𝔁𝓽")),
+ fixture1,
+ );
+ assertArrayEquals(
+ Array.from(Deno.core.ops.op_encode("Hello \udc12\ud834 World")),
+ fixture2,
+ );
+
+ const emptyBuf = Deno.core.ops.op_decode(new Uint8Array(0));
+ if (emptyBuf !== "") throw new Error("assert");
+
+ assert(Deno.core.ops.op_decode(new Uint8Array(fixture1)) === "𝓽𝓮𝔁𝓽");
+ assert(
+ Deno.core.ops.op_decode(new Uint8Array(fixture2)) ===
+ "Hello �� World",
+ );
+
+ // See https://github.com/denoland/deno/issues/6649
+ let thrown = false;
+ try {
+ Deno.core.ops.op_decode(new Uint8Array(2 ** 29));
+ } catch (e) {
+ thrown = true;
+ assert(e instanceof RangeError);
+ assert(e.message === "string too long");
+ }
+ assert(thrown);
+}
+
+main();
diff --git a/core/runtime/error_builder_test.js b/core/runtime/error_builder_test.js
new file mode 100644
index 000000000..f442819cb
--- /dev/null
+++ b/core/runtime/error_builder_test.js
@@ -0,0 +1,32 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+const { core } = Deno;
+const { ops } = core;
+
+class DOMException {
+ constructor(message, code) {
+ this.msg = message;
+ this.code = code;
+ }
+}
+
+core.registerErrorBuilder(
+ "DOMExceptionOperationError",
+ function DOMExceptionOperationError(msg) {
+ return new DOMException(msg, "OperationError");
+ },
+);
+
+try {
+ ops.op_err();
+ throw new Error("op_err didn't throw!");
+} catch (err) {
+ if (!(err instanceof DOMException)) {
+ throw new Error("err not DOMException");
+ }
+ if (err.msg !== "abc") {
+ throw new Error("err.message is incorrect");
+ }
+ if (err.code !== "OperationError") {
+ throw new Error("err.code is incorrect");
+ }
+}
diff --git a/core/runtime/icudtl.dat b/core/runtime/icudtl.dat
new file mode 100644
index 000000000..d1f10917a
--- /dev/null
+++ b/core/runtime/icudtl.dat
Binary files differ
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
new file mode 100644
index 000000000..970b3f5d5
--- /dev/null
+++ b/core/runtime/jsrealm.rs
@@ -0,0 +1,367 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use super::bindings;
+use crate::error::exception_to_err_result;
+use crate::modules::ModuleCode;
+use crate::ops::OpCtx;
+use crate::runtime::JsRuntimeState;
+use crate::task::MaskResultAsSend;
+use crate::JsRuntime;
+use crate::OpId;
+use crate::OpResult;
+use crate::PromiseId;
+use anyhow::Error;
+use std::cell::RefCell;
+use std::collections::HashSet;
+use std::collections::VecDeque;
+use std::hash::BuildHasherDefault;
+use std::hash::Hasher;
+use std::option::Option;
+use std::rc::Rc;
+use tokio::task::JoinSet;
+use v8::HandleScope;
+use v8::Local;
+
+// Hasher used for `unrefed_ops`. Since these are rolling i32, there's no
+// need to actually hash them.
+#[derive(Default)]
+pub(crate) struct IdentityHasher(u64);
+
+impl Hasher for IdentityHasher {
+ fn write_i32(&mut self, i: i32) {
+ self.0 = i as u64;
+ }
+
+ fn finish(&self) -> u64 {
+ self.0
+ }
+
+ fn write(&mut self, _bytes: &[u8]) {
+ unreachable!()
+ }
+}
+
+#[derive(Default)]
+pub(crate) struct ContextState {
+ pub(crate) js_event_loop_tick_cb: Option<Rc<v8::Global<v8::Function>>>,
+ pub(crate) js_build_custom_error_cb: Option<Rc<v8::Global<v8::Function>>>,
+ pub(crate) js_promise_reject_cb: Option<Rc<v8::Global<v8::Function>>>,
+ pub(crate) js_format_exception_cb: Option<Rc<v8::Global<v8::Function>>>,
+ pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
+ pub(crate) pending_promise_rejections:
+ VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>,
+ pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
+ pub(crate) pending_ops:
+ JoinSet<MaskResultAsSend<(PromiseId, OpId, OpResult)>>,
+ // We don't explicitly re-read this prop but need the slice to live alongside
+ // the context
+ pub(crate) op_ctxs: Box<[OpCtx]>,
+ pub(crate) isolate: Option<*mut v8::OwnedIsolate>,
+}
+
+/// A representation of a JavaScript realm tied to a [`JsRuntime`], that allows
+/// execution in the realm's context.
+///
+/// A [`JsRealm`] instance is a reference to an already existing realm, which
+/// does not hold ownership of it, so instances can be created and dropped as
+/// needed. As such, calling [`JsRealm::new`] doesn't create a new realm, and
+/// cloning a [`JsRealm`] only creates a new reference. See
+/// [`JsRuntime::create_realm`] to create new realms instead.
+///
+/// Despite [`JsRealm`] instances being references, multiple instances that
+/// point to the same realm won't overlap because every operation requires
+/// passing a mutable reference to the [`v8::Isolate`]. Therefore, no operation
+/// on two [`JsRealm`] instances tied to the same isolate can be run at the same
+/// time, regardless of whether they point to the same realm.
+///
+/// # Panics
+///
+/// Every method of [`JsRealm`] will panic if you call it with a reference to a
+/// [`v8::Isolate`] other than the one that corresponds to the current context.
+///
+/// In other words, the [`v8::Isolate`] parameter for all the related [`JsRealm`] methods
+/// must be extracted from the pre-existing [`JsRuntime`].
+///
+/// Example usage with the [`JsRealm::execute_script`] method:
+/// ```
+/// use deno_core::JsRuntime;
+/// use deno_core::RuntimeOptions;
+///
+/// let mut runtime = JsRuntime::new(RuntimeOptions::default());
+/// let new_realm = runtime
+/// .create_realm()
+/// .expect("Handle the error properly");
+/// let source_code = "var a = 0; a + 1";
+/// let result = new_realm
+/// .execute_script_static(runtime.v8_isolate(), "<anon>", source_code)
+/// .expect("Handle the error properly");
+/// # drop(result);
+/// ```
+///
+/// # Lifetime of the realm
+///
+/// As long as the corresponding isolate is alive, a [`JsRealm`] instance will
+/// keep the underlying V8 context alive even if it would have otherwise been
+/// garbage collected.
+#[derive(Clone)]
+#[repr(transparent)]
+pub struct JsRealm(pub(crate) JsRealmInner);
+
+#[derive(Clone)]
+pub(crate) struct JsRealmInner {
+ context_state: Rc<RefCell<ContextState>>,
+ context: Rc<v8::Global<v8::Context>>,
+ runtime_state: Rc<RefCell<JsRuntimeState>>,
+ is_global: bool,
+}
+
+impl JsRealmInner {
+ pub(crate) fn new(
+ context_state: Rc<RefCell<ContextState>>,
+ context: v8::Global<v8::Context>,
+ runtime_state: Rc<RefCell<JsRuntimeState>>,
+ is_global: bool,
+ ) -> Self {
+ Self {
+ context_state,
+ context: context.into(),
+ runtime_state,
+ is_global,
+ }
+ }
+
+ pub fn num_pending_ops(&self) -> usize {
+ self.context_state.borrow().pending_ops.len()
+ }
+
+ pub fn num_unrefed_ops(&self) -> usize {
+ self.context_state.borrow().unrefed_ops.len()
+ }
+
+ #[inline(always)]
+ pub fn context(&self) -> &v8::Global<v8::Context> {
+ &self.context
+ }
+
+ #[inline(always)]
+ pub(crate) fn state(&self) -> Rc<RefCell<ContextState>> {
+ self.context_state.clone()
+ }
+
+ /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
+ #[inline(always)]
+ pub fn handle_scope<'s>(
+ &self,
+ isolate: &'s mut v8::Isolate,
+ ) -> v8::HandleScope<'s> {
+ v8::HandleScope::with_context(isolate, &*self.context)
+ }
+
+ pub(crate) fn check_promise_rejections(
+ &self,
+ scope: &mut v8::HandleScope,
+ ) -> Result<(), Error> {
+ let Some((_, handle)) = self.context_state.borrow_mut().pending_promise_rejections.pop_front() else {
+ return Ok(());
+ };
+
+ let exception = v8::Local::new(scope, handle);
+ let state_rc = JsRuntime::state_from(scope);
+ let state = state_rc.borrow();
+ if let Some(inspector) = &state.inspector {
+ let inspector = inspector.borrow();
+ inspector.exception_thrown(scope, exception, true);
+ if inspector.has_blocking_sessions() {
+ return Ok(());
+ }
+ }
+ exception_to_err_result(scope, exception, true)
+ }
+
+ pub(crate) fn is_same(&self, other: &Rc<v8::Global<v8::Context>>) -> bool {
+ Rc::ptr_eq(&self.context, other)
+ }
+
+ pub fn destroy(self) {
+ let state = self.state();
+ let raw_ptr = self.state().borrow().isolate.unwrap();
+ // SAFETY: We know the isolate outlives the realm
+ let isolate = unsafe { raw_ptr.as_mut().unwrap() };
+ let mut realm_state = state.borrow_mut();
+ // These globals will prevent snapshots from completing, take them
+ std::mem::take(&mut realm_state.js_event_loop_tick_cb);
+ std::mem::take(&mut realm_state.js_build_custom_error_cb);
+ std::mem::take(&mut realm_state.js_promise_reject_cb);
+ std::mem::take(&mut realm_state.js_format_exception_cb);
+ std::mem::take(&mut realm_state.js_wasm_streaming_cb);
+ // The OpCtx slice may contain a circular reference
+ std::mem::take(&mut realm_state.op_ctxs);
+
+ self.context().open(isolate).clear_all_slots(isolate);
+
+ // Expect that this context is dead (we only check this in debug mode)
+ // TODO(mmastrac): This check fails for some tests, will need to fix this
+ // debug_assert_eq!(Rc::strong_count(&self.context), 1, "Realm was still alive when we wanted to destory it. Not dropped?");
+ }
+}
+
+impl JsRealm {
+ pub(crate) fn new(inner: JsRealmInner) -> Self {
+ Self(inner)
+ }
+
+ #[inline(always)]
+ pub(crate) fn state_from_scope(
+ scope: &mut v8::HandleScope,
+ ) -> Rc<RefCell<ContextState>> {
+ let context = scope.get_current_context();
+ context
+ .get_slot::<Rc<RefCell<ContextState>>>(scope)
+ .unwrap()
+ .clone()
+ }
+
+ #[inline(always)]
+ pub fn num_pending_ops(&self) -> usize {
+ self.0.num_pending_ops()
+ }
+
+ #[inline(always)]
+ pub fn num_unrefed_ops(&self) -> usize {
+ self.0.num_unrefed_ops()
+ }
+
+ /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
+ #[inline(always)]
+ pub fn handle_scope<'s>(
+ &self,
+ isolate: &'s mut v8::Isolate,
+ ) -> v8::HandleScope<'s> {
+ self.0.handle_scope(isolate)
+ }
+
+ #[inline(always)]
+ pub fn context(&self) -> &v8::Global<v8::Context> {
+ self.0.context()
+ }
+
+ /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
+ pub fn global_object<'s>(
+ &self,
+ isolate: &'s mut v8::Isolate,
+ ) -> v8::Local<'s, v8::Object> {
+ let scope = &mut self.0.handle_scope(isolate);
+ self.0.context.open(scope).global(scope)
+ }
+
+ fn string_from_code<'a>(
+ scope: &mut HandleScope<'a>,
+ code: &ModuleCode,
+ ) -> Option<Local<'a, v8::String>> {
+ if let Some(code) = code.try_static_ascii() {
+ v8::String::new_external_onebyte_static(scope, code)
+ } else {
+ v8::String::new_from_utf8(
+ scope,
+ code.as_bytes(),
+ v8::NewStringType::Normal,
+ )
+ }
+ }
+
+ /// Executes traditional JavaScript code (traditional = not ES modules) in the
+ /// realm's context.
+ ///
+ /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
+ ///
+ /// The `name` parameter can be a filepath or any other string. E.g.:
+ ///
+ /// - "/some/file/path.js"
+ /// - "<anon>"
+ /// - "[native code]"
+ ///
+ /// The same `name` value can be used for multiple executions.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn execute_script_static(
+ &self,
+ isolate: &mut v8::Isolate,
+ name: &'static str,
+ source_code: &'static str,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ self.execute_script(isolate, name, ModuleCode::from_static(source_code))
+ }
+
+ /// Executes traditional JavaScript code (traditional = not ES modules) in the
+ /// realm's context.
+ ///
+ /// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
+ ///
+ /// The `name` parameter can be a filepath or any other string. E.g.:
+ ///
+ /// - "/some/file/path.js"
+ /// - "<anon>"
+ /// - "[native code]"
+ ///
+ /// The same `name` value can be used for multiple executions.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn execute_script(
+ &self,
+ isolate: &mut v8::Isolate,
+ name: &'static str,
+ source_code: ModuleCode,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ let scope = &mut self.0.handle_scope(isolate);
+
+ let source = Self::string_from_code(scope, &source_code).unwrap();
+ debug_assert!(name.is_ascii());
+ let name =
+ v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
+ let origin = bindings::script_origin(scope, name);
+
+ let tc_scope = &mut v8::TryCatch::new(scope);
+
+ let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
+ Some(script) => script,
+ None => {
+ let exception = tc_scope.exception().unwrap();
+ return exception_to_err_result(tc_scope, exception, false);
+ }
+ };
+
+ match script.run(tc_scope) {
+ Some(value) => {
+ let value_handle = v8::Global::new(tc_scope, value);
+ Ok(value_handle)
+ }
+ None => {
+ assert!(tc_scope.has_caught());
+ let exception = tc_scope.exception().unwrap();
+ exception_to_err_result(tc_scope, exception, false)
+ }
+ }
+ }
+
+ // TODO(andreubotella): `mod_evaluate`, `load_main_module`, `load_side_module`
+}
+
+impl Drop for JsRealm {
+ fn drop(&mut self) {
+ // Don't do anything special with the global realm
+ if self.0.is_global {
+ return;
+ }
+
+ // There's us and there's the runtime
+ if Rc::strong_count(&self.0.context) == 2 {
+ self
+ .0
+ .runtime_state
+ .borrow_mut()
+ .remove_realm(&self.0.context);
+ assert_eq!(Rc::strong_count(&self.0.context), 1);
+ self.0.clone().destroy();
+ assert_eq!(Rc::strong_count(&self.0.context_state), 1);
+ }
+ }
+}
diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs
new file mode 100644
index 000000000..3b41a90f1
--- /dev/null
+++ b/core/runtime/jsruntime.rs
@@ -0,0 +1,2416 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use super::bindings;
+use super::jsrealm::JsRealmInner;
+use super::snapshot_util;
+use crate::error::exception_to_err_result;
+use crate::error::generic_error;
+use crate::error::to_v8_type_error;
+use crate::error::GetErrorClassFn;
+use crate::error::JsError;
+use crate::extensions::OpDecl;
+use crate::extensions::OpEventLoopFn;
+use crate::inspector::JsRuntimeInspector;
+use crate::module_specifier::ModuleSpecifier;
+use crate::modules::AssertedModuleType;
+use crate::modules::ExtModuleLoader;
+use crate::modules::ExtModuleLoaderCb;
+use crate::modules::ModuleCode;
+use crate::modules::ModuleError;
+use crate::modules::ModuleId;
+use crate::modules::ModuleLoadId;
+use crate::modules::ModuleLoader;
+use crate::modules::ModuleMap;
+use crate::modules::ModuleName;
+use crate::ops::*;
+use crate::runtime::ContextState;
+use crate::runtime::JsRealm;
+use crate::source_map::SourceMapCache;
+use crate::source_map::SourceMapGetter;
+use crate::Extension;
+use crate::ModuleType;
+use crate::NoopModuleLoader;
+use crate::OpMiddlewareFn;
+use crate::OpResult;
+use crate::OpState;
+use crate::V8_WRAPPER_OBJECT_INDEX;
+use crate::V8_WRAPPER_TYPE_INDEX;
+use anyhow::Context as AnyhowContext;
+use anyhow::Error;
+use futures::channel::oneshot;
+use futures::future::poll_fn;
+use futures::future::Future;
+use futures::stream::StreamExt;
+use smallvec::SmallVec;
+use std::any::Any;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::ffi::c_void;
+use std::mem::ManuallyDrop;
+use std::ops::Deref;
+use std::ops::DerefMut;
+use std::option::Option;
+use std::rc::Rc;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::sync::Once;
+use std::task::Context;
+use std::task::Poll;
+
+const STATE_DATA_OFFSET: u32 = 0;
+const MODULE_MAP_DATA_OFFSET: u32 = 1;
+
+pub enum Snapshot {
+ Static(&'static [u8]),
+ JustCreated(v8::StartupData),
+ Boxed(Box<[u8]>),
+}
+
+/// Objects that need to live as long as the isolate
+#[derive(Default)]
+pub(crate) struct IsolateAllocations {
+ pub(crate) near_heap_limit_callback_data:
+ Option<(Box<RefCell<dyn Any>>, v8::NearHeapLimitCallback)>,
+}
+
+/// ManuallyDrop<Rc<...>> is clone, but it returns a ManuallyDrop<Rc<...>> which is a massive
+/// memory-leak footgun.
+pub(crate) struct ManuallyDropRc<T>(ManuallyDrop<Rc<T>>);
+
+impl<T> ManuallyDropRc<T> {
+ pub fn clone(&self) -> Rc<T> {
+ self.0.deref().clone()
+ }
+}
+
+impl<T> Deref for ManuallyDropRc<T> {
+ type Target = Rc<T>;
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
+ }
+}
+
+impl<T> DerefMut for ManuallyDropRc<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.deref_mut()
+ }
+}
+
+/// This struct contains the [`JsRuntimeState`] and [`v8::OwnedIsolate`] that are required
+/// to do an orderly shutdown of V8. We keep these in a separate struct to allow us to control
+/// the destruction more closely, as snapshots require the isolate to be destroyed by the
+/// snapshot process, not the destructor.
+///
+/// The way rusty_v8 works w/snapshots is that the [`v8::OwnedIsolate`] gets consumed by a
+/// [`v8::snapshot::SnapshotCreator`] that is stored in its annex. It's a bit awkward, because this
+/// means we cannot let it drop (because we don't have it after a snapshot). On top of that, we have
+/// to consume it in the snapshot creator because otherwise it panics.
+///
+/// This inner struct allows us to let the outer JsRuntime drop normally without a Drop impl, while we
+/// control dropping more closely here using ManuallyDrop.
+pub(crate) struct InnerIsolateState {
+ will_snapshot: bool,
+ pub(crate) state: ManuallyDropRc<RefCell<JsRuntimeState>>,
+ v8_isolate: ManuallyDrop<v8::OwnedIsolate>,
+}
+
+impl InnerIsolateState {
+ /// Clean out the opstate and take the inspector to prevent the inspector from getting destroyed
+ /// after we've torn down the contexts. If the inspector is not correctly torn down, random crashes
+ /// happen in tests (and possibly for users using the inspector).
+ pub fn prepare_for_cleanup(&mut self) {
+ let mut state = self.state.borrow_mut();
+ let inspector = state.inspector.take();
+ state.op_state.borrow_mut().clear();
+ if let Some(inspector) = inspector {
+ assert_eq!(
+ Rc::strong_count(&inspector),
+ 1,
+ "The inspector must be dropped before the runtime"
+ );
+ }
+ }
+
+ pub fn cleanup(&mut self) {
+ self.prepare_for_cleanup();
+
+ let state_ptr = self.v8_isolate.get_data(STATE_DATA_OFFSET);
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ _ = unsafe { Rc::from_raw(state_ptr as *const RefCell<JsRuntimeState>) };
+
+ let module_map_ptr = self.v8_isolate.get_data(MODULE_MAP_DATA_OFFSET);
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ _ = unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
+
+ self.state.borrow_mut().destroy_all_realms();
+
+ debug_assert_eq!(Rc::strong_count(&self.state), 1);
+ }
+
+ pub fn prepare_for_snapshot(mut self) -> v8::OwnedIsolate {
+ self.cleanup();
+ // SAFETY: We're copying out of self and then immediately forgetting self
+ let (state, isolate) = unsafe {
+ (
+ ManuallyDrop::take(&mut self.state.0),
+ ManuallyDrop::take(&mut self.v8_isolate),
+ )
+ };
+ std::mem::forget(self);
+ drop(state);
+ isolate
+ }
+}
+
+impl Drop for InnerIsolateState {
+ fn drop(&mut self) {
+ self.cleanup();
+ // SAFETY: We gotta drop these
+ unsafe {
+ ManuallyDrop::drop(&mut self.state.0);
+ if self.will_snapshot {
+ // Create the snapshot and just drop it.
+ eprintln!("WARNING: v8::OwnedIsolate for snapshot was leaked");
+ } else {
+ ManuallyDrop::drop(&mut self.v8_isolate);
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum InitMode {
+ /// We have no snapshot -- this is a pristine context.
+ New,
+ /// We are using a snapshot, thus certain initialization steps are skipped.
+ FromSnapshot,
+}
+
+impl InitMode {
+ fn from_options(options: &RuntimeOptions) -> Self {
+ match options.startup_snapshot {
+ None => Self::New,
+ Some(_) => Self::FromSnapshot,
+ }
+ }
+}
+
+/// A single execution context of JavaScript. Corresponds roughly to the "Web
+/// Worker" concept in the DOM.
+////
+/// The JsRuntime future completes when there is an error or when all
+/// pending ops have completed.
+///
+/// Use [`JsRuntimeForSnapshot`] to be able to create a snapshot.
+pub struct JsRuntime {
+ pub(crate) inner: InnerIsolateState,
+ pub(crate) module_map: Rc<RefCell<ModuleMap>>,
+ pub(crate) allocations: IsolateAllocations,
+ extensions: Vec<Extension>,
+ event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
+ init_mode: InitMode,
+ // Marks if this is considered the top-level runtime. Used only be inspector.
+ is_main: bool,
+}
+
+/// The runtime type used for snapshot creation.
+pub struct JsRuntimeForSnapshot(JsRuntime);
+
+impl Deref for JsRuntimeForSnapshot {
+ type Target = JsRuntime;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for JsRuntimeForSnapshot {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+pub(crate) struct DynImportModEvaluate {
+ load_id: ModuleLoadId,
+ module_id: ModuleId,
+ promise: v8::Global<v8::Promise>,
+ module: v8::Global<v8::Module>,
+}
+
+pub(crate) struct ModEvaluate {
+ pub(crate) promise: Option<v8::Global<v8::Promise>>,
+ pub(crate) has_evaluated: bool,
+ pub(crate) handled_promise_rejections: Vec<v8::Global<v8::Promise>>,
+ sender: oneshot::Sender<Result<(), Error>>,
+}
+
+pub struct CrossIsolateStore<T>(Arc<Mutex<CrossIsolateStoreInner<T>>>);
+
+struct CrossIsolateStoreInner<T> {
+ map: HashMap<u32, T>,
+ last_id: u32,
+}
+
+impl<T> CrossIsolateStore<T> {
+ pub(crate) fn insert(&self, value: T) -> u32 {
+ let mut store = self.0.lock().unwrap();
+ let last_id = store.last_id;
+ store.map.insert(last_id, value);
+ store.last_id += 1;
+ last_id
+ }
+
+ pub(crate) fn take(&self, id: u32) -> Option<T> {
+ let mut store = self.0.lock().unwrap();
+ store.map.remove(&id)
+ }
+}
+
+impl<T> Default for CrossIsolateStore<T> {
+ fn default() -> Self {
+ CrossIsolateStore(Arc::new(Mutex::new(CrossIsolateStoreInner {
+ map: Default::default(),
+ last_id: 0,
+ })))
+ }
+}
+
+impl<T> Clone for CrossIsolateStore<T> {
+ fn clone(&self) -> Self {
+ Self(self.0.clone())
+ }
+}
+
+pub type SharedArrayBufferStore =
+ CrossIsolateStore<v8::SharedRef<v8::BackingStore>>;
+
+pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
+
+/// Internal state for JsRuntime which is stored in one of v8::Isolate's
+/// embedder slots.
+pub struct JsRuntimeState {
+ global_realm: Option<JsRealm>,
+ known_realms: Vec<JsRealmInner>,
+ pub(crate) has_tick_scheduled: bool,
+ pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
+ pub(crate) pending_mod_evaluate: Option<ModEvaluate>,
+ /// A counter used to delay our dynamic import deadlock detection by one spin
+ /// of the event loop.
+ dyn_module_evaluate_idle_counter: u32,
+ pub(crate) source_map_getter: Option<Rc<Box<dyn SourceMapGetter>>>,
+ pub(crate) source_map_cache: Rc<RefCell<SourceMapCache>>,
+ pub(crate) op_state: Rc<RefCell<OpState>>,
+ pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>,
+ pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
+ /// The error that was passed to an `op_dispatch_exception` call.
+ /// It will be retrieved by `exception_to_err_result` and used as an error
+ /// instead of any other exceptions.
+ // TODO(nayeemrmn): This is polled in `exception_to_err_result()` which is
+ // flimsy. Try to poll it similarly to `pending_promise_rejections`.
+ pub(crate) dispatched_exception: Option<v8::Global<v8::Value>>,
+ pub(crate) inspector: Option<Rc<RefCell<JsRuntimeInspector>>>,
+}
+
+impl JsRuntimeState {
+ pub(crate) fn destroy_all_realms(&mut self) {
+ self.global_realm.take();
+ for realm in self.known_realms.drain(..) {
+ realm.destroy()
+ }
+ }
+
+ pub(crate) fn remove_realm(
+ &mut self,
+ realm_context: &Rc<v8::Global<v8::Context>>,
+ ) {
+ self
+ .known_realms
+ .retain(|realm| !realm.is_same(realm_context));
+ }
+}
+
+fn v8_init(
+ v8_platform: Option<v8::SharedRef<v8::Platform>>,
+ predictable: bool,
+) {
+ // Include 10MB ICU data file.
+ #[repr(C, align(16))]
+ struct IcuData([u8; 10541264]);
+ static ICU_DATA: IcuData = IcuData(*include_bytes!("icudtl.dat"));
+ v8::icu::set_common_data_72(&ICU_DATA.0).unwrap();
+
+ let flags = concat!(
+ " --wasm-test-streaming",
+ " --harmony-import-assertions",
+ " --no-validate-asm",
+ " --turbo_fast_api_calls",
+ " --harmony-change-array-by-copy",
+ );
+
+ if predictable {
+ v8::V8::set_flags_from_string(&format!(
+ "{}{}",
+ flags, " --predictable --random-seed=42"
+ ));
+ } else {
+ v8::V8::set_flags_from_string(flags);
+ }
+
+ let v8_platform = v8_platform
+ .unwrap_or_else(|| v8::new_default_platform(0, false).make_shared());
+ v8::V8::initialize_platform(v8_platform);
+ v8::V8::initialize();
+}
+
+#[derive(Default)]
+pub struct RuntimeOptions {
+ /// Source map reference for errors.
+ pub source_map_getter: Option<Box<dyn SourceMapGetter>>,
+
+ /// Allows to map error type to a string "class" used to represent
+ /// error in JavaScript.
+ pub get_error_class_fn: Option<GetErrorClassFn>,
+
+ /// Implementation of `ModuleLoader` which will be
+ /// called when V8 requests to load ES modules.
+ ///
+ /// If not provided runtime will error if code being
+ /// executed tries to load modules.
+ pub module_loader: Option<Rc<dyn ModuleLoader>>,
+
+ /// JsRuntime extensions, not to be confused with ES modules.
+ /// Only ops registered by extensions will be initialized. If you need
+ /// to execute JS code from extensions, pass source files in `js` or `esm`
+ /// option on `ExtensionBuilder`.
+ ///
+ /// If you are creating a runtime from a snapshot take care not to include
+ /// JavaScript sources in the extensions.
+ pub extensions: Vec<Extension>,
+
+ /// V8 snapshot that should be loaded on startup.
+ pub startup_snapshot: Option<Snapshot>,
+
+ /// Isolate creation parameters.
+ pub create_params: Option<v8::CreateParams>,
+
+ /// V8 platform instance to use. Used when Deno initializes V8
+ /// (which it only does once), otherwise it's silenty dropped.
+ pub v8_platform: Option<v8::SharedRef<v8::Platform>>,
+
+ /// The store to use for transferring SharedArrayBuffers between isolates.
+ /// If multiple isolates should have the possibility of sharing
+ /// SharedArrayBuffers, they should use the same [SharedArrayBufferStore]. If
+ /// no [SharedArrayBufferStore] is specified, SharedArrayBuffer can not be
+ /// serialized.
+ pub shared_array_buffer_store: Option<SharedArrayBufferStore>,
+
+ /// The store to use for transferring `WebAssembly.Module` objects between
+ /// isolates.
+ /// If multiple isolates should have the possibility of sharing
+ /// `WebAssembly.Module` objects, they should use the same
+ /// [CompiledWasmModuleStore]. If no [CompiledWasmModuleStore] is specified,
+ /// `WebAssembly.Module` objects cannot be serialized.
+ pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
+
+ /// Start inspector instance to allow debuggers to connect.
+ pub inspector: bool,
+
+ /// Describe if this is the main runtime instance, used by debuggers in some
+ /// situation - like disconnecting when program finishes running.
+ pub is_main: bool,
+}
+
+#[derive(Default)]
+pub struct RuntimeSnapshotOptions {
+ /// An optional callback that will be called for each module that is loaded
+ /// during snapshotting. This callback can be used to transpile source on the
+ /// fly, during snapshotting, eg. to transpile TypeScript to JavaScript.
+ pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
+}
+
+impl JsRuntime {
+ /// Only constructor, configuration is done through `options`.
+ pub fn new(mut options: RuntimeOptions) -> JsRuntime {
+ JsRuntime::init_v8(options.v8_platform.take(), cfg!(test));
+ JsRuntime::new_inner(options, false, None)
+ }
+
+ pub(crate) fn state_from(
+ isolate: &v8::Isolate,
+ ) -> Rc<RefCell<JsRuntimeState>> {
+ let state_ptr = isolate.get_data(STATE_DATA_OFFSET);
+ let state_rc =
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ unsafe { Rc::from_raw(state_ptr as *const RefCell<JsRuntimeState>) };
+ let state = state_rc.clone();
+ std::mem::forget(state_rc);
+ state
+ }
+
+ pub(crate) fn module_map_from(
+ isolate: &v8::Isolate,
+ ) -> Rc<RefCell<ModuleMap>> {
+ let module_map_ptr = isolate.get_data(MODULE_MAP_DATA_OFFSET);
+ let module_map_rc =
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
+ let module_map = module_map_rc.clone();
+ std::mem::forget(module_map_rc);
+ module_map
+ }
+
+ pub(crate) fn event_loop_pending_state_from_scope(
+ scope: &mut v8::HandleScope,
+ ) -> EventLoopPendingState {
+ let state = JsRuntime::state_from(scope);
+ let module_map = JsRuntime::module_map_from(scope);
+ let state = EventLoopPendingState::new(
+ scope,
+ &mut state.borrow_mut(),
+ &module_map.borrow(),
+ );
+ state
+ }
+
+ fn init_v8(
+ v8_platform: Option<v8::SharedRef<v8::Platform>>,
+ predictable: bool,
+ ) {
+ static DENO_INIT: Once = Once::new();
+ static DENO_PREDICTABLE: AtomicBool = AtomicBool::new(false);
+ static DENO_PREDICTABLE_SET: AtomicBool = AtomicBool::new(false);
+
+ if DENO_PREDICTABLE_SET.load(Ordering::SeqCst) {
+ let current = DENO_PREDICTABLE.load(Ordering::SeqCst);
+ assert_eq!(current, predictable, "V8 may only be initialized once in either snapshotting or non-snapshotting mode. Either snapshotting or non-snapshotting mode may be used in a single process, not both.");
+ DENO_PREDICTABLE_SET.store(true, Ordering::SeqCst);
+ DENO_PREDICTABLE.store(predictable, Ordering::SeqCst);
+ }
+
+ DENO_INIT.call_once(move || v8_init(v8_platform, predictable));
+ }
+
+ fn new_inner(
+ mut options: RuntimeOptions,
+ will_snapshot: bool,
+ maybe_load_callback: Option<ExtModuleLoaderCb>,
+ ) -> JsRuntime {
+ let init_mode = InitMode::from_options(&options);
+ let (op_state, ops) = Self::create_opstate(&mut options, init_mode);
+ let op_state = Rc::new(RefCell::new(op_state));
+
+ // Collect event-loop middleware
+ let mut event_loop_middlewares =
+ Vec::with_capacity(options.extensions.len());
+ for extension in &mut options.extensions {
+ if let Some(middleware) = extension.init_event_loop_middleware() {
+ event_loop_middlewares.push(middleware);
+ }
+ }
+
+ let align = std::mem::align_of::<usize>();
+ let layout = std::alloc::Layout::from_size_align(
+ std::mem::size_of::<*mut v8::OwnedIsolate>(),
+ align,
+ )
+ .unwrap();
+ assert!(layout.size() > 0);
+ let isolate_ptr: *mut v8::OwnedIsolate =
+ // SAFETY: we just asserted that layout has non-0 size.
+ unsafe { std::alloc::alloc(layout) as *mut _ };
+
+ let state_rc = Rc::new(RefCell::new(JsRuntimeState {
+ pending_dyn_mod_evaluate: vec![],
+ pending_mod_evaluate: None,
+ dyn_module_evaluate_idle_counter: 0,
+ has_tick_scheduled: false,
+ source_map_getter: options.source_map_getter.map(Rc::new),
+ source_map_cache: Default::default(),
+ shared_array_buffer_store: options.shared_array_buffer_store,
+ compiled_wasm_module_store: options.compiled_wasm_module_store,
+ op_state: op_state.clone(),
+ dispatched_exception: None,
+ // Some fields are initialized later after isolate is created
+ inspector: None,
+ global_realm: None,
+ known_realms: Vec::with_capacity(1),
+ }));
+
+ let weak = Rc::downgrade(&state_rc);
+ let context_state = Rc::new(RefCell::new(ContextState::default()));
+ let op_ctxs = ops
+ .into_iter()
+ .enumerate()
+ .map(|(id, decl)| {
+ OpCtx::new(
+ id as u16,
+ context_state.clone(),
+ Rc::new(decl),
+ op_state.clone(),
+ weak.clone(),
+ )
+ })
+ .collect::<Vec<_>>()
+ .into_boxed_slice();
+ context_state.borrow_mut().op_ctxs = op_ctxs;
+ context_state.borrow_mut().isolate = Some(isolate_ptr);
+
+ let refs = bindings::external_references(&context_state.borrow().op_ctxs);
+ // V8 takes ownership of external_references.
+ let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
+
+ let mut isolate = if will_snapshot {
+ snapshot_util::create_snapshot_creator(
+ refs,
+ options.startup_snapshot.take(),
+ )
+ } else {
+ let mut params = options
+ .create_params
+ .take()
+ .unwrap_or_default()
+ .embedder_wrapper_type_info_offsets(
+ V8_WRAPPER_TYPE_INDEX,
+ V8_WRAPPER_OBJECT_INDEX,
+ )
+ .external_references(&**refs);
+ if let Some(snapshot) = options.startup_snapshot.take() {
+ params = match snapshot {
+ Snapshot::Static(data) => params.snapshot_blob(data),
+ Snapshot::JustCreated(data) => params.snapshot_blob(data),
+ Snapshot::Boxed(data) => params.snapshot_blob(data),
+ };
+ }
+ v8::Isolate::new(params)
+ };
+ isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10);
+ isolate.set_promise_reject_callback(bindings::promise_reject_callback);
+ isolate.set_host_initialize_import_meta_object_callback(
+ bindings::host_initialize_import_meta_object_callback,
+ );
+ isolate.set_host_import_module_dynamically_callback(
+ bindings::host_import_module_dynamically_callback,
+ );
+ isolate.set_wasm_async_resolve_promise_callback(
+ bindings::wasm_async_resolve_promise_callback,
+ );
+
+ let (global_context, snapshotted_data) = {
+ let scope = &mut v8::HandleScope::new(&mut isolate);
+ let context = v8::Context::new(scope);
+
+ // Get module map data from the snapshot
+ let snapshotted_data = if init_mode == InitMode::FromSnapshot {
+ Some(snapshot_util::get_snapshotted_data(scope, context))
+ } else {
+ None
+ };
+
+ (v8::Global::new(scope, context), snapshotted_data)
+ };
+
+ // SAFETY: this is first use of `isolate_ptr` so we are sure we're
+ // not overwriting an existing pointer.
+ isolate = unsafe {
+ isolate_ptr.write(isolate);
+ isolate_ptr.read()
+ };
+
+ let mut context_scope: v8::HandleScope =
+ v8::HandleScope::with_context(&mut isolate, global_context.clone());
+ let scope = &mut context_scope;
+ let context = v8::Local::new(scope, global_context.clone());
+
+ bindings::initialize_context(
+ scope,
+ context,
+ &context_state.borrow().op_ctxs,
+ init_mode,
+ );
+
+ context.set_slot(scope, context_state.clone());
+
+ op_state.borrow_mut().put(isolate_ptr);
+ let inspector = if options.inspector {
+ Some(JsRuntimeInspector::new(scope, context, options.is_main))
+ } else {
+ None
+ };
+
+ let loader = options
+ .module_loader
+ .unwrap_or_else(|| Rc::new(NoopModuleLoader));
+
+ {
+ let global_realm = JsRealmInner::new(
+ context_state,
+ global_context,
+ state_rc.clone(),
+ true,
+ );
+ let mut state = state_rc.borrow_mut();
+ state.global_realm = Some(JsRealm::new(global_realm.clone()));
+ state.inspector = inspector;
+ state.known_realms.push(global_realm);
+ }
+ scope.set_data(
+ STATE_DATA_OFFSET,
+ Rc::into_raw(state_rc.clone()) as *mut c_void,
+ );
+ let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader)));
+ if let Some(snapshotted_data) = snapshotted_data {
+ let mut module_map = module_map_rc.borrow_mut();
+ module_map.update_with_snapshotted_data(scope, snapshotted_data);
+ }
+ scope.set_data(
+ MODULE_MAP_DATA_OFFSET,
+ Rc::into_raw(module_map_rc.clone()) as *mut c_void,
+ );
+
+ drop(context_scope);
+
+ let mut js_runtime = JsRuntime {
+ inner: InnerIsolateState {
+ will_snapshot,
+ state: ManuallyDropRc(ManuallyDrop::new(state_rc)),
+ v8_isolate: ManuallyDrop::new(isolate),
+ },
+ init_mode,
+ allocations: IsolateAllocations::default(),
+ event_loop_middlewares,
+ extensions: options.extensions,
+ module_map: module_map_rc,
+ is_main: options.is_main,
+ };
+
+ let realm = js_runtime.global_realm();
+ // TODO(mmastrac): We should thread errors back out of the runtime
+ js_runtime
+ .init_extension_js(&realm, maybe_load_callback)
+ .unwrap();
+ js_runtime
+ }
+
+ #[cfg(test)]
+ #[inline]
+ pub(crate) fn module_map(&self) -> &Rc<RefCell<ModuleMap>> {
+ &self.module_map
+ }
+
+ #[inline]
+ pub fn global_context(&self) -> v8::Global<v8::Context> {
+ self
+ .inner
+ .state
+ .borrow()
+ .known_realms
+ .get(0)
+ .unwrap()
+ .context()
+ .clone()
+ }
+
+ #[inline]
+ pub fn v8_isolate(&mut self) -> &mut v8::OwnedIsolate {
+ &mut self.inner.v8_isolate
+ }
+
+ #[inline]
+ pub fn inspector(&mut self) -> Rc<RefCell<JsRuntimeInspector>> {
+ self.inner.state.borrow().inspector()
+ }
+
+ #[inline]
+ pub fn global_realm(&mut self) -> JsRealm {
+ let state = self.inner.state.borrow();
+ state.global_realm.clone().unwrap()
+ }
+
+ /// Returns the extensions that this runtime is using (including internal ones).
+ pub fn extensions(&self) -> &Vec<Extension> {
+ &self.extensions
+ }
+
+ /// Creates a new realm (V8 context) in this JS execution context,
+ /// pre-initialized with all of the extensions that were passed in
+ /// [`RuntimeOptions::extensions`] when the [`JsRuntime`] was
+ /// constructed.
+ pub fn create_realm(&mut self) -> Result<JsRealm, Error> {
+ let realm = {
+ let context_state = Rc::new(RefCell::new(ContextState::default()));
+ let op_ctxs: Box<[OpCtx]> = self
+ .global_realm()
+ .0
+ .state()
+ .borrow()
+ .op_ctxs
+ .iter()
+ .map(|op_ctx| {
+ OpCtx::new(
+ op_ctx.id,
+ context_state.clone(),
+ op_ctx.decl.clone(),
+ op_ctx.state.clone(),
+ op_ctx.runtime_state.clone(),
+ )
+ })
+ .collect();
+ context_state.borrow_mut().op_ctxs = op_ctxs;
+ context_state.borrow_mut().isolate = Some(self.v8_isolate() as _);
+
+ let raw_ptr = self.v8_isolate() as *mut v8::OwnedIsolate;
+ // SAFETY: Having the scope tied to self's lifetime makes it impossible to
+ // reference JsRuntimeState::op_ctxs while the scope is alive. Here we
+ // turn it into an unbound lifetime, which is sound because 1. it only
+ // lives until the end of this block, and 2. the HandleScope only has
+ // access to the isolate, and nothing else we're accessing from self does.
+ let isolate = unsafe { raw_ptr.as_mut() }.unwrap();
+ let scope = &mut v8::HandleScope::new(isolate);
+ let context = v8::Context::new(scope);
+ let scope = &mut v8::ContextScope::new(scope, context);
+
+ let context = bindings::initialize_context(
+ scope,
+ context,
+ &context_state.borrow().op_ctxs,
+ self.init_mode,
+ );
+ context.set_slot(scope, context_state.clone());
+ let realm = JsRealmInner::new(
+ context_state,
+ v8::Global::new(scope, context),
+ self.inner.state.clone(),
+ false,
+ );
+ let mut state = self.inner.state.borrow_mut();
+ state.known_realms.push(realm.clone());
+ JsRealm::new(realm)
+ };
+
+ self.init_extension_js(&realm, None)?;
+ Ok(realm)
+ }
+
+ #[inline]
+ pub fn handle_scope(&mut self) -> v8::HandleScope {
+ self.global_realm().handle_scope(self.v8_isolate())
+ }
+
+ /// Initializes JS of provided Extensions in the given realm.
+ fn init_extension_js(
+ &mut self,
+ realm: &JsRealm,
+ maybe_load_callback: Option<ExtModuleLoaderCb>,
+ ) -> Result<(), Error> {
+ // Initialization of JS happens in phases:
+ // 1. Iterate through all extensions:
+ // a. Execute all extension "script" JS files
+ // b. Load all extension "module" JS files (but do not execute them yet)
+ // 2. Iterate through all extensions:
+ // a. If an extension has a `esm_entry_point`, execute it.
+
+ // Take extensions temporarily so we can avoid have a mutable reference to self
+ let extensions = std::mem::take(&mut self.extensions);
+
+ // TODO(nayeemrmn): Module maps should be per-realm.
+ let loader = self.module_map.borrow().loader.clone();
+ let ext_loader = Rc::new(ExtModuleLoader::new(
+ &extensions,
+ maybe_load_callback.map(Rc::new),
+ ));
+ self.module_map.borrow_mut().loader = ext_loader;
+
+ let mut esm_entrypoints = vec![];
+
+ futures::executor::block_on(async {
+ for extension in &extensions {
+ let maybe_esm_entry_point = extension.get_esm_entry_point();
+
+ if let Some(esm_files) = extension.get_esm_sources() {
+ for file_source in esm_files {
+ self
+ .load_side_module(
+ &ModuleSpecifier::parse(file_source.specifier)?,
+ None,
+ )
+ .await?;
+ }
+ }
+
+ if let Some(entry_point) = maybe_esm_entry_point {
+ esm_entrypoints.push(entry_point);
+ }
+
+ if let Some(js_files) = extension.get_js_sources() {
+ for file_source in js_files {
+ realm.execute_script(
+ self.v8_isolate(),
+ file_source.specifier,
+ file_source.load()?,
+ )?;
+ }
+ }
+
+ if extension.is_core {
+ self.init_cbs(realm);
+ }
+ }
+
+ for specifier in esm_entrypoints {
+ let mod_id = {
+ self
+ .module_map
+ .borrow()
+ .get_id(specifier, AssertedModuleType::JavaScriptOrWasm)
+ .unwrap_or_else(|| {
+ panic!("{} not present in the module map", specifier)
+ })
+ };
+ let receiver = self.mod_evaluate(mod_id);
+ self.run_event_loop(false).await?;
+ receiver
+ .await?
+ .with_context(|| format!("Couldn't execute '{specifier}'"))?;
+ }
+
+ #[cfg(debug_assertions)]
+ {
+ let module_map_rc = self.module_map.clone();
+ let mut scope = realm.handle_scope(self.v8_isolate());
+ let module_map = module_map_rc.borrow();
+ module_map.assert_all_modules_evaluated(&mut scope);
+ }
+
+ Ok::<_, anyhow::Error>(())
+ })?;
+
+ self.extensions = extensions;
+ self.module_map.borrow_mut().loader = loader;
+ Ok(())
+ }
+
+ /// Collects ops from extensions & applies middleware
+ fn collect_ops(exts: &mut [Extension]) -> Vec<OpDecl> {
+ for (ext, previous_exts) in
+ exts.iter().enumerate().map(|(i, ext)| (ext, &exts[..i]))
+ {
+ ext.check_dependencies(previous_exts);
+ }
+
+ // Middleware
+ let middleware: Vec<Box<OpMiddlewareFn>> = exts
+ .iter_mut()
+ .filter_map(|e| e.init_middleware())
+ .collect();
+
+ // macroware wraps an opfn in all the middleware
+ let macroware = move |d| middleware.iter().fold(d, |d, m| m(d));
+
+ // Flatten ops, apply middlware & override disabled ops
+ let ops: Vec<_> = exts
+ .iter_mut()
+ .filter_map(|e| e.init_ops())
+ .flatten()
+ .map(|d| OpDecl {
+ name: d.name,
+ ..macroware(d)
+ })
+ .collect();
+
+ // In debug build verify there are no duplicate ops.
+ #[cfg(debug_assertions)]
+ {
+ let mut count_by_name = HashMap::new();
+
+ for op in ops.iter() {
+ count_by_name
+ .entry(&op.name)
+ .or_insert(vec![])
+ .push(op.name.to_string());
+ }
+
+ let mut duplicate_ops = vec![];
+ for (op_name, _count) in
+ count_by_name.iter().filter(|(_k, v)| v.len() > 1)
+ {
+ duplicate_ops.push(op_name.to_string());
+ }
+ if !duplicate_ops.is_empty() {
+ let mut msg = "Found ops with duplicate names:\n".to_string();
+ for op_name in duplicate_ops {
+ msg.push_str(&format!(" - {}\n", op_name));
+ }
+ msg.push_str("Op names need to be unique.");
+ panic!("{}", msg);
+ }
+ }
+
+ ops
+ }
+
+ /// Initializes ops of provided Extensions
+ fn create_opstate(
+ options: &mut RuntimeOptions,
+ init_mode: InitMode,
+ ) -> (OpState, Vec<OpDecl>) {
+ // Add built-in extension
+ if init_mode == InitMode::FromSnapshot {
+ options
+ .extensions
+ .insert(0, crate::ops_builtin::core::init_ops());
+ } else {
+ options
+ .extensions
+ .insert(0, crate::ops_builtin::core::init_ops_and_esm());
+ }
+
+ let ops = Self::collect_ops(&mut options.extensions);
+
+ let mut op_state = OpState::new(ops.len());
+
+ if let Some(get_error_class_fn) = options.get_error_class_fn {
+ op_state.get_error_class_fn = get_error_class_fn;
+ }
+
+ // Setup state
+ for e in &mut options.extensions {
+ // ops are already registered during in bindings::initialize_context();
+ e.init_state(&mut op_state);
+ }
+
+ (op_state, ops)
+ }
+
+ pub fn eval<'s, T>(
+ scope: &mut v8::HandleScope<'s>,
+ code: &str,
+ ) -> Option<v8::Local<'s, T>>
+ where
+ v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Value>, Error = v8::DataError>,
+ {
+ let scope = &mut v8::EscapableHandleScope::new(scope);
+ let source = v8::String::new(scope, code).unwrap();
+ let script = v8::Script::compile(scope, source, None).unwrap();
+ let v = script.run(scope)?;
+ scope.escape(v).try_into().ok()
+ }
+
+ /// Grabs a reference to core.js' eventLoopTick & buildCustomError
+ fn init_cbs(&mut self, realm: &JsRealm) {
+ let (event_loop_tick_cb, build_custom_error_cb) = {
+ let scope = &mut realm.handle_scope(self.v8_isolate());
+ let context = realm.context();
+ let context_local = v8::Local::new(scope, context);
+ let global = context_local.global(scope);
+ let deno_str =
+ v8::String::new_external_onebyte_static(scope, b"Deno").unwrap();
+ let core_str =
+ v8::String::new_external_onebyte_static(scope, b"core").unwrap();
+ let event_loop_tick_str =
+ v8::String::new_external_onebyte_static(scope, b"eventLoopTick")
+ .unwrap();
+ let build_custom_error_str =
+ v8::String::new_external_onebyte_static(scope, b"buildCustomError")
+ .unwrap();
+
+ let deno_obj: v8::Local<v8::Object> = global
+ .get(scope, deno_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let core_obj: v8::Local<v8::Object> = deno_obj
+ .get(scope, core_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let event_loop_tick_cb: v8::Local<v8::Function> = core_obj
+ .get(scope, event_loop_tick_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let build_custom_error_cb: v8::Local<v8::Function> = core_obj
+ .get(scope, build_custom_error_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ (
+ v8::Global::new(scope, event_loop_tick_cb),
+ v8::Global::new(scope, build_custom_error_cb),
+ )
+ };
+
+ // Put global handles in the realm's ContextState
+ let state_rc = realm.0.state();
+ let mut state = state_rc.borrow_mut();
+ state
+ .js_event_loop_tick_cb
+ .replace(Rc::new(event_loop_tick_cb));
+ state
+ .js_build_custom_error_cb
+ .replace(Rc::new(build_custom_error_cb));
+ }
+
+ /// Returns the runtime's op state, which can be used to maintain ops
+ /// and access resources between op calls.
+ pub fn op_state(&mut self) -> Rc<RefCell<OpState>> {
+ let state = self.inner.state.borrow();
+ state.op_state.clone()
+ }
+
+ /// Executes traditional JavaScript code (traditional = not ES modules).
+ ///
+ /// The execution takes place on the current global context, so it is possible
+ /// to maintain local JS state and invoke this method multiple times.
+ ///
+ /// `name` can be a filepath or any other string, but it is required to be 7-bit ASCII, eg.
+ ///
+ /// - "/some/file/path.js"
+ /// - "<anon>"
+ /// - "[native code]"
+ ///
+ /// The same `name` value can be used for multiple executions.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn execute_script(
+ &mut self,
+ name: &'static str,
+ source_code: ModuleCode,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ self
+ .global_realm()
+ .execute_script(self.v8_isolate(), name, source_code)
+ }
+
+ /// Executes traditional JavaScript code (traditional = not ES modules).
+ ///
+ /// The execution takes place on the current global context, so it is possible
+ /// to maintain local JS state and invoke this method multiple times.
+ ///
+ /// `name` can be a filepath or any other string, but it is required to be 7-bit ASCII, eg.
+ ///
+ /// - "/some/file/path.js"
+ /// - "<anon>"
+ /// - "[native code]"
+ ///
+ /// The same `name` value can be used for multiple executions.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn execute_script_static(
+ &mut self,
+ name: &'static str,
+ source_code: &'static str,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ self.global_realm().execute_script(
+ self.v8_isolate(),
+ name,
+ ModuleCode::from_static(source_code),
+ )
+ }
+
+ /// Call a function. If it returns a promise, run the event loop until that
+ /// promise is settled. If the promise rejects or there is an uncaught error
+ /// in the event loop, return `Err(error)`. Or return `Ok(<await returned>)`.
+ pub async fn call_and_await(
+ &mut self,
+ function: &v8::Global<v8::Function>,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ let promise = {
+ let scope = &mut self.handle_scope();
+ let cb = function.open(scope);
+ let this = v8::undefined(scope).into();
+ let promise = cb.call(scope, this, &[]);
+ if promise.is_none() || scope.is_execution_terminating() {
+ let undefined = v8::undefined(scope).into();
+ return exception_to_err_result(scope, undefined, false);
+ }
+ v8::Global::new(scope, promise.unwrap())
+ };
+ self.resolve_value(promise).await
+ }
+
+ /// Returns the namespace object of a module.
+ ///
+ /// This is only available after module evaluation has completed.
+ /// This function panics if module has not been instantiated.
+ pub fn get_module_namespace(
+ &mut self,
+ module_id: ModuleId,
+ ) -> Result<v8::Global<v8::Object>, Error> {
+ let module_handle = self
+ .module_map
+ .borrow()
+ .get_handle(module_id)
+ .expect("ModuleInfo not found");
+
+ let scope = &mut self.handle_scope();
+
+ let module = module_handle.open(scope);
+
+ if module.get_status() == v8::ModuleStatus::Errored {
+ let exception = module.get_exception();
+ return exception_to_err_result(scope, exception, false);
+ }
+
+ assert!(matches!(
+ module.get_status(),
+ v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated
+ ));
+
+ let module_namespace: v8::Local<v8::Object> =
+ v8::Local::try_from(module.get_module_namespace())
+ .map_err(|err: v8::DataError| generic_error(err.to_string()))?;
+
+ Ok(v8::Global::new(scope, module_namespace))
+ }
+
+ /// Registers a callback on the isolate when the memory limits are approached.
+ /// Use this to prevent V8 from crashing the process when reaching the limit.
+ ///
+ /// Calls the closure with the current heap limit and the initial heap limit.
+ /// The return value of the closure is set as the new limit.
+ pub fn add_near_heap_limit_callback<C>(&mut self, cb: C)
+ where
+ C: FnMut(usize, usize) -> usize + 'static,
+ {
+ let boxed_cb = Box::new(RefCell::new(cb));
+ let data = boxed_cb.as_ptr() as *mut c_void;
+
+ let prev = self
+ .allocations
+ .near_heap_limit_callback_data
+ .replace((boxed_cb, near_heap_limit_callback::<C>));
+ if let Some((_, prev_cb)) = prev {
+ self
+ .v8_isolate()
+ .remove_near_heap_limit_callback(prev_cb, 0);
+ }
+
+ self
+ .v8_isolate()
+ .add_near_heap_limit_callback(near_heap_limit_callback::<C>, data);
+ }
+
+ pub fn remove_near_heap_limit_callback(&mut self, heap_limit: usize) {
+ if let Some((_, cb)) = self.allocations.near_heap_limit_callback_data.take()
+ {
+ self
+ .v8_isolate()
+ .remove_near_heap_limit_callback(cb, heap_limit);
+ }
+ }
+
+ fn pump_v8_message_loop(&mut self) -> Result<(), Error> {
+ let scope = &mut self.handle_scope();
+ while v8::Platform::pump_message_loop(
+ &v8::V8::get_current_platform(),
+ scope,
+ false, // don't block if there are no tasks
+ ) {
+ // do nothing
+ }
+
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ tc_scope.perform_microtask_checkpoint();
+ match tc_scope.exception() {
+ None => Ok(()),
+ Some(exception) => exception_to_err_result(tc_scope, exception, false),
+ }
+ }
+
+ pub fn maybe_init_inspector(&mut self) {
+ if self.inner.state.borrow().inspector.is_some() {
+ return;
+ }
+
+ let context = self.global_context();
+ let scope = &mut v8::HandleScope::with_context(
+ self.inner.v8_isolate.as_mut(),
+ context.clone(),
+ );
+ let context = v8::Local::new(scope, context);
+
+ let mut state = self.inner.state.borrow_mut();
+ state.inspector =
+ Some(JsRuntimeInspector::new(scope, context, self.is_main));
+ }
+
+ pub fn poll_value(
+ &mut self,
+ global: &v8::Global<v8::Value>,
+ cx: &mut Context,
+ ) -> Poll<Result<v8::Global<v8::Value>, Error>> {
+ let state = self.poll_event_loop(cx, false);
+
+ let mut scope = self.handle_scope();
+ let local = v8::Local::<v8::Value>::new(&mut scope, global);
+
+ if let Ok(promise) = v8::Local::<v8::Promise>::try_from(local) {
+ match promise.state() {
+ v8::PromiseState::Pending => match state {
+ Poll::Ready(Ok(_)) => {
+ let msg = "Promise resolution is still pending but the event loop has already resolved.";
+ Poll::Ready(Err(generic_error(msg)))
+ }
+ Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
+ Poll::Pending => Poll::Pending,
+ },
+ v8::PromiseState::Fulfilled => {
+ let value = promise.result(&mut scope);
+ let value_handle = v8::Global::new(&mut scope, value);
+ Poll::Ready(Ok(value_handle))
+ }
+ v8::PromiseState::Rejected => {
+ let exception = promise.result(&mut scope);
+ Poll::Ready(exception_to_err_result(&mut scope, exception, false))
+ }
+ }
+ } else {
+ let value_handle = v8::Global::new(&mut scope, local);
+ Poll::Ready(Ok(value_handle))
+ }
+ }
+
+ /// Waits for the given value to resolve while polling the event loop.
+ ///
+ /// This future resolves when either the value is resolved or the event loop runs to
+ /// completion.
+ pub async fn resolve_value(
+ &mut self,
+ global: v8::Global<v8::Value>,
+ ) -> Result<v8::Global<v8::Value>, Error> {
+ poll_fn(|cx| self.poll_value(&global, cx)).await
+ }
+
+ /// Runs event loop to completion
+ ///
+ /// This future resolves when:
+ /// - there are no more pending dynamic imports
+ /// - there are no more pending ops
+ /// - there are no more active inspector sessions (only if `wait_for_inspector` is set to true)
+ pub async fn run_event_loop(
+ &mut self,
+ wait_for_inspector: bool,
+ ) -> Result<(), Error> {
+ poll_fn(|cx| self.poll_event_loop(cx, wait_for_inspector)).await
+ }
+
+ /// Runs a single tick of event loop
+ ///
+ /// If `wait_for_inspector` is set to true event loop
+ /// will return `Poll::Pending` if there are active inspector sessions.
+ pub fn poll_event_loop(
+ &mut self,
+ cx: &mut Context,
+ wait_for_inspector: bool,
+ ) -> Poll<Result<(), Error>> {
+ let has_inspector: bool;
+
+ {
+ let state = self.inner.state.borrow();
+ has_inspector = state.inspector.is_some();
+ state.op_state.borrow().waker.register(cx.waker());
+ }
+
+ if has_inspector {
+ // We poll the inspector first.
+ let _ = self.inspector().borrow().poll_sessions(Some(cx)).unwrap();
+ }
+
+ self.pump_v8_message_loop()?;
+
+ // Dynamic module loading - ie. modules loaded using "import()"
+ {
+ // Run in a loop so that dynamic imports that only depend on another
+ // dynamic import can be resolved in this event loop iteration.
+ //
+ // For example, a dynamically imported module like the following can be
+ // immediately resolved after `dependency.ts` is fully evaluated, but it
+ // wouldn't if not for this loop.
+ //
+ // await delay(1000);
+ // await import("./dependency.ts");
+ // console.log("test")
+ //
+ loop {
+ let poll_imports = self.prepare_dyn_imports(cx)?;
+ assert!(poll_imports.is_ready());
+
+ let poll_imports = self.poll_dyn_imports(cx)?;
+ assert!(poll_imports.is_ready());
+
+ if !self.evaluate_dyn_imports() {
+ break;
+ }
+ }
+ }
+
+ // Resolve async ops, run all next tick callbacks and macrotasks callbacks
+ // and only then check for any promise exceptions (`unhandledrejection`
+ // handlers are run in macrotasks callbacks so we need to let them run
+ // first).
+ self.do_js_event_loop_tick(cx)?;
+ self.check_promise_rejections()?;
+
+ // Event loop middlewares
+ let mut maybe_scheduling = false;
+ {
+ let op_state = self.inner.state.borrow().op_state.clone();
+ for f in &self.event_loop_middlewares {
+ if f(op_state.clone(), cx) {
+ maybe_scheduling = true;
+ }
+ }
+ }
+
+ // Top level module
+ self.evaluate_pending_module();
+
+ let pending_state = self.event_loop_pending_state();
+ if !pending_state.is_pending() && !maybe_scheduling {
+ if has_inspector {
+ let inspector = self.inspector();
+ let has_active_sessions = inspector.borrow().has_active_sessions();
+ let has_blocking_sessions = inspector.borrow().has_blocking_sessions();
+
+ if wait_for_inspector && has_active_sessions {
+ // If there are no blocking sessions (eg. REPL) we can now notify
+ // debugger that the program has finished running and we're ready
+ // to exit the process once debugger disconnects.
+ if !has_blocking_sessions {
+ let context = self.global_context();
+ let scope = &mut self.handle_scope();
+ inspector.borrow_mut().context_destroyed(scope, context);
+ println!("Program finished. Waiting for inspector to disconnect to exit the process...");
+ }
+
+ return Poll::Pending;
+ }
+ }
+
+ return Poll::Ready(Ok(()));
+ }
+
+ let state = self.inner.state.borrow();
+
+ // Check if more async ops have been dispatched
+ // during this turn of event loop.
+ // If there are any pending background tasks, we also wake the runtime to
+ // make sure we don't miss them.
+ // TODO(andreubotella) The event loop will spin as long as there are pending
+ // background tasks. We should look into having V8 notify us when a
+ // background task is done.
+ if pending_state.has_pending_background_tasks
+ || pending_state.has_tick_scheduled
+ || maybe_scheduling
+ {
+ state.op_state.borrow().waker.wake();
+ }
+
+ drop(state);
+
+ if pending_state.has_pending_module_evaluation {
+ if pending_state.has_pending_refed_ops
+ || pending_state.has_pending_dyn_imports
+ || pending_state.has_pending_dyn_module_evaluation
+ || pending_state.has_pending_background_tasks
+ || pending_state.has_tick_scheduled
+ || maybe_scheduling
+ {
+ // pass, will be polled again
+ } else {
+ let scope = &mut self.handle_scope();
+ let messages = find_stalled_top_level_await(scope);
+ // We are gonna print only a single message to provide a nice formatting
+ // with source line of offending promise shown. Once user fixed it, then
+ // they will get another error message for the next promise (but this
+ // situation is gonna be very rare, if ever happening).
+ assert!(!messages.is_empty());
+ let msg = v8::Local::new(scope, messages[0].clone());
+ let js_error = JsError::from_v8_message(scope, msg);
+ return Poll::Ready(Err(js_error.into()));
+ }
+ }
+
+ if pending_state.has_pending_dyn_module_evaluation {
+ if pending_state.has_pending_refed_ops
+ || pending_state.has_pending_dyn_imports
+ || pending_state.has_pending_background_tasks
+ || pending_state.has_tick_scheduled
+ {
+ // pass, will be polled again
+ } else if self.inner.state.borrow().dyn_module_evaluate_idle_counter >= 1
+ {
+ let scope = &mut self.handle_scope();
+ let messages = find_stalled_top_level_await(scope);
+ // We are gonna print only a single message to provide a nice formatting
+ // with source line of offending promise shown. Once user fixed it, then
+ // they will get another error message for the next promise (but this
+ // situation is gonna be very rare, if ever happening).
+ assert!(!messages.is_empty());
+ let msg = v8::Local::new(scope, messages[0].clone());
+ let js_error = JsError::from_v8_message(scope, msg);
+ return Poll::Ready(Err(js_error.into()));
+ } else {
+ let mut state = self.inner.state.borrow_mut();
+ // Delay the above error by one spin of the event loop. A dynamic import
+ // evaluation may complete during this, in which case the counter will
+ // reset.
+ state.dyn_module_evaluate_idle_counter += 1;
+ state.op_state.borrow().waker.wake();
+ }
+ }
+
+ Poll::Pending
+ }
+
+ fn event_loop_pending_state(&mut self) -> EventLoopPendingState {
+ let mut scope = v8::HandleScope::new(self.inner.v8_isolate.as_mut());
+ EventLoopPendingState::new(
+ &mut scope,
+ &mut self.inner.state.borrow_mut(),
+ &self.module_map.borrow(),
+ )
+ }
+}
+
+impl JsRuntimeForSnapshot {
+ pub fn new(
+ mut options: RuntimeOptions,
+ runtime_snapshot_options: RuntimeSnapshotOptions,
+ ) -> JsRuntimeForSnapshot {
+ JsRuntime::init_v8(options.v8_platform.take(), true);
+ JsRuntimeForSnapshot(JsRuntime::new_inner(
+ options,
+ true,
+ runtime_snapshot_options.snapshot_module_load_cb,
+ ))
+ }
+
+ /// Takes a snapshot and consumes the runtime.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn snapshot(mut self) -> v8::StartupData {
+ // Ensure there are no live inspectors to prevent crashes.
+ self.inner.prepare_for_cleanup();
+
+ // Set the context to be snapshot's default context
+ {
+ let context = self.global_context();
+ let mut scope = self.handle_scope();
+ let local_context = v8::Local::new(&mut scope, context);
+ scope.set_default_context(local_context);
+ }
+
+ // Serialize the module map and store its data in the snapshot.
+ {
+ let snapshotted_data = {
+ // `self.module_map` points directly to the v8 isolate data slot, which
+ // we must explicitly drop before destroying the isolate. We have to
+ // take and drop this `Rc` before that.
+ let module_map_rc = std::mem::take(&mut self.module_map);
+ let module_map = module_map_rc.borrow();
+ module_map.serialize_for_snapshotting(&mut self.handle_scope())
+ };
+
+ let context = self.global_context();
+ let mut scope = self.handle_scope();
+ snapshot_util::set_snapshotted_data(
+ &mut scope,
+ context,
+ snapshotted_data,
+ );
+ }
+
+ self
+ .0
+ .inner
+ .prepare_for_snapshot()
+ .create_blob(v8::FunctionCodeHandling::Keep)
+ .unwrap()
+ }
+}
+
+fn get_stalled_top_level_await_message_for_module(
+ scope: &mut v8::HandleScope,
+ module_id: ModuleId,
+) -> Vec<v8::Global<v8::Message>> {
+ let module_map = JsRuntime::module_map_from(scope);
+ let module_map = module_map.borrow();
+ let module_handle = module_map.handles.get(module_id).unwrap();
+
+ let module = v8::Local::new(scope, module_handle);
+ let stalled = module.get_stalled_top_level_await_message(scope);
+ let mut messages = vec![];
+ for (_, message) in stalled {
+ messages.push(v8::Global::new(scope, message));
+ }
+ messages
+}
+
+fn find_stalled_top_level_await(
+ scope: &mut v8::HandleScope,
+) -> Vec<v8::Global<v8::Message>> {
+ let module_map = JsRuntime::module_map_from(scope);
+ let module_map = module_map.borrow();
+
+ // First check if that's root module
+ let root_module_id = module_map
+ .info
+ .iter()
+ .filter(|m| m.main)
+ .map(|m| m.id)
+ .next();
+
+ if let Some(root_module_id) = root_module_id {
+ let messages =
+ get_stalled_top_level_await_message_for_module(scope, root_module_id);
+ if !messages.is_empty() {
+ return messages;
+ }
+ }
+
+ // It wasn't a top module, so iterate over all modules and try to find
+ // any with stalled top level await
+ for module_id in 0..module_map.handles.len() {
+ let messages =
+ get_stalled_top_level_await_message_for_module(scope, module_id);
+ if !messages.is_empty() {
+ return messages;
+ }
+ }
+
+ unreachable!()
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub(crate) struct EventLoopPendingState {
+ has_pending_refed_ops: bool,
+ has_pending_dyn_imports: bool,
+ has_pending_dyn_module_evaluation: bool,
+ has_pending_module_evaluation: bool,
+ has_pending_background_tasks: bool,
+ has_tick_scheduled: bool,
+}
+impl EventLoopPendingState {
+ pub fn new(
+ scope: &mut v8::HandleScope<()>,
+ state: &mut JsRuntimeState,
+ module_map: &ModuleMap,
+ ) -> EventLoopPendingState {
+ let mut num_unrefed_ops = 0;
+ let mut num_pending_ops = 0;
+ for realm in &state.known_realms {
+ num_unrefed_ops += realm.num_unrefed_ops();
+ num_pending_ops += realm.num_pending_ops();
+ }
+
+ EventLoopPendingState {
+ has_pending_refed_ops: num_pending_ops > num_unrefed_ops,
+ has_pending_dyn_imports: module_map.has_pending_dynamic_imports(),
+ has_pending_dyn_module_evaluation: !state
+ .pending_dyn_mod_evaluate
+ .is_empty(),
+ has_pending_module_evaluation: state.pending_mod_evaluate.is_some(),
+ has_pending_background_tasks: scope.has_pending_background_tasks(),
+ has_tick_scheduled: state.has_tick_scheduled,
+ }
+ }
+
+ pub fn is_pending(&self) -> bool {
+ self.has_pending_refed_ops
+ || self.has_pending_dyn_imports
+ || self.has_pending_dyn_module_evaluation
+ || self.has_pending_module_evaluation
+ || self.has_pending_background_tasks
+ || self.has_tick_scheduled
+ }
+}
+
+extern "C" fn near_heap_limit_callback<F>(
+ data: *mut c_void,
+ current_heap_limit: usize,
+ initial_heap_limit: usize,
+) -> usize
+where
+ F: FnMut(usize, usize) -> usize,
+{
+ // SAFETY: The data is a pointer to the Rust callback function. It is stored
+ // in `JsRuntime::allocations` and thus is guaranteed to outlive the isolate.
+ let callback = unsafe { &mut *(data as *mut F) };
+ callback(current_heap_limit, initial_heap_limit)
+}
+
+impl JsRuntimeState {
+ pub(crate) fn inspector(&self) -> Rc<RefCell<JsRuntimeInspector>> {
+ self.inspector.as_ref().unwrap().clone()
+ }
+
+ /// Called by `bindings::host_import_module_dynamically_callback`
+ /// after initiating new dynamic import load.
+ pub fn notify_new_dynamic_import(&mut self) {
+ // Notify event loop to poll again soon.
+ self.op_state.borrow().waker.wake();
+ }
+}
+
+// Related to module loading
+impl JsRuntime {
+ pub(crate) fn instantiate_module(
+ &mut self,
+ id: ModuleId,
+ ) -> Result<(), v8::Global<v8::Value>> {
+ let module_map_rc = self.module_map.clone();
+ let scope = &mut self.handle_scope();
+ let tc_scope = &mut v8::TryCatch::new(scope);
+
+ let module = module_map_rc
+ .borrow()
+ .get_handle(id)
+ .map(|handle| v8::Local::new(tc_scope, handle))
+ .expect("ModuleInfo not found");
+
+ if module.get_status() == v8::ModuleStatus::Errored {
+ return Err(v8::Global::new(tc_scope, module.get_exception()));
+ }
+
+ // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
+ // `module_resolve_callback` will be calling into `ModuleMap` from within
+ // the isolate.
+ let instantiate_result =
+ module.instantiate_module(tc_scope, bindings::module_resolve_callback);
+
+ if instantiate_result.is_none() {
+ let exception = tc_scope.exception().unwrap();
+ return Err(v8::Global::new(tc_scope, exception));
+ }
+
+ Ok(())
+ }
+
+ fn dynamic_import_module_evaluate(
+ &mut self,
+ load_id: ModuleLoadId,
+ id: ModuleId,
+ ) -> Result<(), Error> {
+ let module_handle = self
+ .module_map
+ .borrow()
+ .get_handle(id)
+ .expect("ModuleInfo not found");
+
+ let status = {
+ let scope = &mut self.handle_scope();
+ let module = module_handle.open(scope);
+ module.get_status()
+ };
+
+ match status {
+ v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {}
+ _ => return Ok(()),
+ }
+
+ // IMPORTANT: Top-level-await is enabled, which means that return value
+ // of module evaluation is a promise.
+ //
+ // This promise is internal, and not the same one that gets returned to
+ // the user. We add an empty `.catch()` handler so that it does not result
+ // in an exception if it rejects. That will instead happen for the other
+ // promise if not handled by the user.
+ //
+ // For more details see:
+ // https://github.com/denoland/deno/issues/4908
+ // https://v8.dev/features/top-level-await#module-execution-order
+ let global_realm =
+ self.inner.state.borrow_mut().global_realm.clone().unwrap();
+ let scope = &mut global_realm.handle_scope(&mut self.inner.v8_isolate);
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let module = v8::Local::new(tc_scope, &module_handle);
+ let maybe_value = module.evaluate(tc_scope);
+
+ // Update status after evaluating.
+ let status = module.get_status();
+
+ if let Some(value) = maybe_value {
+ assert!(
+ status == v8::ModuleStatus::Evaluated
+ || status == v8::ModuleStatus::Errored
+ );
+ let promise = v8::Local::<v8::Promise>::try_from(value)
+ .expect("Expected to get promise as module evaluation result");
+ let empty_fn = bindings::create_empty_fn(tc_scope).unwrap();
+ promise.catch(tc_scope, empty_fn);
+ let promise_global = v8::Global::new(tc_scope, promise);
+ let module_global = v8::Global::new(tc_scope, module);
+
+ let dyn_import_mod_evaluate = DynImportModEvaluate {
+ load_id,
+ module_id: id,
+ promise: promise_global,
+ module: module_global,
+ };
+
+ self
+ .inner
+ .state
+ .borrow_mut()
+ .pending_dyn_mod_evaluate
+ .push(dyn_import_mod_evaluate);
+ } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
+ return Err(
+ generic_error("Cannot evaluate dynamically imported module, because JavaScript execution has been terminated.")
+ );
+ } else {
+ assert!(status == v8::ModuleStatus::Errored);
+ }
+
+ Ok(())
+ }
+
+ // TODO(bartlomieju): make it return `ModuleEvaluationFuture`?
+ /// Evaluates an already instantiated ES module.
+ ///
+ /// Returns a receiver handle that resolves when module promise resolves.
+ /// Implementors must manually call [`JsRuntime::run_event_loop`] to drive
+ /// module evaluation future.
+ ///
+ /// `Error` can usually be downcast to `JsError` and should be awaited and
+ /// checked after [`JsRuntime::run_event_loop`] completion.
+ ///
+ /// This function panics if module has not been instantiated.
+ pub fn mod_evaluate(
+ &mut self,
+ id: ModuleId,
+ ) -> oneshot::Receiver<Result<(), Error>> {
+ let global_realm = self.global_realm();
+ let state_rc = self.inner.state.clone();
+ let module_map_rc = self.module_map.clone();
+ let scope = &mut self.handle_scope();
+ let tc_scope = &mut v8::TryCatch::new(scope);
+
+ let module = module_map_rc
+ .borrow()
+ .get_handle(id)
+ .map(|handle| v8::Local::new(tc_scope, handle))
+ .expect("ModuleInfo not found");
+ let mut status = module.get_status();
+ assert_eq!(
+ status,
+ v8::ModuleStatus::Instantiated,
+ "Module not instantiated {id}"
+ );
+
+ let (sender, receiver) = oneshot::channel();
+
+ // IMPORTANT: Top-level-await is enabled, which means that return value
+ // of module evaluation is a promise.
+ //
+ // Because that promise is created internally by V8, when error occurs during
+ // module evaluation the promise is rejected, and since the promise has no rejection
+ // handler it will result in call to `bindings::promise_reject_callback` adding
+ // the promise to pending promise rejection table - meaning JsRuntime will return
+ // error on next poll().
+ //
+ // This situation is not desirable as we want to manually return error at the
+ // end of this function to handle it further. It means we need to manually
+ // remove this promise from pending promise rejection table.
+ //
+ // For more details see:
+ // https://github.com/denoland/deno/issues/4908
+ // https://v8.dev/features/top-level-await#module-execution-order
+ {
+ let mut state = state_rc.borrow_mut();
+ assert!(
+ state.pending_mod_evaluate.is_none(),
+ "There is already pending top level module evaluation"
+ );
+ state.pending_mod_evaluate = Some(ModEvaluate {
+ promise: None,
+ has_evaluated: false,
+ handled_promise_rejections: vec![],
+ sender,
+ });
+ }
+
+ let maybe_value = module.evaluate(tc_scope);
+ {
+ let mut state = state_rc.borrow_mut();
+ let pending_mod_evaluate = state.pending_mod_evaluate.as_mut().unwrap();
+ pending_mod_evaluate.has_evaluated = true;
+ }
+
+ // Update status after evaluating.
+ status = module.get_status();
+
+ let has_dispatched_exception =
+ state_rc.borrow_mut().dispatched_exception.is_some();
+ if has_dispatched_exception {
+ // This will be overrided in `exception_to_err_result()`.
+ let exception = v8::undefined(tc_scope).into();
+ let pending_mod_evaluate = {
+ let mut state = state_rc.borrow_mut();
+ state.pending_mod_evaluate.take().unwrap()
+ };
+ pending_mod_evaluate
+ .sender
+ .send(exception_to_err_result(tc_scope, exception, false))
+ .expect("Failed to send module evaluation error.");
+ } else if let Some(value) = maybe_value {
+ assert!(
+ status == v8::ModuleStatus::Evaluated
+ || status == v8::ModuleStatus::Errored
+ );
+ let promise = v8::Local::<v8::Promise>::try_from(value)
+ .expect("Expected to get promise as module evaluation result");
+ let promise_global = v8::Global::new(tc_scope, promise);
+ let mut state = state_rc.borrow_mut();
+ {
+ let pending_mod_evaluate = state.pending_mod_evaluate.as_ref().unwrap();
+ let pending_rejection_was_already_handled = pending_mod_evaluate
+ .handled_promise_rejections
+ .contains(&promise_global);
+ if !pending_rejection_was_already_handled {
+ global_realm
+ .0
+ .state()
+ .borrow_mut()
+ .pending_promise_rejections
+ .retain(|(key, _)| key != &promise_global);
+ }
+ }
+ let promise_global = v8::Global::new(tc_scope, promise);
+ state.pending_mod_evaluate.as_mut().unwrap().promise =
+ Some(promise_global);
+ tc_scope.perform_microtask_checkpoint();
+ } else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
+ let pending_mod_evaluate = {
+ let mut state = state_rc.borrow_mut();
+ state.pending_mod_evaluate.take().unwrap()
+ };
+ pending_mod_evaluate.sender.send(Err(
+ generic_error("Cannot evaluate module, because JavaScript execution has been terminated.")
+ )).expect("Failed to send module evaluation error.");
+ } else {
+ assert!(status == v8::ModuleStatus::Errored);
+ }
+
+ receiver
+ }
+
+ /// Clear the module map, meant to be used after initializing extensions.
+ /// Optionally pass a list of exceptions `(old_name, new_name)` representing
+ /// specifiers which will be renamed and preserved in the module map.
+ pub fn clear_module_map(
+ &self,
+ exceptions: impl Iterator<Item = (&'static str, &'static str)>,
+ ) {
+ let mut module_map = self.module_map.borrow_mut();
+ let handles = exceptions
+ .map(|(old_name, new_name)| {
+ (module_map.get_handle_by_name(old_name).unwrap(), new_name)
+ })
+ .collect::<Vec<_>>();
+ module_map.clear();
+ for (handle, new_name) in handles {
+ module_map.inject_handle(
+ ModuleName::from_static(new_name),
+ ModuleType::JavaScript,
+ handle,
+ )
+ }
+ }
+
+ fn dynamic_import_reject(
+ &mut self,
+ id: ModuleLoadId,
+ exception: v8::Global<v8::Value>,
+ ) {
+ let module_map_rc = self.module_map.clone();
+ let scope = &mut self.handle_scope();
+
+ let resolver_handle = module_map_rc
+ .borrow_mut()
+ .dynamic_import_map
+ .remove(&id)
+ .expect("Invalid dynamic import id");
+ let resolver = resolver_handle.open(scope);
+
+ // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
+ // rejecting the promise might initiate another `import()` which will
+ // in turn call `bindings::host_import_module_dynamically_callback` which
+ // will reach into `ModuleMap` from within the isolate.
+ let exception = v8::Local::new(scope, exception);
+ resolver.reject(scope, exception).unwrap();
+ scope.perform_microtask_checkpoint();
+ }
+
+ fn dynamic_import_resolve(&mut self, id: ModuleLoadId, mod_id: ModuleId) {
+ let state_rc = self.inner.state.clone();
+ let module_map_rc = self.module_map.clone();
+ let scope = &mut self.handle_scope();
+
+ let resolver_handle = module_map_rc
+ .borrow_mut()
+ .dynamic_import_map
+ .remove(&id)
+ .expect("Invalid dynamic import id");
+ let resolver = resolver_handle.open(scope);
+
+ let module = {
+ module_map_rc
+ .borrow()
+ .get_handle(mod_id)
+ .map(|handle| v8::Local::new(scope, handle))
+ .expect("Dyn import module info not found")
+ };
+ // Resolution success
+ assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
+
+ // IMPORTANT: No borrows to `ModuleMap` can be held at this point because
+ // resolving the promise might initiate another `import()` which will
+ // in turn call `bindings::host_import_module_dynamically_callback` which
+ // will reach into `ModuleMap` from within the isolate.
+ let module_namespace = module.get_module_namespace();
+ resolver.resolve(scope, module_namespace).unwrap();
+ state_rc.borrow_mut().dyn_module_evaluate_idle_counter = 0;
+ scope.perform_microtask_checkpoint();
+ }
+
+ fn prepare_dyn_imports(
+ &mut self,
+ cx: &mut Context,
+ ) -> Poll<Result<(), Error>> {
+ if self
+ .module_map
+ .borrow()
+ .preparing_dynamic_imports
+ .is_empty()
+ {
+ return Poll::Ready(Ok(()));
+ }
+
+ loop {
+ let poll_result = self
+ .module_map
+ .borrow_mut()
+ .preparing_dynamic_imports
+ .poll_next_unpin(cx);
+
+ if let Poll::Ready(Some(prepare_poll)) = poll_result {
+ let dyn_import_id = prepare_poll.0;
+ let prepare_result = prepare_poll.1;
+
+ match prepare_result {
+ Ok(load) => {
+ self
+ .module_map
+ .borrow_mut()
+ .pending_dynamic_imports
+ .push(load.into_future());
+ }
+ Err(err) => {
+ let exception = to_v8_type_error(&mut self.handle_scope(), err);
+ self.dynamic_import_reject(dyn_import_id, exception);
+ }
+ }
+ // Continue polling for more prepared dynamic imports.
+ continue;
+ }
+
+ // There are no active dynamic import loads, or none are ready.
+ return Poll::Ready(Ok(()));
+ }
+ }
+
+ fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll<Result<(), Error>> {
+ if self.module_map.borrow().pending_dynamic_imports.is_empty() {
+ return Poll::Ready(Ok(()));
+ }
+
+ loop {
+ let poll_result = self
+ .module_map
+ .borrow_mut()
+ .pending_dynamic_imports
+ .poll_next_unpin(cx);
+
+ if let Poll::Ready(Some(load_stream_poll)) = poll_result {
+ let maybe_result = load_stream_poll.0;
+ let mut load = load_stream_poll.1;
+ let dyn_import_id = load.id;
+
+ if let Some(load_stream_result) = maybe_result {
+ match load_stream_result {
+ Ok((request, info)) => {
+ // A module (not necessarily the one dynamically imported) has been
+ // fetched. Create and register it, and if successful, poll for the
+ // next recursive-load event related to this dynamic import.
+ let register_result = load.register_and_recurse(
+ &mut self.handle_scope(),
+ &request,
+ info,
+ );
+
+ match register_result {
+ Ok(()) => {
+ // Keep importing until it's fully drained
+ self
+ .module_map
+ .borrow_mut()
+ .pending_dynamic_imports
+ .push(load.into_future());
+ }
+ Err(err) => {
+ let exception = match err {
+ ModuleError::Exception(e) => e,
+ ModuleError::Other(e) => {
+ to_v8_type_error(&mut self.handle_scope(), e)
+ }
+ };
+ self.dynamic_import_reject(dyn_import_id, exception)
+ }
+ }
+ }
+ Err(err) => {
+ // A non-javascript error occurred; this could be due to a an invalid
+ // module specifier, or a problem with the source map, or a failure
+ // to fetch the module source code.
+ let exception = to_v8_type_error(&mut self.handle_scope(), err);
+ self.dynamic_import_reject(dyn_import_id, exception);
+ }
+ }
+ } else {
+ // The top-level module from a dynamic import has been instantiated.
+ // Load is done.
+ let module_id =
+ load.root_module_id.expect("Root module should be loaded");
+ let result = self.instantiate_module(module_id);
+ if let Err(exception) = result {
+ self.dynamic_import_reject(dyn_import_id, exception);
+ }
+ self.dynamic_import_module_evaluate(dyn_import_id, module_id)?;
+ }
+
+ // Continue polling for more ready dynamic imports.
+ continue;
+ }
+
+ // There are no active dynamic import loads, or none are ready.
+ return Poll::Ready(Ok(()));
+ }
+ }
+
+ /// "deno_core" runs V8 with Top Level Await enabled. It means that each
+ /// module evaluation returns a promise from V8.
+ /// Feature docs: https://v8.dev/features/top-level-await
+ ///
+ /// This promise resolves after all dependent modules have also
+ /// resolved. Each dependent module may perform calls to "import()" and APIs
+ /// using async ops will add futures to the runtime's event loop.
+ /// It means that the promise returned from module evaluation will
+ /// resolve only after all futures in the event loop are done.
+ ///
+ /// Thus during turn of event loop we need to check if V8 has
+ /// resolved or rejected the promise. If the promise is still pending
+ /// then another turn of event loop must be performed.
+ fn evaluate_pending_module(&mut self) {
+ let maybe_module_evaluation =
+ self.inner.state.borrow_mut().pending_mod_evaluate.take();
+
+ if maybe_module_evaluation.is_none() {
+ return;
+ }
+
+ let mut module_evaluation = maybe_module_evaluation.unwrap();
+ let state_rc = self.inner.state.clone();
+ let scope = &mut self.handle_scope();
+
+ let promise_global = module_evaluation.promise.clone().unwrap();
+ let promise = promise_global.open(scope);
+ let promise_state = promise.state();
+
+ match promise_state {
+ v8::PromiseState::Pending => {
+ // NOTE: `poll_event_loop` will decide if
+ // runtime would be woken soon
+ state_rc.borrow_mut().pending_mod_evaluate = Some(module_evaluation);
+ }
+ v8::PromiseState::Fulfilled => {
+ scope.perform_microtask_checkpoint();
+ // Receiver end might have been already dropped, ignore the result
+ let _ = module_evaluation.sender.send(Ok(()));
+ module_evaluation.handled_promise_rejections.clear();
+ }
+ v8::PromiseState::Rejected => {
+ let exception = promise.result(scope);
+ scope.perform_microtask_checkpoint();
+
+ // Receiver end might have been already dropped, ignore the result
+ if module_evaluation
+ .handled_promise_rejections
+ .contains(&promise_global)
+ {
+ let _ = module_evaluation.sender.send(Ok(()));
+ module_evaluation.handled_promise_rejections.clear();
+ } else {
+ let _ = module_evaluation
+ .sender
+ .send(exception_to_err_result(scope, exception, false));
+ }
+ }
+ }
+ }
+
+ // Returns true if some dynamic import was resolved.
+ fn evaluate_dyn_imports(&mut self) -> bool {
+ let pending = std::mem::take(
+ &mut self.inner.state.borrow_mut().pending_dyn_mod_evaluate,
+ );
+ if pending.is_empty() {
+ return false;
+ }
+ let mut resolved_any = false;
+ let mut still_pending = vec![];
+ for pending_dyn_evaluate in pending {
+ let maybe_result = {
+ let scope = &mut self.handle_scope();
+
+ let module_id = pending_dyn_evaluate.module_id;
+ let promise = pending_dyn_evaluate.promise.open(scope);
+ let _module = pending_dyn_evaluate.module.open(scope);
+ let promise_state = promise.state();
+
+ match promise_state {
+ v8::PromiseState::Pending => {
+ still_pending.push(pending_dyn_evaluate);
+ None
+ }
+ v8::PromiseState::Fulfilled => {
+ Some(Ok((pending_dyn_evaluate.load_id, module_id)))
+ }
+ v8::PromiseState::Rejected => {
+ let exception = promise.result(scope);
+ let exception = v8::Global::new(scope, exception);
+ Some(Err((pending_dyn_evaluate.load_id, exception)))
+ }
+ }
+ };
+
+ if let Some(result) = maybe_result {
+ resolved_any = true;
+ match result {
+ Ok((dyn_import_id, module_id)) => {
+ self.dynamic_import_resolve(dyn_import_id, module_id);
+ }
+ Err((dyn_import_id, exception)) => {
+ self.dynamic_import_reject(dyn_import_id, exception);
+ }
+ }
+ }
+ }
+ self.inner.state.borrow_mut().pending_dyn_mod_evaluate = still_pending;
+ resolved_any
+ }
+
+ /// Asynchronously load specified module and all of its dependencies.
+ ///
+ /// The module will be marked as "main", and because of that
+ /// "import.meta.main" will return true when checked inside that module.
+ ///
+ /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId`
+ /// manually after load is finished.
+ pub async fn load_main_module(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ code: Option<ModuleCode>,
+ ) -> Result<ModuleId, Error> {
+ let module_map_rc = self.module_map.clone();
+ if let Some(code) = code {
+ let specifier = specifier.as_str().to_owned().into();
+ let scope = &mut self.handle_scope();
+ // true for main module
+ module_map_rc
+ .borrow_mut()
+ .new_es_module(scope, true, specifier, code, false)
+ .map_err(|e| match e {
+ ModuleError::Exception(exception) => {
+ let exception = v8::Local::new(scope, exception);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ }
+ ModuleError::Other(error) => error,
+ })?;
+ }
+
+ let mut load =
+ ModuleMap::load_main(module_map_rc.clone(), &specifier).await?;
+
+ while let Some(load_result) = load.next().await {
+ let (request, info) = load_result?;
+ let scope = &mut self.handle_scope();
+ load.register_and_recurse(scope, &request, info).map_err(
+ |e| match e {
+ ModuleError::Exception(exception) => {
+ let exception = v8::Local::new(scope, exception);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ }
+ ModuleError::Other(error) => error,
+ },
+ )?;
+ }
+
+ let root_id = load.root_module_id.expect("Root module should be loaded");
+ self.instantiate_module(root_id).map_err(|e| {
+ let scope = &mut self.handle_scope();
+ let exception = v8::Local::new(scope, e);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ })?;
+ Ok(root_id)
+ }
+
+ /// Asynchronously load specified ES module and all of its dependencies.
+ ///
+ /// This method is meant to be used when loading some utility code that
+ /// might be later imported by the main module (ie. an entry point module).
+ ///
+ /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId`
+ /// manually after load is finished.
+ pub async fn load_side_module(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ code: Option<ModuleCode>,
+ ) -> Result<ModuleId, Error> {
+ let module_map_rc = self.module_map.clone();
+ if let Some(code) = code {
+ let specifier = specifier.as_str().to_owned().into();
+ let scope = &mut self.handle_scope();
+ // false for side module (not main module)
+ module_map_rc
+ .borrow_mut()
+ .new_es_module(scope, false, specifier, code, false)
+ .map_err(|e| match e {
+ ModuleError::Exception(exception) => {
+ let exception = v8::Local::new(scope, exception);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ }
+ ModuleError::Other(error) => error,
+ })?;
+ }
+
+ let mut load =
+ ModuleMap::load_side(module_map_rc.clone(), &specifier).await?;
+
+ while let Some(load_result) = load.next().await {
+ let (request, info) = load_result?;
+ let scope = &mut self.handle_scope();
+ load.register_and_recurse(scope, &request, info).map_err(
+ |e| match e {
+ ModuleError::Exception(exception) => {
+ let exception = v8::Local::new(scope, exception);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ }
+ ModuleError::Other(error) => error,
+ },
+ )?;
+ }
+
+ let root_id = load.root_module_id.expect("Root module should be loaded");
+ self.instantiate_module(root_id).map_err(|e| {
+ let scope = &mut self.handle_scope();
+ let exception = v8::Local::new(scope, e);
+ exception_to_err_result::<()>(scope, exception, false).unwrap_err()
+ })?;
+ Ok(root_id)
+ }
+
+ fn check_promise_rejections(&mut self) -> Result<(), Error> {
+ let state = self.inner.state.clone();
+ let scope = &mut self.handle_scope();
+ let state = state.borrow();
+ for realm in &state.known_realms {
+ realm.check_promise_rejections(scope)?;
+ }
+ Ok(())
+ }
+
+ // Polls pending ops and then runs `Deno.core.eventLoopTick` callback.
+ fn do_js_event_loop_tick(&mut self, cx: &mut Context) -> Result<(), Error> {
+ // Handle responses for each realm.
+ let state = self.inner.state.clone();
+ let isolate = &mut self.inner.v8_isolate;
+ let realm_count = state.borrow().known_realms.len();
+ for realm_idx in 0..realm_count {
+ let realm = state.borrow().known_realms.get(realm_idx).unwrap().clone();
+ let context_state = realm.state();
+ let mut context_state = context_state.borrow_mut();
+ let scope = &mut realm.handle_scope(isolate);
+
+ // We return async responses to JS in unbounded batches (may change),
+ // each batch is a flat vector of tuples:
+ // `[promise_id1, op_result1, promise_id2, op_result2, ...]`
+ // promise_id is a simple integer, op_result is an ops::OpResult
+ // which contains a value OR an error, encoded as a tuple.
+ // This batch is received in JS via the special `arguments` variable
+ // and then each tuple is used to resolve or reject promises
+ //
+ // This can handle 15 promises futures in a single batch without heap
+ // allocations.
+ let mut args: SmallVec<[v8::Local<v8::Value>; 32]> =
+ SmallVec::with_capacity(32);
+
+ loop {
+ let item = {
+ let next = std::pin::pin!(context_state.pending_ops.join_next());
+ let Poll::Ready(Some(item)) = next.poll(cx) else {
+ break;
+ };
+ item
+ };
+ let (promise_id, op_id, mut resp) = item.unwrap().into_inner();
+ state
+ .borrow()
+ .op_state
+ .borrow()
+ .tracker
+ .track_async_completed(op_id);
+ context_state.unrefed_ops.remove(&promise_id);
+ args.push(v8::Integer::new(scope, promise_id).into());
+ args.push(match resp.to_v8(scope) {
+ Ok(v) => v,
+ Err(e) => OpResult::Err(OpError::new(&|_| "TypeError", e.into()))
+ .to_v8(scope)
+ .unwrap(),
+ });
+ }
+
+ let has_tick_scheduled =
+ v8::Boolean::new(scope, self.inner.state.borrow().has_tick_scheduled);
+ args.push(has_tick_scheduled.into());
+
+ let js_event_loop_tick_cb_handle =
+ context_state.js_event_loop_tick_cb.clone().unwrap();
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let js_event_loop_tick_cb = js_event_loop_tick_cb_handle.open(tc_scope);
+ let this = v8::undefined(tc_scope).into();
+ drop(context_state);
+ js_event_loop_tick_cb.call(tc_scope, this, args.as_slice());
+
+ if let Some(exception) = tc_scope.exception() {
+ // TODO(@andreubotella): Returning here can cause async ops in other
+ // realms to never resolve.
+ return exception_to_err_result(tc_scope, exception, false);
+ }
+
+ if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
+ return Ok(());
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs
new file mode 100644
index 000000000..2bd3ea9fe
--- /dev/null
+++ b/core/runtime/mod.rs
@@ -0,0 +1,35 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+mod bindings;
+mod jsrealm;
+mod jsruntime;
+#[doc(hidden)]
+pub mod ops;
+mod snapshot_util;
+
+#[cfg(test)]
+mod tests;
+
+pub const V8_WRAPPER_TYPE_INDEX: i32 = 0;
+pub const V8_WRAPPER_OBJECT_INDEX: i32 = 1;
+
+pub(crate) use jsrealm::ContextState;
+pub use jsrealm::JsRealm;
+pub use jsruntime::CompiledWasmModuleStore;
+pub use jsruntime::CrossIsolateStore;
+pub(crate) use jsruntime::InitMode;
+pub use jsruntime::JsRuntime;
+pub use jsruntime::JsRuntimeForSnapshot;
+pub use jsruntime::JsRuntimeState;
+pub use jsruntime::RuntimeOptions;
+pub use jsruntime::RuntimeSnapshotOptions;
+pub use jsruntime::SharedArrayBufferStore;
+pub use jsruntime::Snapshot;
+pub use snapshot_util::create_snapshot;
+pub use snapshot_util::get_js_files;
+pub use snapshot_util::CreateSnapshotOptions;
+pub use snapshot_util::CreateSnapshotOutput;
+pub use snapshot_util::FilterFn;
+pub(crate) use snapshot_util::SnapshottedData;
+
+pub use bindings::script_origin;
+pub use bindings::throw_type_error;
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
new file mode 100644
index 000000000..c9e7fa6c7
--- /dev/null
+++ b/core/runtime/ops.rs
@@ -0,0 +1,156 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use crate::ops::*;
+use crate::OpResult;
+use crate::PromiseId;
+use anyhow::Error;
+use futures::future::Future;
+use futures::future::FutureExt;
+use futures::future::MaybeDone;
+use futures::task::noop_waker;
+use std::cell::RefCell;
+use std::option::Option;
+use std::pin::Pin;
+use std::task::Context;
+use std::task::Poll;
+
+#[inline]
+pub fn queue_fast_async_op<R: serde::Serialize + 'static>(
+ ctx: &OpCtx,
+ promise_id: PromiseId,
+ op: impl Future<Output = Result<R, Error>> + 'static,
+) {
+ let get_class = {
+ let state = RefCell::borrow(&ctx.state);
+ state.tracker.track_async(ctx.id);
+ state.get_error_class_fn
+ };
+ let fut = op
+ .map(|result| crate::_ops::to_op_result(get_class, result))
+ .boxed_local();
+ // SAFETY: this this is guaranteed to be running on a current-thread executor
+ ctx.context_state.borrow_mut().pending_ops.spawn(unsafe {
+ crate::task::MaskFutureAsSend::new(OpCall::pending(ctx, promise_id, fut))
+ });
+}
+
+#[inline]
+pub fn map_async_op1<R: serde::Serialize + 'static>(
+ ctx: &OpCtx,
+ op: impl Future<Output = Result<R, Error>> + 'static,
+) -> MaybeDone<Pin<Box<dyn Future<Output = OpResult>>>> {
+ let get_class = {
+ let state = RefCell::borrow(&ctx.state);
+ state.tracker.track_async(ctx.id);
+ state.get_error_class_fn
+ };
+
+ let fut = op
+ .map(|result| crate::_ops::to_op_result(get_class, result))
+ .boxed_local();
+ MaybeDone::Future(fut)
+}
+
+#[inline]
+pub fn map_async_op2<R: serde::Serialize + 'static>(
+ ctx: &OpCtx,
+ op: impl Future<Output = R> + 'static,
+) -> MaybeDone<Pin<Box<dyn Future<Output = OpResult>>>> {
+ let state = RefCell::borrow(&ctx.state);
+ state.tracker.track_async(ctx.id);
+
+ let fut = op.map(|result| OpResult::Ok(result.into())).boxed_local();
+ MaybeDone::Future(fut)
+}
+
+#[inline]
+pub fn map_async_op3<R: serde::Serialize + 'static>(
+ ctx: &OpCtx,
+ op: Result<impl Future<Output = Result<R, Error>> + 'static, Error>,
+) -> MaybeDone<Pin<Box<dyn Future<Output = OpResult>>>> {
+ let get_class = {
+ let state = RefCell::borrow(&ctx.state);
+ state.tracker.track_async(ctx.id);
+ state.get_error_class_fn
+ };
+
+ match op {
+ Err(err) => MaybeDone::Done(OpResult::Err(OpError::new(get_class, err))),
+ Ok(fut) => MaybeDone::Future(
+ fut
+ .map(|result| crate::_ops::to_op_result(get_class, result))
+ .boxed_local(),
+ ),
+ }
+}
+
+#[inline]
+pub fn map_async_op4<R: serde::Serialize + 'static>(
+ ctx: &OpCtx,
+ op: Result<impl Future<Output = R> + 'static, Error>,
+) -> MaybeDone<Pin<Box<dyn Future<Output = OpResult>>>> {
+ let get_class = {
+ let state = RefCell::borrow(&ctx.state);
+ state.tracker.track_async(ctx.id);
+ state.get_error_class_fn
+ };
+
+ match op {
+ Err(err) => MaybeDone::Done(OpResult::Err(OpError::new(get_class, err))),
+ Ok(fut) => MaybeDone::Future(
+ fut.map(|result| OpResult::Ok(result.into())).boxed_local(),
+ ),
+ }
+}
+
+pub fn queue_async_op<'s>(
+ ctx: &OpCtx,
+ scope: &'s mut v8::HandleScope,
+ deferred: bool,
+ promise_id: PromiseId,
+ mut op: MaybeDone<Pin<Box<dyn Future<Output = OpResult>>>>,
+) -> Option<v8::Local<'s, v8::Value>> {
+ // An op's realm (as given by `OpCtx::realm_idx`) must match the realm in
+ // which it is invoked. Otherwise, we might have cross-realm object exposure.
+ // deno_core doesn't currently support such exposure, even though embedders
+ // can cause them, so we panic in debug mode (since the check is expensive).
+ // TODO(mmastrac): Restore this
+ // debug_assert_eq!(
+ // runtime_state.borrow().context(ctx.realm_idx as usize, scope),
+ // Some(scope.get_current_context())
+ // );
+
+ // All ops are polled immediately
+ let waker = noop_waker();
+ let mut cx = Context::from_waker(&waker);
+
+ // Note that MaybeDone returns () from the future
+ let op_call = match op.poll_unpin(&mut cx) {
+ Poll::Pending => {
+ let MaybeDone::Future(fut) = op else {
+ unreachable!()
+ };
+ OpCall::pending(ctx, promise_id, fut)
+ }
+ Poll::Ready(_) => {
+ let mut op_result = Pin::new(&mut op).take_output().unwrap();
+ // If the op is ready and is not marked as deferred we can immediately return
+ // the result.
+ if !deferred {
+ ctx.state.borrow_mut().tracker.track_async_completed(ctx.id);
+ return Some(op_result.to_v8(scope).unwrap());
+ }
+
+ OpCall::ready(ctx, promise_id, op_result)
+ }
+ };
+
+ // Otherwise we will push it to the `pending_ops` and let it be polled again
+ // or resolved on the next tick of the event loop.
+ ctx
+ .context_state
+ .borrow_mut()
+ .pending_ops
+ // SAFETY: this this is guaranteed to be running on a current-thread executor
+ .spawn(unsafe { crate::task::MaskFutureAsSend::new(op_call) });
+ None
+}
diff --git a/core/runtime/serialize_deserialize_test.js b/core/runtime/serialize_deserialize_test.js
new file mode 100644
index 000000000..70397cdf8
--- /dev/null
+++ b/core/runtime/serialize_deserialize_test.js
@@ -0,0 +1,74 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+"use strict";
+
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+
+function assertArrayEquals(a1, a2) {
+ if (a1.length !== a2.length) throw Error("assert");
+
+ for (const index in a1) {
+ if (a1[index] !== a2[index]) {
+ throw Error(`assert: (index ${index}) ${a1[index]} !== ${a2[index]}`);
+ }
+ }
+}
+
+function main() {
+ const emptyString = "";
+ const emptyStringSerialized = [255, 15, 34, 0];
+ assertArrayEquals(
+ Deno.core.ops.op_serialize(emptyString),
+ emptyStringSerialized,
+ );
+ assert(
+ Deno.core.ops.op_deserialize(
+ new Uint8Array(emptyStringSerialized),
+ ) ===
+ emptyString,
+ );
+
+ const primitiveValueArray = ["test", "a", null, undefined];
+ // deno-fmt-ignore
+ const primitiveValueArraySerialized = [
+ 255, 15, 65, 4, 34, 4, 116, 101, 115, 116,
+ 34, 1, 97, 48, 95, 36, 0, 4,
+ ];
+ assertArrayEquals(
+ Deno.core.ops.op_serialize(primitiveValueArray),
+ primitiveValueArraySerialized,
+ );
+
+ assertArrayEquals(
+ Deno.core.ops.op_deserialize(
+ new Uint8Array(primitiveValueArraySerialized),
+ ),
+ primitiveValueArray,
+ );
+
+ const circularObject = { test: null, test2: "dd", test3: "aa" };
+ circularObject.test = circularObject;
+ // deno-fmt-ignore
+ const circularObjectSerialized = [
+ 255, 15, 111, 34, 4, 116, 101, 115,
+ 116, 94, 0, 34, 5, 116, 101, 115,
+ 116, 50, 34, 2, 100, 100, 34, 5,
+ 116, 101, 115, 116, 51, 34, 2, 97,
+ 97, 123, 3,
+ ];
+
+ assertArrayEquals(
+ Deno.core.ops.op_serialize(circularObject),
+ circularObjectSerialized,
+ );
+
+ const deserializedCircularObject = Deno.core.ops.op_deserialize(
+ new Uint8Array(circularObjectSerialized),
+ );
+ assert(deserializedCircularObject.test == deserializedCircularObject);
+}
+
+main();
diff --git a/core/runtime/snapshot_util.rs b/core/runtime/snapshot_util.rs
new file mode 100644
index 000000000..88c273147
--- /dev/null
+++ b/core/runtime/snapshot_util.rs
@@ -0,0 +1,252 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::path::Path;
+use std::path::PathBuf;
+use std::time::Instant;
+
+use crate::runtime::RuntimeSnapshotOptions;
+use crate::ExtModuleLoaderCb;
+use crate::Extension;
+use crate::JsRuntimeForSnapshot;
+use crate::RuntimeOptions;
+use crate::Snapshot;
+
+pub type CompressionCb = dyn Fn(&mut Vec<u8>, &[u8]);
+
+pub struct CreateSnapshotOptions {
+ pub cargo_manifest_dir: &'static str,
+ pub snapshot_path: PathBuf,
+ pub startup_snapshot: Option<Snapshot>,
+ pub extensions: Vec<Extension>,
+ pub compression_cb: Option<Box<CompressionCb>>,
+ pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
+}
+
+pub struct CreateSnapshotOutput {
+ /// Any files marked as LoadedFromFsDuringSnapshot are collected here and should be
+ /// printed as 'cargo:rerun-if-changed' lines from your build script.
+ pub files_loaded_during_snapshot: Vec<PathBuf>,
+}
+
+#[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
+pub fn create_snapshot(
+ create_snapshot_options: CreateSnapshotOptions,
+) -> CreateSnapshotOutput {
+ let mut mark = Instant::now();
+
+ let js_runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ startup_snapshot: create_snapshot_options.startup_snapshot,
+ extensions: create_snapshot_options.extensions,
+ ..Default::default()
+ },
+ RuntimeSnapshotOptions {
+ snapshot_module_load_cb: create_snapshot_options.snapshot_module_load_cb,
+ },
+ );
+ println!(
+ "JsRuntime for snapshot prepared, took {:#?} ({})",
+ Instant::now().saturating_duration_since(mark),
+ create_snapshot_options.snapshot_path.display()
+ );
+ mark = Instant::now();
+
+ let mut files_loaded_during_snapshot = vec![];
+ for source in js_runtime
+ .extensions()
+ .iter()
+ .flat_map(|e| vec![e.get_esm_sources(), e.get_js_sources()])
+ .flatten()
+ .flatten()
+ {
+ use crate::ExtensionFileSourceCode;
+ if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
+ &source.code
+ {
+ files_loaded_during_snapshot.push(path.clone());
+ }
+ }
+
+ let snapshot = js_runtime.snapshot();
+ let snapshot_slice: &[u8] = &snapshot;
+ println!(
+ "Snapshot size: {}, took {:#?} ({})",
+ snapshot_slice.len(),
+ Instant::now().saturating_duration_since(mark),
+ create_snapshot_options.snapshot_path.display()
+ );
+ mark = Instant::now();
+
+ let maybe_compressed_snapshot: Box<dyn AsRef<[u8]>> =
+ if let Some(compression_cb) = create_snapshot_options.compression_cb {
+ let mut vec = vec![];
+
+ vec.extend_from_slice(
+ &u32::try_from(snapshot.len())
+ .expect("snapshot larger than 4gb")
+ .to_le_bytes(),
+ );
+
+ (compression_cb)(&mut vec, snapshot_slice);
+
+ println!(
+ "Snapshot compressed size: {}, took {:#?} ({})",
+ vec.len(),
+ Instant::now().saturating_duration_since(mark),
+ create_snapshot_options.snapshot_path.display()
+ );
+ mark = std::time::Instant::now();
+
+ Box::new(vec)
+ } else {
+ Box::new(snapshot_slice)
+ };
+
+ std::fs::write(
+ &create_snapshot_options.snapshot_path,
+ &*maybe_compressed_snapshot,
+ )
+ .unwrap();
+ println!(
+ "Snapshot written, took: {:#?} ({})",
+ Instant::now().saturating_duration_since(mark),
+ create_snapshot_options.snapshot_path.display(),
+ );
+ CreateSnapshotOutput {
+ files_loaded_during_snapshot,
+ }
+}
+
+pub type FilterFn = Box<dyn Fn(&PathBuf) -> bool>;
+
+pub fn get_js_files(
+ cargo_manifest_dir: &'static str,
+ directory: &str,
+ filter: Option<FilterFn>,
+) -> Vec<PathBuf> {
+ let manifest_dir = Path::new(cargo_manifest_dir);
+ let mut js_files = std::fs::read_dir(directory)
+ .unwrap()
+ .map(|dir_entry| {
+ let file = dir_entry.unwrap();
+ manifest_dir.join(file.path())
+ })
+ .filter(|path| {
+ path.extension().unwrap_or_default() == "js"
+ && filter.as_ref().map(|filter| filter(path)).unwrap_or(true)
+ })
+ .collect::<Vec<PathBuf>>();
+ js_files.sort();
+ js_files
+}
+
+fn data_error_to_panic(err: v8::DataError) -> ! {
+ match err {
+ v8::DataError::BadType { actual, expected } => {
+ panic!(
+ "Invalid type for snapshot data: expected {expected}, got {actual}"
+ );
+ }
+ v8::DataError::NoData { expected } => {
+ panic!("No data for snapshot data: expected {expected}");
+ }
+ }
+}
+
+pub(crate) struct SnapshottedData {
+ pub module_map_data: v8::Global<v8::Array>,
+ pub module_handles: Vec<v8::Global<v8::Module>>,
+}
+
+static MODULE_MAP_CONTEXT_DATA_INDEX: usize = 0;
+
+pub(crate) fn get_snapshotted_data(
+ scope: &mut v8::HandleScope<()>,
+ context: v8::Local<v8::Context>,
+) -> SnapshottedData {
+ let mut scope = v8::ContextScope::new(scope, context);
+
+ // The 0th element is the module map itself, followed by X number of module
+ // handles. We need to deserialize the "next_module_id" field from the
+ // map to see how many module handles we expect.
+ let result = scope.get_context_data_from_snapshot_once::<v8::Array>(
+ MODULE_MAP_CONTEXT_DATA_INDEX,
+ );
+
+ let val = match result {
+ Ok(v) => v,
+ Err(err) => data_error_to_panic(err),
+ };
+
+ let next_module_id = {
+ let info_data: v8::Local<v8::Array> =
+ val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
+ info_data.length()
+ };
+
+ // Over allocate so executing a few scripts doesn't have to resize this vec.
+ let mut module_handles = Vec::with_capacity(next_module_id as usize + 16);
+ for i in 1..=next_module_id {
+ match scope.get_context_data_from_snapshot_once::<v8::Module>(i as usize) {
+ Ok(val) => {
+ let module_global = v8::Global::new(&mut scope, val);
+ module_handles.push(module_global);
+ }
+ Err(err) => data_error_to_panic(err),
+ }
+ }
+
+ SnapshottedData {
+ module_map_data: v8::Global::new(&mut scope, val),
+ module_handles,
+ }
+}
+
+pub(crate) fn set_snapshotted_data(
+ scope: &mut v8::HandleScope<()>,
+ context: v8::Global<v8::Context>,
+ snapshotted_data: SnapshottedData,
+) {
+ let local_context = v8::Local::new(scope, context);
+ let local_data = v8::Local::new(scope, snapshotted_data.module_map_data);
+ let offset = scope.add_context_data(local_context, local_data);
+ assert_eq!(offset, MODULE_MAP_CONTEXT_DATA_INDEX);
+
+ for (index, handle) in snapshotted_data.module_handles.into_iter().enumerate()
+ {
+ let module_handle = v8::Local::new(scope, handle);
+ let offset = scope.add_context_data(local_context, module_handle);
+ assert_eq!(offset, index + 1);
+ }
+}
+
+/// Returns an isolate set up for snapshotting.
+pub(crate) fn create_snapshot_creator(
+ external_refs: &'static v8::ExternalReferences,
+ maybe_startup_snapshot: Option<Snapshot>,
+) -> v8::OwnedIsolate {
+ if let Some(snapshot) = maybe_startup_snapshot {
+ match snapshot {
+ Snapshot::Static(data) => {
+ v8::Isolate::snapshot_creator_from_existing_snapshot(
+ data,
+ Some(external_refs),
+ )
+ }
+ Snapshot::JustCreated(data) => {
+ v8::Isolate::snapshot_creator_from_existing_snapshot(
+ data,
+ Some(external_refs),
+ )
+ }
+ Snapshot::Boxed(data) => {
+ v8::Isolate::snapshot_creator_from_existing_snapshot(
+ data,
+ Some(external_refs),
+ )
+ }
+ }
+ } else {
+ v8::Isolate::snapshot_creator(Some(external_refs))
+ }
+}
diff --git a/core/runtime/tests.rs b/core/runtime/tests.rs
new file mode 100644
index 000000000..857290b80
--- /dev/null
+++ b/core/runtime/tests.rs
@@ -0,0 +1,2306 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use crate::ascii_str;
+use crate::error::custom_error;
+use crate::error::generic_error;
+use crate::error::AnyError;
+use crate::error::JsError;
+use crate::extensions::OpDecl;
+use crate::include_ascii_string;
+use crate::module_specifier::ModuleSpecifier;
+use crate::modules::AssertedModuleType;
+use crate::modules::ModuleCode;
+use crate::modules::ModuleInfo;
+use crate::modules::ModuleLoadId;
+use crate::modules::ModuleLoader;
+use crate::modules::ModuleSource;
+use crate::modules::ModuleSourceFuture;
+use crate::modules::ModuleType;
+use crate::modules::ResolutionKind;
+use crate::modules::SymbolicModule;
+use crate::Extension;
+use crate::ZeroCopyBuf;
+use crate::*;
+use anyhow::Error;
+use deno_ops::op;
+use futures::future::poll_fn;
+use futures::future::Future;
+use futures::FutureExt;
+use std::cell::RefCell;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use std::task::Context;
+use std::task::Poll;
+
+// deno_ops macros generate code assuming deno_core in scope.
+mod deno_core {
+ pub use crate::*;
+}
+
+#[derive(Copy, Clone)]
+pub enum Mode {
+ Async,
+ AsyncDeferred,
+ AsyncZeroCopy(bool),
+}
+
+struct TestState {
+ mode: Mode,
+ dispatch_count: Arc<AtomicUsize>,
+}
+
+#[op]
+async fn op_test(
+ rc_op_state: Rc<RefCell<OpState>>,
+ control: u8,
+ buf: Option<ZeroCopyBuf>,
+) -> Result<u8, AnyError> {
+ #![allow(clippy::await_holding_refcell_ref)] // False positive.
+ let op_state_ = rc_op_state.borrow();
+ let test_state = op_state_.borrow::<TestState>();
+ test_state.dispatch_count.fetch_add(1, Ordering::Relaxed);
+ let mode = test_state.mode;
+ drop(op_state_);
+ match mode {
+ Mode::Async => {
+ assert_eq!(control, 42);
+ Ok(43)
+ }
+ Mode::AsyncDeferred => {
+ tokio::task::yield_now().await;
+ assert_eq!(control, 42);
+ Ok(43)
+ }
+ Mode::AsyncZeroCopy(has_buffer) => {
+ assert_eq!(buf.is_some(), has_buffer);
+ if let Some(buf) = buf {
+ assert_eq!(buf.len(), 1);
+ }
+ Ok(43)
+ }
+ }
+}
+
+fn setup(mode: Mode) -> (JsRuntime, Arc<AtomicUsize>) {
+ let dispatch_count = Arc::new(AtomicUsize::new(0));
+ deno_core::extension!(
+ test_ext,
+ ops = [op_test],
+ options = {
+ mode: Mode,
+ dispatch_count: Arc<AtomicUsize>,
+ },
+ state = |state, options| {
+ state.put(TestState {
+ mode: options.mode,
+ dispatch_count: options.dispatch_count
+ })
+ }
+ );
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops(mode, dispatch_count.clone())],
+ get_error_class_fn: Some(&|error| {
+ crate::error::get_custom_error_class(error).unwrap()
+ }),
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "setup.js",
+ r#"
+ function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+ }
+ "#,
+ )
+ .unwrap();
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ (runtime, dispatch_count)
+}
+
+#[tokio::test]
+async fn test_ref_unref_ops() {
+ let (mut runtime, _dispatch_count) = setup(Mode::AsyncDeferred);
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+
+ var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
+ var p1 = Deno.core.opAsync("op_test", 42);
+ var p2 = Deno.core.opAsync("op_test", 42);
+ "#,
+ )
+ .unwrap();
+ {
+ let realm = runtime.global_realm();
+ assert_eq!(realm.num_pending_ops(), 2);
+ assert_eq!(realm.num_unrefed_ops(), 0);
+ }
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+ Deno.core.ops.op_unref_op(p1[promiseIdSymbol]);
+ Deno.core.ops.op_unref_op(p2[promiseIdSymbol]);
+ "#,
+ )
+ .unwrap();
+ {
+ let realm = runtime.global_realm();
+ assert_eq!(realm.num_pending_ops(), 2);
+ assert_eq!(realm.num_unrefed_ops(), 2);
+ }
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+ Deno.core.ops.op_ref_op(p1[promiseIdSymbol]);
+ Deno.core.ops.op_ref_op(p2[promiseIdSymbol]);
+ "#,
+ )
+ .unwrap();
+ {
+ let realm = runtime.global_realm();
+ assert_eq!(realm.num_pending_ops(), 2);
+ assert_eq!(realm.num_unrefed_ops(), 0);
+ }
+}
+
+#[test]
+fn test_dispatch() {
+ let (mut runtime, dispatch_count) = setup(Mode::Async);
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+ let control = 42;
+
+ Deno.core.opAsync("op_test", control);
+ async function main() {
+ Deno.core.opAsync("op_test", control);
+ }
+ main();
+ "#,
+ )
+ .unwrap();
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
+}
+
+#[test]
+fn test_op_async_promise_id() {
+ let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+
+ const p = Deno.core.opAsync("op_test", 42);
+ if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) {
+ throw new Error("missing id on returned promise");
+ }
+ "#,
+ )
+ .unwrap();
+}
+
+#[test]
+fn test_dispatch_no_zero_copy_buf() {
+ let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(false));
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+
+ Deno.core.opAsync("op_test");
+ "#,
+ )
+ .unwrap();
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+}
+
+#[test]
+fn test_dispatch_stack_zero_copy_bufs() {
+ let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(true));
+ runtime
+ .execute_script_static(
+ "filename.js",
+ r#"
+ const { op_test } = Deno.core.ensureFastOps();
+ let zero_copy_a = new Uint8Array([0]);
+ op_test(null, zero_copy_a);
+ "#,
+ )
+ .unwrap();
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+}
+
+#[test]
+fn test_execute_script_return_value() {
+ let mut runtime = JsRuntime::new(Default::default());
+ let value_global =
+ runtime.execute_script_static("a.js", "a = 1 + 2").unwrap();
+ {
+ let scope = &mut runtime.handle_scope();
+ let value = value_global.open(scope);
+ assert_eq!(value.integer_value(scope).unwrap(), 3);
+ }
+ let value_global = runtime
+ .execute_script_static("b.js", "b = 'foobar'")
+ .unwrap();
+ {
+ let scope = &mut runtime.handle_scope();
+ let value = value_global.open(scope);
+ assert!(value.is_string());
+ assert_eq!(
+ value.to_string(scope).unwrap().to_rust_string_lossy(scope),
+ "foobar"
+ );
+ }
+}
+
+#[tokio::test]
+async fn test_poll_value() {
+ let mut runtime = JsRuntime::new(Default::default());
+ poll_fn(move |cx| {
+ let value_global = runtime
+ .execute_script_static("a.js", "Promise.resolve(1 + 2)")
+ .unwrap();
+ let v = runtime.poll_value(&value_global, cx);
+ {
+ let scope = &mut runtime.handle_scope();
+ assert!(
+ matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 3)
+ );
+ }
+
+ let value_global = runtime
+ .execute_script_static(
+ "a.js",
+ "Promise.resolve(new Promise(resolve => resolve(2 + 2)))",
+ )
+ .unwrap();
+ let v = runtime.poll_value(&value_global, cx);
+ {
+ let scope = &mut runtime.handle_scope();
+ assert!(
+ matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 4)
+ );
+ }
+
+ let value_global = runtime
+ .execute_script_static("a.js", "Promise.reject(new Error('fail'))")
+ .unwrap();
+ let v = runtime.poll_value(&value_global, cx);
+ assert!(
+ matches!(v, Poll::Ready(Err(e)) if e.downcast_ref::<JsError>().unwrap().exception_message == "Uncaught Error: fail")
+ );
+
+ let value_global = runtime
+ .execute_script_static("a.js", "new Promise(resolve => {})")
+ .unwrap();
+ let v = runtime.poll_value(&value_global, cx);
+ matches!(v, Poll::Ready(Err(e)) if e.to_string() == "Promise resolution is still pending but the event loop has already resolved.");
+ Poll::Ready(())
+ }).await;
+}
+
+#[tokio::test]
+async fn test_resolve_value() {
+ let mut runtime = JsRuntime::new(Default::default());
+ let value_global = runtime
+ .execute_script_static("a.js", "Promise.resolve(1 + 2)")
+ .unwrap();
+ let result_global = runtime.resolve_value(value_global).await.unwrap();
+ {
+ let scope = &mut runtime.handle_scope();
+ let value = result_global.open(scope);
+ assert_eq!(value.integer_value(scope).unwrap(), 3);
+ }
+
+ let value_global = runtime
+ .execute_script_static(
+ "a.js",
+ "Promise.resolve(new Promise(resolve => resolve(2 + 2)))",
+ )
+ .unwrap();
+ let result_global = runtime.resolve_value(value_global).await.unwrap();
+ {
+ let scope = &mut runtime.handle_scope();
+ let value = result_global.open(scope);
+ assert_eq!(value.integer_value(scope).unwrap(), 4);
+ }
+
+ let value_global = runtime
+ .execute_script_static("a.js", "Promise.reject(new Error('fail'))")
+ .unwrap();
+ let err = runtime.resolve_value(value_global).await.unwrap_err();
+ assert_eq!(
+ "Uncaught Error: fail",
+ err.downcast::<JsError>().unwrap().exception_message
+ );
+
+ let value_global = runtime
+ .execute_script_static("a.js", "new Promise(resolve => {})")
+ .unwrap();
+ let error_string = runtime
+ .resolve_value(value_global)
+ .await
+ .unwrap_err()
+ .to_string();
+ assert_eq!(
+ "Promise resolution is still pending but the event loop has already resolved.",
+ error_string,
+ );
+}
+
+#[test]
+fn terminate_execution_webassembly() {
+ let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ let v8_isolate_handle = runtime.v8_isolate().thread_safe_handle();
+
+ // Run an infinite loop in Webassemby code, which should be terminated.
+ let promise = runtime.execute_script_static("infinite_wasm_loop.js",
+ r#"
+ (async () => {
+ const wasmCode = new Uint8Array([
+ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1,
+ 96, 0, 0, 3, 2, 1, 0, 7, 17, 1, 13,
+ 105, 110, 102, 105, 110, 105, 116, 101, 95, 108, 111,
+ 111, 112, 0, 0, 10, 9, 1, 7, 0, 3, 64,
+ 12, 0, 11, 11,
+ ]);
+ const wasmModule = await WebAssembly.compile(wasmCode);
+ globalThis.wasmInstance = new WebAssembly.Instance(wasmModule);
+ })()
+ "#).unwrap();
+ futures::executor::block_on(runtime.resolve_value(promise)).unwrap();
+ let terminator_thread = std::thread::spawn(move || {
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+
+ // terminate execution
+ let ok = v8_isolate_handle.terminate_execution();
+ assert!(ok);
+ });
+ let err = runtime
+ .execute_script_static(
+ "infinite_wasm_loop2.js",
+ "globalThis.wasmInstance.exports.infinite_loop();",
+ )
+ .unwrap_err();
+ assert_eq!(err.to_string(), "Uncaught Error: execution terminated");
+ // Cancel the execution-terminating exception in order to allow script
+ // execution again.
+ let ok = runtime.v8_isolate().cancel_terminate_execution();
+ assert!(ok);
+
+ // Verify that the isolate usable again.
+ runtime
+ .execute_script_static("simple.js", "1 + 1")
+ .expect("execution should be possible again");
+
+ terminator_thread.join().unwrap();
+}
+
+#[test]
+fn terminate_execution() {
+ let (mut isolate, _dispatch_count) = setup(Mode::Async);
+ let v8_isolate_handle = isolate.v8_isolate().thread_safe_handle();
+
+ let terminator_thread = std::thread::spawn(move || {
+ // allow deno to boot and run
+ std::thread::sleep(std::time::Duration::from_millis(100));
+
+ // terminate execution
+ let ok = v8_isolate_handle.terminate_execution();
+ assert!(ok);
+ });
+
+ // Rn an infinite loop, which should be terminated.
+ match isolate.execute_script_static("infinite_loop.js", "for(;;) {}") {
+ Ok(_) => panic!("execution should be terminated"),
+ Err(e) => {
+ assert_eq!(e.to_string(), "Uncaught Error: execution terminated")
+ }
+ };
+
+ // Cancel the execution-terminating exception in order to allow script
+ // execution again.
+ let ok = isolate.v8_isolate().cancel_terminate_execution();
+ assert!(ok);
+
+ // Verify that the isolate usable again.
+ isolate
+ .execute_script_static("simple.js", "1 + 1")
+ .expect("execution should be possible again");
+
+ terminator_thread.join().unwrap();
+}
+
+#[test]
+fn dangling_shared_isolate() {
+ let v8_isolate_handle = {
+ // isolate is dropped at the end of this block
+ let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ runtime.v8_isolate().thread_safe_handle()
+ };
+
+ // this should not SEGFAULT
+ v8_isolate_handle.terminate_execution();
+}
+
+#[test]
+fn syntax_error() {
+ let mut runtime = JsRuntime::new(Default::default());
+ let src = "hocuspocus(";
+ let r = runtime.execute_script_static("i.js", src);
+ let e = r.unwrap_err();
+ let js_error = e.downcast::<JsError>().unwrap();
+ let frame = js_error.frames.first().unwrap();
+ assert_eq!(frame.column_number, Some(12));
+}
+
+#[tokio::test]
+async fn test_encode_decode() {
+ let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ poll_fn(move |cx| {
+ runtime
+ .execute_script(
+ "encode_decode_test.js",
+ // Note: We make this to_owned because it contains non-ASCII chars
+ include_str!("encode_decode_test.js").to_owned().into(),
+ )
+ .unwrap();
+ if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) {
+ unreachable!();
+ }
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[tokio::test]
+async fn test_serialize_deserialize() {
+ let (mut runtime, _dispatch_count) = setup(Mode::Async);
+ poll_fn(move |cx| {
+ runtime
+ .execute_script(
+ "serialize_deserialize_test.js",
+ include_ascii_string!("serialize_deserialize_test.js"),
+ )
+ .unwrap();
+ if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) {
+ unreachable!();
+ }
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[tokio::test]
+async fn test_error_builder() {
+ #[op]
+ fn op_err() -> Result<(), Error> {
+ Err(custom_error("DOMExceptionOperationError", "abc"))
+ }
+
+ pub fn get_error_class_name(_: &Error) -> &'static str {
+ "DOMExceptionOperationError"
+ }
+
+ deno_core::extension!(test_ext, ops = [op_err]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ get_error_class_fn: Some(&get_error_class_name),
+ ..Default::default()
+ });
+ poll_fn(move |cx| {
+ runtime
+ .execute_script_static(
+ "error_builder_test.js",
+ include_str!("error_builder_test.js"),
+ )
+ .unwrap();
+ if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) {
+ unreachable!();
+ }
+ Poll::Ready(())
+ })
+ .await;
+}
+
+/// Ensure that putting the inspector into OpState doesn't cause crashes. The only valid place we currently allow
+/// the inspector to be stashed without cleanup is the OpState, and this should not actually cause crashes.
+#[test]
+fn inspector() {
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ inspector: true,
+ ..Default::default()
+ });
+ // This was causing a crash
+ runtime.op_state().borrow_mut().put(runtime.inspector());
+ runtime.execute_script_static("check.js", "null").unwrap();
+}
+
+#[test]
+fn will_snapshot() {
+ let snapshot = {
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
+ runtime.execute_script_static("a.js", "a = 1 + 2").unwrap();
+ runtime.snapshot()
+ };
+
+ let snapshot = Snapshot::JustCreated(snapshot);
+ let mut runtime2 = JsRuntime::new(RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ });
+ runtime2
+ .execute_script_static("check.js", "if (a != 3) throw Error('x')")
+ .unwrap();
+}
+
+#[test]
+fn will_snapshot2() {
+ let startup_data = {
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
+ runtime
+ .execute_script_static("a.js", "let a = 1 + 2")
+ .unwrap();
+ runtime.snapshot()
+ };
+
+ let snapshot = Snapshot::JustCreated(startup_data);
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ },
+ Default::default(),
+ );
+
+ let startup_data = {
+ runtime
+ .execute_script_static("check_a.js", "if (a != 3) throw Error('x')")
+ .unwrap();
+ runtime.execute_script_static("b.js", "b = 2 + 3").unwrap();
+ runtime.snapshot()
+ };
+
+ let snapshot = Snapshot::JustCreated(startup_data);
+ {
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ });
+ runtime
+ .execute_script_static("check_b.js", "if (b != 5) throw Error('x')")
+ .unwrap();
+ runtime
+ .execute_script_static("check2.js", "if (!Deno.core) throw Error('x')")
+ .unwrap();
+ }
+}
+
+#[test]
+fn test_snapshot_callbacks() {
+ let snapshot = {
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
+ runtime
+ .execute_script_static(
+ "a.js",
+ r#"
+ Deno.core.setMacrotaskCallback(() => {
+ return true;
+ });
+ Deno.core.ops.op_set_format_exception_callback(()=> {
+ return null;
+ })
+ Deno.core.setPromiseRejectCallback(() => {
+ return false;
+ });
+ a = 1 + 2;
+ "#,
+ )
+ .unwrap();
+ runtime.snapshot()
+ };
+
+ let snapshot = Snapshot::JustCreated(snapshot);
+ let mut runtime2 = JsRuntime::new(RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ });
+ runtime2
+ .execute_script_static("check.js", "if (a != 3) throw Error('x')")
+ .unwrap();
+}
+
+#[test]
+fn test_from_boxed_snapshot() {
+ let snapshot = {
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
+ runtime.execute_script_static("a.js", "a = 1 + 2").unwrap();
+ let snap: &[u8] = &runtime.snapshot();
+ Vec::from(snap).into_boxed_slice()
+ };
+
+ let snapshot = Snapshot::Boxed(snapshot);
+ let mut runtime2 = JsRuntime::new(RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ });
+ runtime2
+ .execute_script_static("check.js", "if (a != 3) throw Error('x')")
+ .unwrap();
+}
+
+#[test]
+fn test_get_module_namespace() {
+ #[derive(Default)]
+ struct ModsLoader;
+
+ impl ModuleLoader for ModsLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ _kind: ResolutionKind,
+ ) -> Result<ModuleSpecifier, Error> {
+ assert_eq!(specifier, "file:///main.js");
+ assert_eq!(referrer, ".");
+ let s = crate::resolve_import(specifier, referrer).unwrap();
+ Ok(s)
+ }
+
+ fn load(
+ &self,
+ _module_specifier: &ModuleSpecifier,
+ _maybe_referrer: Option<&ModuleSpecifier>,
+ _is_dyn_import: bool,
+ ) -> Pin<Box<ModuleSourceFuture>> {
+ async { Err(generic_error("Module loading is not supported")) }
+ .boxed_local()
+ }
+ }
+
+ let loader = std::rc::Rc::new(ModsLoader::default());
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ module_loader: Some(loader),
+ ..Default::default()
+ });
+
+ let specifier = crate::resolve_url("file:///main.js").unwrap();
+ let source_code = ascii_str!(
+ r#"
+ export const a = "b";
+ export default 1 + 2;
+ "#
+ );
+
+ let module_id = futures::executor::block_on(
+ runtime.load_main_module(&specifier, Some(source_code)),
+ )
+ .unwrap();
+
+ #[allow(clippy::let_underscore_future)]
+ let _ = runtime.mod_evaluate(module_id);
+
+ let module_namespace = runtime.get_module_namespace(module_id).unwrap();
+
+ let scope = &mut runtime.handle_scope();
+
+ let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
+
+ assert!(module_namespace.is_module_namespace_object());
+
+ let unknown_export_name = v8::String::new(scope, "none").unwrap();
+ let binding = module_namespace.get(scope, unknown_export_name.into());
+
+ assert!(binding.is_some());
+ assert!(binding.unwrap().is_undefined());
+
+ let empty_export_name = v8::String::new(scope, "").unwrap();
+ let binding = module_namespace.get(scope, empty_export_name.into());
+
+ assert!(binding.is_some());
+ assert!(binding.unwrap().is_undefined());
+
+ let a_export_name = v8::String::new(scope, "a").unwrap();
+ let binding = module_namespace.get(scope, a_export_name.into());
+
+ assert!(binding.unwrap().is_string());
+ assert_eq!(binding.unwrap(), v8::String::new(scope, "b").unwrap());
+
+ let default_export_name = v8::String::new(scope, "default").unwrap();
+ let binding = module_namespace.get(scope, default_export_name.into());
+
+ assert!(binding.unwrap().is_number());
+ assert_eq!(binding.unwrap(), v8::Number::new(scope, 3_f64));
+}
+
+#[test]
+fn test_heap_limits() {
+ let create_params =
+ v8::Isolate::create_params().heap_limits(0, 5 * 1024 * 1024);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ create_params: Some(create_params),
+ ..Default::default()
+ });
+ let cb_handle = runtime.v8_isolate().thread_safe_handle();
+
+ let callback_invoke_count = Rc::new(AtomicUsize::new(0));
+ let inner_invoke_count = Rc::clone(&callback_invoke_count);
+
+ runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| {
+ inner_invoke_count.fetch_add(1, Ordering::SeqCst);
+ cb_handle.terminate_execution();
+ current_limit * 2
+ });
+ let err = runtime
+ .execute_script_static(
+ "script name",
+ r#"let s = ""; while(true) { s += "Hello"; }"#,
+ )
+ .expect_err("script should fail");
+ assert_eq!(
+ "Uncaught Error: execution terminated",
+ err.downcast::<JsError>().unwrap().exception_message
+ );
+ assert!(callback_invoke_count.load(Ordering::SeqCst) > 0)
+}
+
+#[test]
+fn test_heap_limit_cb_remove() {
+ let mut runtime = JsRuntime::new(Default::default());
+
+ runtime.add_near_heap_limit_callback(|current_limit, _initial_limit| {
+ current_limit * 2
+ });
+ runtime.remove_near_heap_limit_callback(3 * 1024 * 1024);
+ assert!(runtime.allocations.near_heap_limit_callback_data.is_none());
+}
+
+#[test]
+fn test_heap_limit_cb_multiple() {
+ let create_params =
+ v8::Isolate::create_params().heap_limits(0, 5 * 1024 * 1024);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ create_params: Some(create_params),
+ ..Default::default()
+ });
+ let cb_handle = runtime.v8_isolate().thread_safe_handle();
+
+ let callback_invoke_count_first = Rc::new(AtomicUsize::new(0));
+ let inner_invoke_count_first = Rc::clone(&callback_invoke_count_first);
+ runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| {
+ inner_invoke_count_first.fetch_add(1, Ordering::SeqCst);
+ current_limit * 2
+ });
+
+ let callback_invoke_count_second = Rc::new(AtomicUsize::new(0));
+ let inner_invoke_count_second = Rc::clone(&callback_invoke_count_second);
+ runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| {
+ inner_invoke_count_second.fetch_add(1, Ordering::SeqCst);
+ cb_handle.terminate_execution();
+ current_limit * 2
+ });
+
+ let err = runtime
+ .execute_script_static(
+ "script name",
+ r#"let s = ""; while(true) { s += "Hello"; }"#,
+ )
+ .expect_err("script should fail");
+ assert_eq!(
+ "Uncaught Error: execution terminated",
+ err.downcast::<JsError>().unwrap().exception_message
+ );
+ assert_eq!(0, callback_invoke_count_first.load(Ordering::SeqCst));
+ assert!(callback_invoke_count_second.load(Ordering::SeqCst) > 0);
+}
+
+#[test]
+fn es_snapshot() {
+ #[derive(Default)]
+ struct ModsLoader;
+
+ impl ModuleLoader for ModsLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ _kind: ResolutionKind,
+ ) -> Result<ModuleSpecifier, Error> {
+ let s = crate::resolve_import(specifier, referrer).unwrap();
+ Ok(s)
+ }
+
+ fn load(
+ &self,
+ _module_specifier: &ModuleSpecifier,
+ _maybe_referrer: Option<&ModuleSpecifier>,
+ _is_dyn_import: bool,
+ ) -> Pin<Box<ModuleSourceFuture>> {
+ eprintln!("load() should not be called");
+ unreachable!()
+ }
+ }
+
+ fn create_module(
+ runtime: &mut JsRuntime,
+ i: usize,
+ main: bool,
+ ) -> ModuleInfo {
+ let specifier = crate::resolve_url(&format!("file:///{i}.js")).unwrap();
+ let prev = i - 1;
+ let source_code = format!(
+ r#"
+ import {{ f{prev} }} from "file:///{prev}.js";
+ export function f{i}() {{ return f{prev}() }}
+ "#
+ )
+ .into();
+
+ let id = if main {
+ futures::executor::block_on(
+ runtime.load_main_module(&specifier, Some(source_code)),
+ )
+ .unwrap()
+ } else {
+ futures::executor::block_on(
+ runtime.load_side_module(&specifier, Some(source_code)),
+ )
+ .unwrap()
+ };
+ assert_eq!(i, id);
+
+ #[allow(clippy::let_underscore_future)]
+ let _ = runtime.mod_evaluate(id);
+ futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
+
+ ModuleInfo {
+ id,
+ main,
+ name: specifier.into(),
+ requests: vec![crate::modules::ModuleRequest {
+ specifier: format!("file:///{prev}.js"),
+ asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
+ }],
+ module_type: ModuleType::JavaScript,
+ }
+ }
+
+ fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec<ModuleInfo>) {
+ let module_map = runtime.module_map.borrow();
+ assert_eq!(module_map.handles.len(), modules.len());
+ assert_eq!(module_map.info.len(), modules.len());
+ assert_eq!(
+ module_map.by_name(AssertedModuleType::Json).len()
+ + module_map
+ .by_name(AssertedModuleType::JavaScriptOrWasm)
+ .len(),
+ modules.len()
+ );
+
+ assert_eq!(module_map.next_load_id, (modules.len() + 1) as ModuleLoadId);
+
+ for info in modules {
+ assert!(module_map.handles.get(info.id).is_some());
+ assert_eq!(module_map.info.get(info.id).unwrap(), info);
+ assert_eq!(
+ module_map
+ .by_name(AssertedModuleType::JavaScriptOrWasm)
+ .get(&info.name)
+ .unwrap(),
+ &SymbolicModule::Mod(info.id)
+ );
+ }
+ }
+
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ Ok(String::from("test"))
+ }
+
+ let loader = Rc::new(ModsLoader::default());
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader.clone()),
+ extensions: vec![Extension::builder("text_ext")
+ .ops(vec![op_test::decl()])
+ .build()],
+ ..Default::default()
+ },
+ Default::default(),
+ );
+
+ let specifier = crate::resolve_url("file:///0.js").unwrap();
+ let source_code =
+ ascii_str!(r#"export function f0() { return "hello world" }"#);
+ let id = futures::executor::block_on(
+ runtime.load_side_module(&specifier, Some(source_code)),
+ )
+ .unwrap();
+
+ #[allow(clippy::let_underscore_future)]
+ let _ = runtime.mod_evaluate(id);
+ futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
+
+ let mut modules = vec![];
+ modules.push(ModuleInfo {
+ id,
+ main: false,
+ name: specifier.into(),
+ requests: vec![],
+ module_type: ModuleType::JavaScript,
+ });
+
+ modules.extend((1..200).map(|i| create_module(&mut runtime, i, false)));
+
+ assert_module_map(&mut runtime, &modules);
+
+ let snapshot = runtime.snapshot();
+
+ let mut runtime2 = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader.clone()),
+ startup_snapshot: Some(Snapshot::JustCreated(snapshot)),
+ extensions: vec![Extension::builder("text_ext")
+ .ops(vec![op_test::decl()])
+ .build()],
+ ..Default::default()
+ },
+ Default::default(),
+ );
+
+ assert_module_map(&mut runtime2, &modules);
+
+ modules.extend((200..400).map(|i| create_module(&mut runtime2, i, false)));
+ modules.push(create_module(&mut runtime2, 400, true));
+
+ assert_module_map(&mut runtime2, &modules);
+
+ let snapshot2 = runtime2.snapshot();
+
+ let mut runtime3 = JsRuntime::new(RuntimeOptions {
+ module_loader: Some(loader),
+ startup_snapshot: Some(Snapshot::JustCreated(snapshot2)),
+ extensions: vec![Extension::builder("text_ext")
+ .ops(vec![op_test::decl()])
+ .build()],
+ ..Default::default()
+ });
+
+ assert_module_map(&mut runtime3, &modules);
+
+ let source_code = r#"(async () => {
+ const mod = await import("file:///400.js");
+ return mod.f400() + " " + Deno.core.ops.op_test();
+ })();"#;
+ let val = runtime3.execute_script_static(".", source_code).unwrap();
+ let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap();
+ {
+ let scope = &mut runtime3.handle_scope();
+ let value = v8::Local::new(scope, val);
+ let str_ = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
+ assert_eq!(str_, "hello world test");
+ }
+}
+
+#[test]
+fn test_error_without_stack() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ // SyntaxError
+ let result = runtime.execute_script_static(
+ "error_without_stack.js",
+ r#"
+function main() {
+ console.log("asdf);
+}
+main();
+"#,
+ );
+ let expected_error = r#"Uncaught SyntaxError: Invalid or unexpected token
+ at error_without_stack.js:3:15"#;
+ assert_eq!(result.unwrap_err().to_string(), expected_error);
+}
+
+#[test]
+fn test_error_stack() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ let result = runtime.execute_script_static(
+ "error_stack.js",
+ r#"
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+function main() {
+ assert(false);
+}
+main();
+ "#,
+ );
+ let expected_error = r#"Error: assert
+ at assert (error_stack.js:4:11)
+ at main (error_stack.js:8:3)
+ at error_stack.js:10:1"#;
+ assert_eq!(result.unwrap_err().to_string(), expected_error);
+}
+
+#[tokio::test]
+async fn test_error_async_stack() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ poll_fn(move |cx| {
+ runtime
+ .execute_script_static(
+ "error_async_stack.js",
+ r#"
+ (async () => {
+ const p = (async () => {
+ await Promise.resolve().then(() => {
+ throw new Error("async");
+ });
+ })();
+ try {
+ await p;
+ } catch (error) {
+ console.log(error.stack);
+ throw error;
+ }
+ })();"#,
+ )
+ .unwrap();
+ let expected_error = r#"Error: async
+ at error_async_stack.js:5:13
+ at async error_async_stack.js:4:5
+ at async error_async_stack.js:9:5"#;
+
+ match runtime.poll_event_loop(cx, false) {
+ Poll::Ready(Err(e)) => {
+ assert_eq!(e.to_string(), expected_error);
+ }
+ _ => panic!(),
+ };
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[tokio::test]
+async fn test_error_context() {
+ use anyhow::anyhow;
+
+ #[op]
+ fn op_err_sync() -> Result<(), Error> {
+ Err(anyhow!("original sync error").context("higher-level sync error"))
+ }
+
+ #[op]
+ async fn op_err_async() -> Result<(), Error> {
+ Err(anyhow!("original async error").context("higher-level async error"))
+ }
+
+ deno_core::extension!(test_ext, ops = [op_err_sync, op_err_async]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ poll_fn(move |cx| {
+ runtime
+ .execute_script_static(
+ "test_error_context_sync.js",
+ r#"
+let errMessage;
+try {
+ Deno.core.ops.op_err_sync();
+} catch (err) {
+ errMessage = err.message;
+}
+if (errMessage !== "higher-level sync error: original sync error") {
+ throw new Error("unexpected error message from op_err_sync: " + errMessage);
+}
+"#,
+ )
+ .unwrap();
+
+ let promise = runtime
+ .execute_script_static(
+ "test_error_context_async.js",
+ r#"
+
+(async () => {
+let errMessage;
+try {
+ await Deno.core.opAsync("op_err_async");
+} catch (err) {
+ errMessage = err.message;
+}
+if (errMessage !== "higher-level async error: original async error") {
+ throw new Error("unexpected error message from op_err_async: " + errMessage);
+}
+})()
+"#,
+ )
+ .unwrap();
+
+ match runtime.poll_value(&promise, cx) {
+ Poll::Ready(Ok(_)) => {}
+ Poll::Ready(Err(err)) => panic!("{err:?}"),
+ _ => panic!(),
+ }
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[tokio::test]
+async fn test_pump_message_loop() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ poll_fn(move |cx| {
+ runtime
+ .execute_script_static(
+ "pump_message_loop.js",
+ r#"
+function assertEquals(a, b) {
+if (a === b) return;
+throw a + " does not equal " + b;
+}
+const sab = new SharedArrayBuffer(16);
+const i32a = new Int32Array(sab);
+globalThis.resolved = false;
+(function() {
+const result = Atomics.waitAsync(i32a, 0, 0);
+result.value.then(
+ (value) => { assertEquals("ok", value); globalThis.resolved = true; },
+ () => { assertUnreachable();
+});
+})();
+const notify_return_value = Atomics.notify(i32a, 0, 1);
+assertEquals(1, notify_return_value);
+"#,
+ )
+ .unwrap();
+
+ match runtime.poll_event_loop(cx, false) {
+ Poll::Ready(Ok(())) => {}
+ _ => panic!(),
+ };
+
+ // noop script, will resolve promise from first script
+ runtime
+ .execute_script_static("pump_message_loop2.js", r#"assertEquals(1, 1);"#)
+ .unwrap();
+
+ // check that promise from `Atomics.waitAsync` has been resolved
+ runtime
+ .execute_script_static(
+ "pump_message_loop3.js",
+ r#"assertEquals(globalThis.resolved, true);"#,
+ )
+ .unwrap();
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[test]
+fn test_v8_platform() {
+ let options = RuntimeOptions {
+ v8_platform: Some(v8::new_default_platform(0, false).make_shared()),
+ ..Default::default()
+ };
+ let mut runtime = JsRuntime::new(options);
+ runtime.execute_script_static("<none>", "").unwrap();
+}
+
+#[ignore] // TODO(@littledivy): Fast API ops when snapshot is not loaded.
+#[test]
+fn test_is_proxy() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ let all_true: v8::Global<v8::Value> = runtime
+ .execute_script_static(
+ "is_proxy.js",
+ r#"
+ (function () {
+ const o = { a: 1, b: 2};
+ const p = new Proxy(o, {});
+ return Deno.core.ops.op_is_proxy(p) && !Deno.core.ops.op_is_proxy(o) && !Deno.core.ops.op_is_proxy(42);
+ })()
+ "#,
+ )
+ .unwrap();
+ let mut scope = runtime.handle_scope();
+ let all_true = v8::Local::<v8::Value>::new(&mut scope, &all_true);
+ assert!(all_true.is_true());
+}
+
+#[tokio::test]
+async fn test_async_opstate_borrow() {
+ struct InnerState(u64);
+
+ #[op]
+ async fn op_async_borrow(
+ op_state: Rc<RefCell<OpState>>,
+ ) -> Result<(), Error> {
+ let n = {
+ let op_state = op_state.borrow();
+ let inner_state = op_state.borrow::<InnerState>();
+ inner_state.0
+ };
+ // Future must be Poll::Pending on first call
+ tokio::time::sleep(std::time::Duration::from_millis(1)).await;
+ if n != 42 {
+ unreachable!();
+ }
+ Ok(())
+ }
+
+ deno_core::extension!(
+ test_ext,
+ ops = [op_async_borrow],
+ state = |state| state.put(InnerState(42))
+ );
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "op_async_borrow.js",
+ "Deno.core.opAsync(\"op_async_borrow\")",
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_sync_op_serialize_object_with_numbers_as_keys() {
+ #[op]
+ fn op_sync_serialize_object_with_numbers_as_keys(
+ value: serde_json::Value,
+ ) -> Result<(), Error> {
+ assert_eq!(
+ value.to_string(),
+ r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"#
+ );
+ Ok(())
+ }
+
+ deno_core::extension!(
+ test_ext,
+ ops = [op_sync_serialize_object_with_numbers_as_keys]
+ );
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "op_sync_serialize_object_with_numbers_as_keys.js",
+ r#"
+Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({
+lines: {
+ 100: {
+ unit: "m"
+ },
+ 200: {
+ unit: "cm"
+ }
+}
+})
+"#,
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_async_op_serialize_object_with_numbers_as_keys() {
+ #[op]
+ async fn op_async_serialize_object_with_numbers_as_keys(
+ value: serde_json::Value,
+ ) -> Result<(), Error> {
+ assert_eq!(
+ value.to_string(),
+ r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"#
+ );
+ Ok(())
+ }
+
+ deno_core::extension!(
+ test_ext,
+ ops = [op_async_serialize_object_with_numbers_as_keys]
+ );
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "op_async_serialize_object_with_numbers_as_keys.js",
+ r#"
+
+Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
+lines: {
+ 100: {
+ unit: "m"
+ },
+ 200: {
+ unit: "cm"
+ }
+}
+})
+"#,
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_set_macrotask_callback_set_next_tick_callback() {
+ #[op]
+ async fn op_async_sleep() -> Result<(), Error> {
+ // Future must be Poll::Pending on first call
+ tokio::time::sleep(std::time::Duration::from_millis(1)).await;
+ Ok(())
+ }
+
+ deno_core::extension!(test_ext, ops = [op_async_sleep]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "macrotasks_and_nextticks.js",
+ r#"
+
+ (async function () {
+ const results = [];
+ Deno.core.setMacrotaskCallback(() => {
+ results.push("macrotask");
+ return true;
+ });
+ Deno.core.setNextTickCallback(() => {
+ results.push("nextTick");
+ Deno.core.ops.op_set_has_tick_scheduled(false);
+ });
+ Deno.core.ops.op_set_has_tick_scheduled(true);
+ await Deno.core.opAsync('op_async_sleep');
+ if (results[0] != "nextTick") {
+ throw new Error(`expected nextTick, got: ${results[0]}`);
+ }
+ if (results[1] != "macrotask") {
+ throw new Error(`expected macrotask, got: ${results[1]}`);
+ }
+ })();
+ "#,
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap();
+}
+
+#[test]
+fn test_has_tick_scheduled() {
+ use futures::task::ArcWake;
+
+ static MACROTASK: AtomicUsize = AtomicUsize::new(0);
+ static NEXT_TICK: AtomicUsize = AtomicUsize::new(0);
+
+ #[op]
+ fn op_macrotask() -> Result<(), AnyError> {
+ MACROTASK.fetch_add(1, Ordering::Relaxed);
+ Ok(())
+ }
+
+ #[op]
+ fn op_next_tick() -> Result<(), AnyError> {
+ NEXT_TICK.fetch_add(1, Ordering::Relaxed);
+ Ok(())
+ }
+
+ deno_core::extension!(test_ext, ops = [op_macrotask, op_next_tick]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "has_tick_scheduled.js",
+ r#"
+ Deno.core.setMacrotaskCallback(() => {
+ Deno.core.ops.op_macrotask();
+ return true; // We're done.
+ });
+ Deno.core.setNextTickCallback(() => Deno.core.ops.op_next_tick());
+ Deno.core.ops.op_set_has_tick_scheduled(true);
+ "#,
+ )
+ .unwrap();
+
+ struct ArcWakeImpl(Arc<AtomicUsize>);
+ impl ArcWake for ArcWakeImpl {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ arc_self.0.fetch_add(1, Ordering::Relaxed);
+ }
+ }
+
+ let awoken_times = Arc::new(AtomicUsize::new(0));
+ let waker = futures::task::waker(Arc::new(ArcWakeImpl(awoken_times.clone())));
+ let cx = &mut Context::from_waker(&waker);
+
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+ assert_eq!(1, MACROTASK.load(Ordering::Relaxed));
+ assert_eq!(1, NEXT_TICK.load(Ordering::Relaxed));
+ assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+ assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+ assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+ assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
+
+ runtime.inner.state.borrow_mut().has_tick_scheduled = false;
+ assert!(matches!(
+ runtime.poll_event_loop(cx, false),
+ Poll::Ready(Ok(()))
+ ));
+ assert_eq!(awoken_times.load(Ordering::Relaxed), 0);
+ assert!(matches!(
+ runtime.poll_event_loop(cx, false),
+ Poll::Ready(Ok(()))
+ ));
+ assert_eq!(awoken_times.load(Ordering::Relaxed), 0);
+}
+
+#[test]
+fn terminate_during_module_eval() {
+ #[derive(Default)]
+ struct ModsLoader;
+
+ impl ModuleLoader for ModsLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ _kind: ResolutionKind,
+ ) -> Result<ModuleSpecifier, Error> {
+ assert_eq!(specifier, "file:///main.js");
+ assert_eq!(referrer, ".");
+ let s = crate::resolve_import(specifier, referrer).unwrap();
+ Ok(s)
+ }
+
+ fn load(
+ &self,
+ _module_specifier: &ModuleSpecifier,
+ _maybe_referrer: Option<&ModuleSpecifier>,
+ _is_dyn_import: bool,
+ ) -> Pin<Box<ModuleSourceFuture>> {
+ async move {
+ Ok(ModuleSource::for_test(
+ "console.log('hello world');",
+ "file:///main.js",
+ ))
+ }
+ .boxed_local()
+ }
+ }
+
+ let loader = std::rc::Rc::new(ModsLoader::default());
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ module_loader: Some(loader),
+ ..Default::default()
+ });
+
+ let specifier = crate::resolve_url("file:///main.js").unwrap();
+ let source_code = ascii_str!("Deno.core.print('hello\\n')");
+
+ let module_id = futures::executor::block_on(
+ runtime.load_main_module(&specifier, Some(source_code)),
+ )
+ .unwrap();
+
+ runtime.v8_isolate().terminate_execution();
+
+ let mod_result =
+ futures::executor::block_on(runtime.mod_evaluate(module_id)).unwrap();
+ assert!(mod_result
+ .unwrap_err()
+ .to_string()
+ .contains("JavaScript execution has been terminated"));
+}
+
+#[tokio::test]
+async fn test_unhandled_rejection_order() {
+ let mut runtime = JsRuntime::new(Default::default());
+ runtime
+ .execute_script_static(
+ "",
+ r#"
+ for (let i = 0; i < 100; i++) {
+ Promise.reject(i);
+ }
+ "#,
+ )
+ .unwrap();
+ let err = runtime.run_event_loop(false).await.unwrap_err();
+ assert_eq!(err.to_string(), "Uncaught (in promise) 0");
+}
+
+#[tokio::test]
+async fn test_set_promise_reject_callback() {
+ static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0);
+
+ #[op]
+ fn op_promise_reject() -> Result<(), AnyError> {
+ PROMISE_REJECT.fetch_add(1, Ordering::Relaxed);
+ Ok(())
+ }
+
+ deno_core::extension!(test_ext, ops = [op_promise_reject]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "promise_reject_callback.js",
+ r#"
+ // Note: |promise| is not the promise created below, it's a child.
+ Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => {
+ if (type !== /* PromiseRejectWithNoHandler */ 0) {
+ throw Error("unexpected type: " + type);
+ }
+ if (reason.message !== "reject") {
+ throw Error("unexpected reason: " + reason);
+ }
+ Deno.core.ops.op_store_pending_promise_rejection(promise);
+ Deno.core.ops.op_promise_reject();
+ });
+ new Promise((_, reject) => reject(Error("reject")));
+ "#,
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap_err();
+
+ assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed));
+
+ runtime
+ .execute_script_static(
+ "promise_reject_callback.js",
+ r#"
+ {
+ const prev = Deno.core.ops.op_set_promise_reject_callback((...args) => {
+ prev(...args);
+ });
+ }
+ new Promise((_, reject) => reject(Error("reject")));
+ "#,
+ )
+ .unwrap();
+ runtime.run_event_loop(false).await.unwrap_err();
+
+ assert_eq!(2, PROMISE_REJECT.load(Ordering::Relaxed));
+}
+
+#[tokio::test]
+async fn test_set_promise_reject_callback_realms() {
+ let mut runtime = JsRuntime::new(RuntimeOptions::default());
+ let global_realm = runtime.global_realm();
+ let realm1 = runtime.create_realm().unwrap();
+ let realm2 = runtime.create_realm().unwrap();
+
+ let realm_expectations = &[
+ (&global_realm, "global_realm", 42),
+ (&realm1, "realm1", 140),
+ (&realm2, "realm2", 720),
+ ];
+
+ // Set up promise reject callbacks.
+ for (realm, realm_name, number) in realm_expectations {
+ realm
+ .execute_script(
+ runtime.v8_isolate(),
+ "",
+ format!(
+ r#"
+
+ globalThis.rejectValue = undefined;
+ Deno.core.setPromiseRejectCallback((_type, _promise, reason) => {{
+ globalThis.rejectValue = `{realm_name}/${{reason}}`;
+ }});
+ Deno.core.opAsync("op_void_async").then(() => Promise.reject({number}));
+ "#
+ ).into()
+ )
+ .unwrap();
+ }
+
+ runtime.run_event_loop(false).await.unwrap();
+
+ for (realm, realm_name, number) in realm_expectations {
+ let reject_value = realm
+ .execute_script_static(runtime.v8_isolate(), "", "globalThis.rejectValue")
+ .unwrap();
+ let scope = &mut realm.handle_scope(runtime.v8_isolate());
+ let reject_value = v8::Local::new(scope, reject_value);
+ assert!(reject_value.is_string());
+ let reject_value_string = reject_value.to_rust_string_lossy(scope);
+ assert_eq!(reject_value_string, format!("{realm_name}/{number}"));
+ }
+}
+
+#[tokio::test]
+async fn test_set_promise_reject_callback_top_level_await() {
+ static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0);
+
+ #[op]
+ fn op_promise_reject() -> Result<(), AnyError> {
+ PROMISE_REJECT.fetch_add(1, Ordering::Relaxed);
+ Ok(())
+ }
+
+ deno_core::extension!(test_ext, ops = [op_promise_reject]);
+
+ #[derive(Default)]
+ struct ModsLoader;
+
+ impl ModuleLoader for ModsLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ _kind: ResolutionKind,
+ ) -> Result<ModuleSpecifier, Error> {
+ assert_eq!(specifier, "file:///main.js");
+ assert_eq!(referrer, ".");
+ let s = crate::resolve_import(specifier, referrer).unwrap();
+ Ok(s)
+ }
+
+ fn load(
+ &self,
+ _module_specifier: &ModuleSpecifier,
+ _maybe_referrer: Option<&ModuleSpecifier>,
+ _is_dyn_import: bool,
+ ) -> Pin<Box<ModuleSourceFuture>> {
+ let code = r#"
+ Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => {
+ Deno.core.ops.op_promise_reject();
+ });
+ throw new Error('top level throw');
+ "#;
+
+ async move { Ok(ModuleSource::for_test(code, "file:///main.js")) }
+ .boxed_local()
+ }
+ }
+
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ module_loader: Some(Rc::new(ModsLoader)),
+ ..Default::default()
+ });
+
+ let id = runtime
+ .load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None)
+ .await
+ .unwrap();
+ let receiver = runtime.mod_evaluate(id);
+ runtime.run_event_loop(false).await.unwrap();
+ receiver.await.unwrap().unwrap_err();
+
+ assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed));
+}
+
+#[test]
+fn test_op_return_serde_v8_error() {
+ #[op]
+ fn op_err() -> Result<std::collections::BTreeMap<u64, u64>, anyhow::Error> {
+ Ok([(1, 2), (3, 4)].into_iter().collect()) // Maps can't have non-string keys in serde_v8
+ }
+
+ deno_core::extension!(test_ext, ops = [op_err]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ assert!(runtime
+ .execute_script_static(
+ "test_op_return_serde_v8_error.js",
+ "Deno.core.ops.op_err()"
+ )
+ .is_err());
+}
+
+#[test]
+fn test_op_high_arity() {
+ #[op]
+ fn op_add_4(
+ x1: i64,
+ x2: i64,
+ x3: i64,
+ x4: i64,
+ ) -> Result<i64, anyhow::Error> {
+ Ok(x1 + x2 + x3 + x4)
+ }
+
+ deno_core::extension!(test_ext, ops = [op_add_4]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ let r = runtime
+ .execute_script_static("test.js", "Deno.core.ops.op_add_4(1, 2, 3, 4)")
+ .unwrap();
+ let scope = &mut runtime.handle_scope();
+ assert_eq!(r.open(scope).integer_value(scope), Some(10));
+}
+
+#[test]
+fn test_op_disabled() {
+ #[op]
+ fn op_foo() -> Result<i64, anyhow::Error> {
+ Ok(42)
+ }
+
+ fn ops() -> Vec<OpDecl> {
+ vec![op_foo::decl().disable()]
+ }
+
+ deno_core::extension!(test_ext, ops_fn = ops);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ let err = runtime
+ .execute_script_static("test.js", "Deno.core.ops.op_foo()")
+ .unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("TypeError: Deno.core.ops.op_foo is not a function"));
+}
+
+#[test]
+fn test_op_detached_buffer() {
+ use serde_v8::DetachedBuffer;
+
+ #[op]
+ fn op_sum_take(b: DetachedBuffer) -> Result<u64, anyhow::Error> {
+ Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum())
+ }
+
+ #[op]
+ fn op_boomerang(b: DetachedBuffer) -> Result<DetachedBuffer, anyhow::Error> {
+ Ok(b)
+ }
+
+ deno_core::extension!(test_ext, ops = [op_sum_take, op_boomerang]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ runtime
+ .execute_script_static(
+ "test.js",
+ r#"
+ const a1 = new Uint8Array([1,2,3]);
+ const a1b = a1.subarray(0, 3);
+ const a2 = new Uint8Array([5,10,15]);
+ const a2b = a2.subarray(0, 3);
+ if (!(a1.length > 0 && a1b.length > 0)) {
+ throw new Error("a1 & a1b should have a length");
+ }
+ let sum = Deno.core.ops.op_sum_take(a1b);
+ if (sum !== 6) {
+ throw new Error(`Bad sum: ${sum}`);
+ }
+ if (a1.length > 0 || a1b.length > 0) {
+ throw new Error("expecting a1 & a1b to be detached");
+ }
+ const a3 = Deno.core.ops.op_boomerang(a2b);
+ if (a3.byteLength != 3) {
+ throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`);
+ }
+ if (a3[0] !== 5 || a3[1] !== 10) {
+ throw new Error(`Invalid a3: ${a3[0]}, ${a3[1]}`);
+ }
+ if (a2.byteLength > 0 || a2b.byteLength > 0) {
+ throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
+ }
+ const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
+ const w32 = new Uint32Array(wmem.buffer);
+ w32[0] = 1; w32[1] = 2; w32[2] = 3;
+ const assertWasmThrow = (() => {
+ try {
+ let sum = Deno.core.ops.op_sum_take(w32.subarray(0, 2));
+ return false;
+ } catch(e) {
+ return e.message.includes('invalid type; expected: detachable');
+ }
+ });
+ if (!assertWasmThrow()) {
+ throw new Error("expected wasm mem to not be detachable");
+ }
+ "#,
+ )
+ .unwrap();
+}
+
+#[test]
+fn test_op_unstable_disabling() {
+ #[op]
+ fn op_foo() -> Result<i64, anyhow::Error> {
+ Ok(42)
+ }
+
+ #[op(unstable)]
+ fn op_bar() -> Result<i64, anyhow::Error> {
+ Ok(42)
+ }
+
+ deno_core::extension!(
+ test_ext,
+ ops = [op_foo, op_bar],
+ middleware = |op| if op.is_unstable { op.disable() } else { op }
+ );
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ runtime
+ .execute_script_static(
+ "test.js",
+ r#"
+ if (Deno.core.ops.op_foo() !== 42) {
+ throw new Error("Exptected op_foo() === 42");
+ }
+ if (typeof Deno.core.ops.op_bar !== "undefined") {
+ throw new Error("Expected op_bar to be disabled")
+ }
+ "#,
+ )
+ .unwrap();
+}
+
+#[test]
+fn js_realm_simple() {
+ let mut runtime = JsRuntime::new(Default::default());
+ let main_context = runtime.global_context();
+ let main_global = {
+ let scope = &mut runtime.handle_scope();
+ let local_global = main_context.open(scope).global(scope);
+ v8::Global::new(scope, local_global)
+ };
+
+ let realm = runtime.create_realm().unwrap();
+ assert_ne!(realm.context(), &main_context);
+ assert_ne!(realm.global_object(runtime.v8_isolate()), main_global);
+
+ let main_object = runtime.execute_script_static("", "Object").unwrap();
+ let realm_object = realm
+ .execute_script_static(runtime.v8_isolate(), "", "Object")
+ .unwrap();
+ assert_ne!(main_object, realm_object);
+}
+
+#[test]
+fn js_realm_init() {
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ deno_core::extension!(test_ext, ops = [op_test]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ let realm = runtime.create_realm().unwrap();
+ let ret = realm
+ .execute_script_static(runtime.v8_isolate(), "", "Deno.core.ops.op_test()")
+ .unwrap();
+
+ let scope = &mut realm.handle_scope(runtime.v8_isolate());
+ assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap());
+}
+
+#[test]
+fn js_realm_init_snapshot() {
+ let snapshot = {
+ let runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
+ let snap: &[u8] = &runtime.snapshot();
+ Vec::from(snap).into_boxed_slice()
+ };
+
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ deno_core::extension!(test_ext, ops = [op_test]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ startup_snapshot: Some(Snapshot::Boxed(snapshot)),
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+ let realm = runtime.create_realm().unwrap();
+ let ret = realm
+ .execute_script_static(runtime.v8_isolate(), "", "Deno.core.ops.op_test()")
+ .unwrap();
+
+ let scope = &mut realm.handle_scope(runtime.v8_isolate());
+ assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap());
+}
+
+#[test]
+fn js_realm_sync_ops() {
+ // Test that returning a ZeroCopyBuf and throwing an exception from a sync
+ // op result in objects with prototypes from the right realm. Note that we
+ // don't test the result of returning structs, because they will be
+ // serialized to objects with null prototype.
+
+ #[op]
+ fn op_test(fail: bool) -> Result<ZeroCopyBuf, Error> {
+ if !fail {
+ Ok(ZeroCopyBuf::empty())
+ } else {
+ Err(crate::error::type_error("Test"))
+ }
+ }
+
+ deno_core::extension!(test_ext, ops = [op_test]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ get_error_class_fn: Some(&|error| {
+ crate::error::get_custom_error_class(error).unwrap()
+ }),
+ ..Default::default()
+ });
+ let new_realm = runtime.create_realm().unwrap();
+
+ // Test in both realms
+ for realm in [runtime.global_realm(), new_realm].into_iter() {
+ let ret = realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+ const buf = Deno.core.ops.op_test(false);
+ try {
+ Deno.core.ops.op_test(true);
+ } catch(e) {
+ err = e;
+ }
+ buf instanceof Uint8Array && buf.byteLength === 0 &&
+ err instanceof TypeError && err.message === "Test"
+ "#,
+ )
+ .unwrap();
+ assert!(ret.open(runtime.v8_isolate()).is_true());
+ }
+}
+
+#[tokio::test]
+async fn js_realm_async_ops() {
+ // Test that returning a ZeroCopyBuf and throwing an exception from a async
+ // op result in objects with prototypes from the right realm. Note that we
+ // don't test the result of returning structs, because they will be
+ // serialized to objects with null prototype.
+
+ #[op]
+ async fn op_test(fail: bool) -> Result<ZeroCopyBuf, Error> {
+ if !fail {
+ Ok(ZeroCopyBuf::empty())
+ } else {
+ Err(crate::error::type_error("Test"))
+ }
+ }
+
+ deno_core::extension!(test_ext, ops = [op_test]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ get_error_class_fn: Some(&|error| {
+ crate::error::get_custom_error_class(error).unwrap()
+ }),
+ ..Default::default()
+ });
+
+ let global_realm = runtime.global_realm();
+ let new_realm = runtime.create_realm().unwrap();
+
+ let mut rets = vec![];
+
+ // Test in both realms
+ for realm in [global_realm, new_realm].into_iter() {
+ let ret = realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+
+ (async function () {
+ const buf = await Deno.core.opAsync("op_test", false);
+ let err;
+ try {
+ await Deno.core.opAsync("op_test", true);
+ } catch(e) {
+ err = e;
+ }
+ return buf instanceof Uint8Array && buf.byteLength === 0 &&
+ err instanceof TypeError && err.message === "Test" ;
+ })();
+ "#,
+ )
+ .unwrap();
+ rets.push((realm, ret));
+ }
+
+ runtime.run_event_loop(false).await.unwrap();
+
+ for ret in rets {
+ let scope = &mut ret.0.handle_scope(runtime.v8_isolate());
+ let value = v8::Local::new(scope, ret.1);
+ let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
+ let result = promise.result(scope);
+
+ assert!(result.is_boolean() && result.is_true());
+ }
+}
+
+#[ignore]
+#[tokio::test]
+async fn js_realm_gc() {
+ static INVOKE_COUNT: AtomicUsize = AtomicUsize::new(0);
+ struct PendingFuture {}
+
+ impl Future for PendingFuture {
+ type Output = ();
+ fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
+ Poll::Pending
+ }
+ }
+
+ impl Drop for PendingFuture {
+ fn drop(&mut self) {
+ assert_eq!(INVOKE_COUNT.fetch_sub(1, Ordering::SeqCst), 1);
+ }
+ }
+
+ // Never resolves.
+ #[op]
+ async fn op_pending() {
+ assert_eq!(INVOKE_COUNT.fetch_add(1, Ordering::SeqCst), 0);
+ PendingFuture {}.await
+ }
+
+ deno_core::extension!(test_ext, ops = [op_pending]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ // Detect a drop in OpState
+ let opstate_drop_detect = Rc::new(());
+ runtime
+ .op_state()
+ .borrow_mut()
+ .put(opstate_drop_detect.clone());
+ assert_eq!(Rc::strong_count(&opstate_drop_detect), 2);
+
+ let other_realm = runtime.create_realm().unwrap();
+ other_realm
+ .execute_script(
+ runtime.v8_isolate(),
+ "future",
+ ModuleCode::from_static("Deno.core.opAsync('op_pending')"),
+ )
+ .unwrap();
+ while INVOKE_COUNT.load(Ordering::SeqCst) == 0 {
+ poll_fn(|cx: &mut Context| runtime.poll_event_loop(cx, false))
+ .await
+ .unwrap();
+ }
+ drop(other_realm);
+ while INVOKE_COUNT.load(Ordering::SeqCst) == 1 {
+ poll_fn(|cx| runtime.poll_event_loop(cx, false))
+ .await
+ .unwrap();
+ }
+ drop(runtime);
+
+ // Make sure the OpState was dropped properly when the runtime dropped
+ assert_eq!(Rc::strong_count(&opstate_drop_detect), 1);
+}
+
+#[tokio::test]
+async fn js_realm_ref_unref_ops() {
+ // Never resolves.
+ #[op]
+ async fn op_pending() {
+ futures::future::pending().await
+ }
+
+ deno_core::extension!(test_ext, ops = [op_pending]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ poll_fn(move |cx| {
+ let main_realm = runtime.global_realm();
+ let other_realm = runtime.create_realm().unwrap();
+
+ main_realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+
+ var promise = Deno.core.opAsync("op_pending");
+ "#,
+ )
+ .unwrap();
+ other_realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+
+ var promise = Deno.core.opAsync("op_pending");
+ "#,
+ )
+ .unwrap();
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+
+ main_realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+ let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
+ Deno.core.unrefOp(promise[promiseIdSymbol]);
+ "#,
+ )
+ .unwrap();
+ assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
+
+ other_realm
+ .execute_script_static(
+ runtime.v8_isolate(),
+ "",
+ r#"
+ let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
+ Deno.core.unrefOp(promise[promiseIdSymbol]);
+ "#,
+ )
+ .unwrap();
+ assert!(matches!(
+ runtime.poll_event_loop(cx, false),
+ Poll::Ready(Ok(()))
+ ));
+ Poll::Ready(())
+ })
+ .await;
+}
+
+#[test]
+fn test_array_by_copy() {
+ // Verify that "array by copy" proposal is enabled (https://github.com/tc39/proposal-change-array-by-copy)
+ let mut runtime = JsRuntime::new(Default::default());
+ assert!(runtime
+ .execute_script_static(
+ "test_array_by_copy.js",
+ "const a = [1, 2, 3];
+ const b = a.toReversed();
+ if (!(a[0] === 1 && a[1] === 2 && a[2] === 3)) {
+ throw new Error('Expected a to be intact');
+ }
+ if (!(b[0] === 3 && b[1] === 2 && b[2] === 1)) {
+ throw new Error('Expected b to be reversed');
+ }",
+ )
+ .is_ok());
+}
+
+#[cfg(debug_assertions)]
+#[test]
+#[should_panic(expected = "Found ops with duplicate names:")]
+fn duplicate_op_names() {
+ mod a {
+ use super::*;
+
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+ }
+
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ deno_core::extension!(test_ext, ops = [a::op_test, op_test]);
+ JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+}
+
+#[test]
+fn ops_in_js_have_proper_names() {
+ #[op]
+ fn op_test_sync() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ #[op]
+ async fn op_test_async() -> Result<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ deno_core::extension!(test_ext, ops = [op_test_sync, op_test_async]);
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ extensions: vec![test_ext::init_ops()],
+ ..Default::default()
+ });
+
+ let src = r#"
+ if (Deno.core.ops.op_test_sync.name !== "op_test_sync") {
+ throw new Error();
+ }
+
+ if (Deno.core.ops.op_test_async.name !== "op_test_async") {
+ throw new Error();
+ }
+
+ const { op_test_async } = Deno.core.ensureFastOps();
+ if (op_test_async.name !== "op_test_async") {
+ throw new Error();
+ }
+ "#;
+ runtime.execute_script_static("test", src).unwrap();
+}