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.rs545
-rw-r--r--core/runtime/encode_decode_test.js69
-rw-r--r--core/runtime/error_builder_test.js32
-rw-r--r--core/runtime/icudtl.datbin10541264 -> 0 bytes
-rw-r--r--core/runtime/jsrealm.rs365
-rw-r--r--core/runtime/jsruntime.rs2316
-rw-r--r--core/runtime/mod.rs34
-rw-r--r--core/runtime/ops.rs634
-rw-r--r--core/runtime/serialize_deserialize_test.js74
-rw-r--r--core/runtime/snapshot_util.rs259
-rw-r--r--core/runtime/tests.rs2396
12 files changed, 0 insertions, 6775 deletions
diff --git a/core/runtime/bindings.js b/core/runtime/bindings.js
deleted file mode 100644
index 21d27a2c3..000000000
--- a/core/runtime/bindings.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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
deleted file mode 100644
index 55911825f..000000000
--- a/core/runtime/bindings.rs
+++ /dev/null
@@ -1,545 +0,0 @@
-// 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::throw_type_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 maintained 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);
-}
diff --git a/core/runtime/encode_decode_test.js b/core/runtime/encode_decode_test.js
deleted file mode 100644
index 0f4668765..000000000
--- a/core/runtime/encode_decode_test.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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
deleted file mode 100644
index f442819cb..000000000
--- a/core/runtime/error_builder_test.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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
deleted file mode 100644
index d1f10917a..000000000
--- a/core/runtime/icudtl.dat
+++ /dev/null
Binary files differ
diff --git a/core/runtime/jsrealm.rs b/core/runtime/jsrealm.rs
deleted file mode 100644
index 72818eebe..000000000
--- a/core/runtime/jsrealm.rs
+++ /dev/null
@@ -1,365 +0,0 @@
-// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-use super::bindings;
-use crate::error::exception_to_err_result;
-use crate::joinset::JoinSet;
-use crate::modules::ModuleCode;
-use crate::ops::OpCtx;
-use crate::runtime::JsRuntimeState;
-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 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<(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 destroy 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
deleted file mode 100644
index cea8fcb1f..000000000
--- a/core/runtime/jsruntime.rs
+++ /dev/null
@@ -1,2316 +0,0 @@
-// 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::include_js_files;
-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::ops::*;
-use crate::runtime::ContextState;
-use crate::runtime::JsRealm;
-use crate::source_map::SourceMapCache;
-use crate::source_map::SourceMapGetter;
-use crate::Extension;
-use crate::ExtensionFileSource;
-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::stream::StreamExt;
-use once_cell::sync::Lazy;
-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,
- }
- }
-}
-
-pub(crate) static BUILTIN_SOURCES: Lazy<Vec<ExtensionFileSource>> =
- Lazy::new(|| {
- include_js_files!(
- core
- "00_primordials.js",
- "01_core.js",
- "02_error.js",
- )
- });
-
-/// 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>,
-
- /// If provided, the module map will be cleared and left only with the specifiers
- /// in this list, with the new names provided. If not provided, the module map is
- /// left intact.
- pub rename_modules: Option<Vec<(&'static str, &'static str)>>,
-
- /// 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 silently 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);
- 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();
-
- // If the user has requested that we rename modules
- if let Some(rename_modules) = options.rename_modules {
- js_runtime
- .module_map
- .borrow_mut()
- .clear_module_map(rename_modules.into_iter());
- }
-
- 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 {
- if self.init_mode == InitMode::New {
- for file_source in &*BUILTIN_SOURCES {
- realm.execute_script(
- self.v8_isolate(),
- file_source.specifier,
- file_source.load()?,
- )?;
- }
- }
- self.init_cbs(realm);
-
- for extension in &extensions {
- let maybe_esm_entry_point = extension.get_esm_entry_point();
-
- for file_source in extension.get_esm_sources() {
- 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);
- }
-
- for file_source in extension.get_js_sources() {
- realm.execute_script(
- self.v8_isolate(),
- file_source.specifier,
- file_source.load()?,
- )?;
- }
- }
-
- 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 middleware & 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) -> (OpState, Vec<OpDecl>) {
- // Add built-in extension
- options
- .extensions
- .insert(0, crate::ops_builtin::core::init_ops());
-
- 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> {
- self
- .module_map
- .clone()
- .borrow()
- .get_module_namespace(&mut self.handle_scope(), module_id)
- }
-
- /// 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();
- }
-
- let module_map = self.module_map.clone();
- 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 = module_map.borrow().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 = module_map.borrow().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()
- }
-}
-
-#[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>> {
- self
- .module_map
- .clone()
- .borrow_mut()
- .instantiate_module(&mut self.handle_scope(), id)
- }
-
- 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 overridden 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
- }
-
- 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 Poll::Ready(item) = context_state.pending_ops.poll_join_next(cx) else {
- break;
- };
- // TODO(mmastrac): If this task is really errored, things could be pretty bad
- let (promise_id, op_id, mut resp) = item.unwrap();
- 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
deleted file mode 100644
index aa546b8c7..000000000
--- a/core/runtime/mod.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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;
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
deleted file mode 100644
index 5ecab5edf..000000000
--- a/core/runtime/ops.rs
+++ /dev/null
@@ -1,634 +0,0 @@
-// 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::Either;
-use futures::future::Future;
-use futures::future::FutureExt;
-use futures::task::noop_waker_ref;
-use std::borrow::Cow;
-use std::cell::RefCell;
-use std::future::ready;
-use std::mem::MaybeUninit;
-use std::option::Option;
-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));
- ctx
- .context_state
- .borrow_mut()
- .pending_ops
- .spawn(OpCall::new(ctx, promise_id, fut));
-}
-
-#[inline]
-pub fn map_async_op1<R: serde::Serialize + 'static>(
- ctx: &OpCtx,
- op: impl Future<Output = Result<R, Error>> + 'static,
-) -> impl Future<Output = OpResult> {
- let get_class = {
- let state = RefCell::borrow(&ctx.state);
- state.tracker.track_async(ctx.id);
- state.get_error_class_fn
- };
-
- op.map(|res| crate::_ops::to_op_result(get_class, res))
-}
-
-#[inline]
-pub fn map_async_op2<R: serde::Serialize + 'static>(
- ctx: &OpCtx,
- op: impl Future<Output = R> + 'static,
-) -> impl Future<Output = OpResult> {
- let state = RefCell::borrow(&ctx.state);
- state.tracker.track_async(ctx.id);
-
- op.map(|res| OpResult::Ok(res.into()))
-}
-
-#[inline]
-pub fn map_async_op3<R: serde::Serialize + 'static>(
- ctx: &OpCtx,
- op: Result<impl Future<Output = Result<R, Error>> + 'static, Error>,
-) -> impl 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) => {
- Either::Left(ready(OpResult::Err(OpError::new(get_class, err))))
- }
- Ok(fut) => {
- Either::Right(fut.map(|res| crate::_ops::to_op_result(get_class, res)))
- }
- }
-}
-
-#[inline]
-pub fn map_async_op4<R: serde::Serialize + 'static>(
- ctx: &OpCtx,
- op: Result<impl Future<Output = R> + 'static, Error>,
-) -> impl 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) => {
- Either::Left(ready(OpResult::Err(OpError::new(get_class, err))))
- }
- Ok(fut) => Either::Right(fut.map(|r| OpResult::Ok(r.into()))),
- }
-}
-
-pub fn queue_async_op<'s>(
- ctx: &OpCtx,
- scope: &'s mut v8::HandleScope,
- deferred: bool,
- promise_id: PromiseId,
- op: impl Future<Output = OpResult> + 'static,
-) -> 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())
- // );
-
- let id = ctx.id;
-
- // TODO(mmastrac): We have to poll every future here because that assumption is baked into a large number
- // of ops. If we can figure out a way around this, we can remove this call to boxed_local and save a malloc per future.
- let mut pinned = op.map(move |res| (promise_id, id, res)).boxed_local();
-
- match pinned.poll_unpin(&mut Context::from_waker(noop_waker_ref())) {
- Poll::Pending => {}
- Poll::Ready(mut res) => {
- if deferred {
- ctx.context_state.borrow_mut().pending_ops.spawn(ready(res));
- return None;
- } else {
- ctx.state.borrow_mut().tracker.track_async_completed(ctx.id);
- return Some(res.2.to_v8(scope).unwrap());
- }
- }
- }
-
- ctx.context_state.borrow_mut().pending_ops.spawn(pinned);
- None
-}
-
-macro_rules! try_number {
- ($n:ident $type:ident $is:ident) => {
- if $n.$is() {
- // SAFETY: v8 handles can be transmuted
- let n: &v8::Uint32 = unsafe { std::mem::transmute($n) };
- return n.value() as _;
- }
- };
-}
-
-pub fn to_u32(number: &v8::Value) -> u32 {
- try_number!(number Uint32 is_uint32);
- try_number!(number Int32 is_int32);
- try_number!(number Number is_number);
- if number.is_big_int() {
- // SAFETY: v8 handles can be transmuted
- let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
- return n.u64_value().0 as _;
- }
- 0
-}
-
-pub fn to_i32(number: &v8::Value) -> i32 {
- try_number!(number Uint32 is_uint32);
- try_number!(number Int32 is_int32);
- try_number!(number Number is_number);
- if number.is_big_int() {
- // SAFETY: v8 handles can be transmuted
- let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
- return n.i64_value().0 as _;
- }
- 0
-}
-
-#[allow(unused)]
-pub fn to_u64(number: &v8::Value) -> u32 {
- try_number!(number Uint32 is_uint32);
- try_number!(number Int32 is_int32);
- try_number!(number Number is_number);
- if number.is_big_int() {
- // SAFETY: v8 handles can be transmuted
- let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
- return n.u64_value().0 as _;
- }
- 0
-}
-
-#[allow(unused)]
-pub fn to_i64(number: &v8::Value) -> i32 {
- try_number!(number Uint32 is_uint32);
- try_number!(number Int32 is_int32);
- try_number!(number Number is_number);
- if number.is_big_int() {
- // SAFETY: v8 handles can be transmuted
- let n: &v8::BigInt = unsafe { std::mem::transmute(number) };
- return n.i64_value().0 as _;
- }
- 0
-}
-
-/// Expands `inbuf` to `outbuf`, assuming that `outbuf` has at least 2x `input_length`.
-#[inline(always)]
-unsafe fn latin1_to_utf8(
- input_length: usize,
- inbuf: *const u8,
- outbuf: *mut u8,
-) -> usize {
- let mut output = 0;
- let mut input = 0;
- while input < input_length {
- let char = *(inbuf.add(input));
- if char < 0x80 {
- *(outbuf.add(output)) = char;
- output += 1;
- } else {
- // Top two bits
- *(outbuf.add(output)) = (char >> 6) | 0b1100_0000;
- // Bottom six bits
- *(outbuf.add(output + 1)) = (char & 0b0011_1111) | 0b1000_0000;
- output += 2;
- }
- input += 1;
- }
- output
-}
-
-/// Converts a [`v8::fast_api::FastApiOneByteString`] to either an owned string, or a borrowed string, depending on whether it fits into the
-/// provided buffer.
-pub fn to_str_ptr<'a, const N: usize>(
- string: &mut v8::fast_api::FastApiOneByteString,
- buffer: &'a mut [MaybeUninit<u8>; N],
-) -> Cow<'a, str> {
- let input_buf = string.as_bytes();
- let input_len = input_buf.len();
- let output_len = buffer.len();
-
- // We know that this string is full of either one or two-byte UTF-8 chars, so if it's < 1/2 of N we
- // can skip the ASCII check and just start copying.
- if input_len < N / 2 {
- debug_assert!(output_len >= input_len * 2);
- let buffer = buffer.as_mut_ptr() as *mut u8;
-
- let written =
- // SAFETY: We checked that buffer is at least 2x the size of input_buf
- unsafe { latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), buffer) };
-
- debug_assert!(written <= output_len);
-
- let slice = std::ptr::slice_from_raw_parts(buffer, written);
- // SAFETY: We know it's valid UTF-8, so make a string
- Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(&*slice) })
- } else {
- // TODO(mmastrac): We could be smarter here about not allocating
- Cow::Owned(to_string_ptr(string))
- }
-}
-
-/// Converts a [`v8::fast_api::FastApiOneByteString`] to an owned string. May over-allocate to avoid
-/// re-allocation.
-pub fn to_string_ptr(
- string: &mut v8::fast_api::FastApiOneByteString,
-) -> String {
- let input_buf = string.as_bytes();
- let capacity = input_buf.len() * 2;
-
- // SAFETY: We're allocating a buffer of 2x the input size, writing valid UTF-8, then turning that into a string
- unsafe {
- // Create an uninitialized buffer of `capacity` bytes. We need to be careful here to avoid
- // accidentally creating a slice of u8 which would be invalid.
- let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
- let out = std::alloc::alloc(layout);
-
- let written = latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), out);
-
- debug_assert!(written <= capacity);
- // We know it's valid UTF-8, so make a string
- String::from_raw_parts(out, written, capacity)
- }
-}
-
-/// Converts a [`v8::String`] to either an owned string, or a borrowed string, depending on whether it fits into the
-/// provided buffer.
-#[inline(always)]
-pub fn to_str<'a, const N: usize>(
- scope: &mut v8::Isolate,
- string: &v8::Value,
- buffer: &'a mut [MaybeUninit<u8>; N],
-) -> Cow<'a, str> {
- if !string.is_string() {
- return Cow::Borrowed("");
- }
-
- // SAFETY: We checked is_string above
- let string: &v8::String = unsafe { std::mem::transmute(string) };
-
- string.to_rust_cow_lossy(scope, buffer)
-}
-
-#[cfg(test)]
-mod tests {
- use crate::error::generic_error;
- use crate::error::AnyError;
- use crate::error::JsError;
- use crate::FastString;
- use crate::JsRuntime;
- use crate::RuntimeOptions;
- use deno_ops::op2;
- use std::borrow::Cow;
- use std::cell::Cell;
-
- crate::extension!(
- testing,
- ops = [
- op_test_fail,
- op_test_add,
- op_test_add_option,
- op_test_result_void_switch,
- op_test_result_void_ok,
- op_test_result_void_err,
- op_test_result_primitive_ok,
- op_test_result_primitive_err,
- op_test_string_owned,
- op_test_string_ref,
- op_test_string_cow,
- op_test_string_roundtrip_char,
- op_test_string_return,
- op_test_string_option_return,
- op_test_string_roundtrip,
- op_test_generics<String>,
- ]
- );
-
- thread_local! {
- static FAIL: Cell<bool> = Cell::new(false)
- }
-
- #[op2(core, fast)]
- pub fn op_test_fail() {
- FAIL.with(|b| b.set(true))
- }
-
- /// Run a test for a single op.
- fn run_test2(repeat: usize, op: &str, test: &str) -> Result<(), AnyError> {
- let mut runtime = JsRuntime::new(RuntimeOptions {
- extensions: vec![testing::init_ops_and_esm()],
- ..Default::default()
- });
- runtime
- .execute_script(
- "",
- FastString::Owned(
- format!(
- r"
- const {{ op_test_fail, {op} }} = Deno.core.ensureFastOps();
- function assert(b) {{
- if (!b) {{
- op_test_fail();
- }}
- }}
- "
- )
- .into(),
- ),
- )
- .unwrap();
- FAIL.with(|b| b.set(false));
- runtime.execute_script(
- "",
- FastString::Owned(
- format!(
- r"
- for (let __index__ = 0; __index__ < {repeat}; __index__++) {{
- {test}
- }}
- "
- )
- .into(),
- ),
- )?;
- if FAIL.with(|b| b.get()) {
- Err(generic_error(format!("{op} test failed ({test})")))
- } else {
- Ok(())
- }
- }
-
- #[tokio::test(flavor = "current_thread")]
- pub async fn test_op_fail() {
- assert!(run_test2(1, "", "assert(false)").is_err());
- }
-
- #[op2(core, fast)]
- pub fn op_test_add(a: u32, b: u32) -> u32 {
- a + b
- }
-
- #[tokio::test(flavor = "current_thread")]
- pub async fn test_op_add() -> Result<(), Box<dyn std::error::Error>> {
- Ok(run_test2(
- 10000,
- "op_test_add",
- "assert(op_test_add(1, 11) == 12)",
- )?)
- }
-
- #[op2(core)]
- pub fn op_test_add_option(a: u32, b: Option<u32>) -> u32 {
- a + b.unwrap_or(100)
- }
-
- #[tokio::test(flavor = "current_thread")]
- pub async fn test_op_add_option() -> Result<(), Box<dyn std::error::Error>> {
- // This isn't fast, so we don't repeat it
- run_test2(
- 1,
- "op_test_add_option",
- "assert(op_test_add_option(1, 11) == 12)",
- )?;
- run_test2(
- 1,
- "op_test_add_option",
- "assert(op_test_add_option(1, null) == 101)",
- )?;
- Ok(())
- }
-
- thread_local! {
- static RETURN_COUNT: Cell<usize> = Cell::new(0);
- }
-
- #[op2(core, fast)]
- pub fn op_test_result_void_switch() -> Result<(), AnyError> {
- let count = RETURN_COUNT.with(|count| {
- let new = count.get() + 1;
- count.set(new);
- new
- });
- if count > 5000 {
- Err(generic_error("failed!!!"))
- } else {
- Ok(())
- }
- }
-
- #[op2(core, fast)]
- pub fn op_test_result_void_err() -> Result<(), AnyError> {
- Err(generic_error("failed!!!"))
- }
-
- #[op2(core, fast)]
- pub fn op_test_result_void_ok() -> Result<(), AnyError> {
- Ok(())
- }
-
- #[tokio::test(flavor = "current_thread")]
- pub async fn test_op_result_void() -> Result<(), Box<dyn std::error::Error>> {
- // Test the non-switching kinds
- run_test2(
- 10000,
- "op_test_result_void_err",
- "try { op_test_result_void_err(); assert(false) } catch (e) {}",
- )?;
- run_test2(10000, "op_test_result_void_ok", "op_test_result_void_ok()")?;
- Ok(())
- }
-
- #[tokio::test(flavor = "current_thread")]
- pub async fn test_op_result_void_switch(
- ) -> Result<(), Box<dyn std::error::Error>> {
- RETURN_COUNT.with(|count| count.set(0));
- let err = run_test2(
- 10000,
- "op_test_result_void_switch",
- "op_test_result_void_switch();",
- )
- .expect_err("Expected this to fail");
- let js_err = err.downcast::<JsError>().unwrap();
- assert_eq!(js_err.message, Some("failed!!!".into()));
- assert_eq!(RETURN_COUNT.with(|count| count.get()), 5001);
- Ok(())
- }
-
- #[op2(core, fast)]
- pub fn op_test_result_primitive_err() -> Result<u32, AnyError> {
- Err(generic_error("failed!!!"))
- }
-
- #[op2(core, fast)]
- pub fn op_test_result_primitive_ok() -> Result<u32, AnyError> {
- Ok(123)
- }
-
- #[tokio::test]
- pub async fn test_op_result_primitive(
- ) -> Result<(), Box<dyn std::error::Error>> {
- run_test2(
- 10000,
- "op_test_result_primitive_err",
- "try { op_test_result_primitive_err(); assert(false) } catch (e) {}",
- )?;
- run_test2(
- 10000,
- "op_test_result_primitive_ok",
- "op_test_result_primitive_ok()",
- )?;
- Ok(())
- }
-
- #[op2(core, fast)]
- pub fn op_test_string_owned(#[string] s: String) -> u32 {
- s.len() as _
- }
-
- #[op2(core, fast)]
- pub fn op_test_string_ref(#[string] s: &str) -> u32 {
- s.len() as _
- }
-
- #[op2(core, fast)]
- pub fn op_test_string_cow(#[string] s: Cow<str>) -> u32 {
- s.len() as _
- }
-
- #[op2(core, fast)]
- pub fn op_test_string_roundtrip_char(#[string] s: Cow<str>) -> u32 {
- s.chars().next().unwrap() as u32
- }
-
- #[tokio::test]
- pub async fn test_op_strings() -> Result<(), Box<dyn std::error::Error>> {
- for op in [
- "op_test_string_owned",
- "op_test_string_cow",
- "op_test_string_ref",
- ] {
- for (len, str) in [
- // ASCII
- (3, "'abc'"),
- // Latin-1 (one byte but two UTF-8 chars)
- (2, "'\\u00a0'"),
- // ASCII
- (10000, "'a'.repeat(10000)"),
- // Latin-1
- (20000, "'\\u00a0'.repeat(10000)"),
- // 4-byte UTF-8 emoji (1F995 = 🦕)
- (40000, "'\\u{1F995}'.repeat(10000)"),
- ] {
- let test = format!("assert({op}({str}) == {len})");
- run_test2(10000, op, &test)?;
- }
- }
-
- // Ensure that we're correctly encoding UTF-8
- run_test2(
- 10000,
- "op_test_string_roundtrip_char",
- "assert(op_test_string_roundtrip_char('\\u00a0') == 0xa0)",
- )?;
- run_test2(
- 10000,
- "op_test_string_roundtrip_char",
- "assert(op_test_string_roundtrip_char('\\u00ff') == 0xff)",
- )?;
- run_test2(
- 10000,
- "op_test_string_roundtrip_char",
- "assert(op_test_string_roundtrip_char('\\u0080') == 0x80)",
- )?;
- run_test2(
- 10000,
- "op_test_string_roundtrip_char",
- "assert(op_test_string_roundtrip_char('\\u0100') == 0x100)",
- )?;
- Ok(())
- }
-
- #[op2(core)]
- #[string]
- pub fn op_test_string_return(
- #[string] a: Cow<str>,
- #[string] b: Cow<str>,
- ) -> String {
- (a + b).to_string()
- }
-
- #[op2(core)]
- #[string]
- pub fn op_test_string_option_return(
- #[string] a: Cow<str>,
- #[string] b: Cow<str>,
- ) -> Option<String> {
- if a == "none" {
- return None;
- }
- Some((a + b).to_string())
- }
-
- #[op2(core)]
- #[string]
- pub fn op_test_string_roundtrip(#[string] s: String) -> String {
- s
- }
-
- #[tokio::test]
- pub async fn test_op_string_returns() -> Result<(), Box<dyn std::error::Error>>
- {
- run_test2(
- 1,
- "op_test_string_return",
- "assert(op_test_string_return('a', 'b') == 'ab')",
- )?;
- run_test2(
- 1,
- "op_test_string_option_return",
- "assert(op_test_string_option_return('a', 'b') == 'ab')",
- )?;
- run_test2(
- 1,
- "op_test_string_option_return",
- "assert(op_test_string_option_return('none', 'b') == null)",
- )?;
- run_test2(
- 1,
- "op_test_string_roundtrip",
- "assert(op_test_string_roundtrip('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')",
- )?;
- Ok(())
- }
-
- // We don't actually test this one -- we just want it to compile
- #[op2(core, fast)]
- pub fn op_test_generics<T: Clone>() {}
-}
diff --git a/core/runtime/serialize_deserialize_test.js b/core/runtime/serialize_deserialize_test.js
deleted file mode 100644
index 70397cdf8..000000000
--- a/core/runtime/serialize_deserialize_test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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
deleted file mode 100644
index e664856c6..000000000
--- a/core/runtime/snapshot_util.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-// 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::jsruntime::BUILTIN_SOURCES;
-use crate::runtime::RuntimeSnapshotOptions;
-use crate::ExtModuleLoaderCb;
-use crate::Extension;
-use crate::ExtensionFileSourceCode;
-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 &*BUILTIN_SOURCES {
- if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
- &source.code
- {
- files_loaded_during_snapshot.push(path.clone());
- }
- }
- for source in js_runtime
- .extensions()
- .iter()
- .flat_map(|e| vec![e.get_esm_sources(), e.get_js_sources()])
- .flatten()
- {
- 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
deleted file mode 100644
index 663645bb1..000000000
--- a/core/runtime/tests.rs
+++ /dev/null
@@ -1,2396 +0,0 @@
-// 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::JsBuffer;
-use crate::*;
-use anyhow::Error;
-use cooked_waker::IntoWaker;
-use cooked_waker::Wake;
-use cooked_waker::WakeRef;
-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::AtomicBool;
-use std::sync::atomic::AtomicI8;
-use std::sync::atomic::AtomicUsize;
-use std::sync::atomic::Ordering;
-use std::sync::Arc;
-use std::task::Context;
-use std::task::Poll;
-use std::time::Duration;
-
-// 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<JsBuffer>,
-) -> 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", 0);
- "#,
- )
- .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(0, 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"
- );
- }
-}
-
-#[derive(Default)]
-struct LoggingWaker {
- woken: AtomicBool,
-}
-
-impl Wake for LoggingWaker {
- fn wake(self) {
- self.woken.store(true, Ordering::SeqCst);
- }
-}
-
-impl WakeRef for LoggingWaker {
- fn wake_by_ref(&self) {
- self.woken.store(true, Ordering::SeqCst);
- }
-}
-
-/// This is a reproduction for a very obscure bug where the Deno runtime locks up we end up polling
-/// an empty JoinSet and attempt to resolve ops after-the-fact. There's a small footgun in the JoinSet
-/// API where polling it while empty returns Ready(None), which means that it never holds on to the
-/// waker. This means that if we aren't testing for this particular return value and don't stash the waker
-/// ourselves for a future async op to eventually queue, we can end up losing the waker entirely and the
-/// op wakes up, notifies tokio, which notifies the JoinSet, which then has nobody to notify )`:.
-#[tokio::test]
-async fn test_wakers_for_async_ops() {
- static STATE: AtomicI8 = AtomicI8::new(0);
-
- #[op]
- async fn op_async_sleep() -> Result<(), Error> {
- STATE.store(1, Ordering::SeqCst);
- tokio::time::sleep(std::time::Duration::from_millis(1)).await;
- STATE.store(2, Ordering::SeqCst);
- Ok(())
- }
-
- STATE.store(0, Ordering::SeqCst);
-
- let logging_waker = Arc::new(LoggingWaker::default());
- let waker = logging_waker.clone().into_waker();
-
- deno_core::extension!(test_ext, ops = [op_async_sleep]);
- let mut runtime = JsRuntime::new(RuntimeOptions {
- extensions: vec![test_ext::init_ops()],
- ..Default::default()
- });
-
- // Drain events until we get to Ready
- loop {
- logging_waker.woken.store(false, Ordering::SeqCst);
- let res = runtime.poll_event_loop(&mut Context::from_waker(&waker), false);
- let ready = matches!(res, Poll::Ready(Ok(())));
- assert!(ready || logging_waker.woken.load(Ordering::SeqCst));
- if ready {
- break;
- }
- }
-
- // Start the AIIFE
- runtime
- .execute_script(
- "",
- FastString::from_static(
- "(async () => { await Deno.core.opAsync('op_async_sleep'); })()",
- ),
- )
- .unwrap();
-
- // Wait for future to finish
- while STATE.load(Ordering::SeqCst) < 2 {
- tokio::time::sleep(Duration::from_millis(1)).await;
- }
-
- // This shouldn't take one minute, but if it does, things are definitely locked up
- for _ in 0..Duration::from_secs(60).as_millis() {
- if logging_waker.woken.load(Ordering::SeqCst) {
- // Success
- return;
- }
- tokio::time::sleep(Duration::from_millis(1)).await;
- }
-
- panic!("The waker was never woken after the future completed");
-}
-
-#[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 WebAssembly 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("Expected 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 RustToV8Buf 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<ToJsBuffer, Error> {
- if !fail {
- Ok(ToJsBuffer::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 RustToV8Buf 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<ToJsBuffer, Error> {
- if !fail {
- Ok(ToJsBuffer::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();
-}