diff --git a/core/error.rs b/core/error.rs index 07dc98a22f..55fdcaa7ce 100644 --- a/core/error.rs +++ b/core/error.rs @@ -695,6 +695,12 @@ pub(crate) fn exception_to_err_result( Err(js_error.into()) } +pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef) { + let message = v8::String::new(scope, message.as_ref()).unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/lib.rs b/core/lib.rs index 82cd1dd435..ddc4d6d910 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -127,6 +127,7 @@ pub fn v8_version() -> &'static str { /// An internal module re-exporting functions used by the #[op] (`deno_ops`) macro #[doc(hidden)] pub mod _ops { + pub use super::error::throw_type_error; pub use super::error_codes::get_error_code; pub use super::ops::to_op_result; pub use super::ops::OpCtx; @@ -137,7 +138,6 @@ pub mod _ops { pub use super::runtime::ops::map_async_op4; pub use super::runtime::ops::queue_async_op; pub use super::runtime::ops::queue_fast_async_op; - pub use super::runtime::throw_type_error; pub use super::runtime::V8_WRAPPER_OBJECT_INDEX; pub use super::runtime::V8_WRAPPER_TYPE_INDEX; } diff --git a/core/modules/map.rs b/core/modules/map.rs index 828d5888b7..837099d2f8 100644 --- a/core/modules/map.rs +++ b/core/modules/map.rs @@ -1,5 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::error::exception_to_err_result; use crate::error::generic_error; +use crate::error::throw_type_error; use crate::fast_string::FastString; use crate::modules::get_asserted_module_type_from_assertions; use crate::modules::parse_import_assertions; @@ -554,6 +556,105 @@ impl ModuleMap { Ok(id) } + pub(crate) fn instantiate_module( + &mut self, + scope: &mut v8::HandleScope, + id: ModuleId, + ) -> Result<(), v8::Global> { + let tc_scope = &mut v8::TryCatch::new(scope); + + let module = self + .get_handle(id) + .map(|handle| v8::Local::new(tc_scope, handle)) + .expect("ModuleInfo not found"); + + if module.get_status() == v8::ModuleStatus::Errored { + return Err(v8::Global::new(tc_scope, module.get_exception())); + } + + tc_scope.set_slot(self as *const _); + let instantiate_result = + module.instantiate_module(tc_scope, Self::module_resolve_callback); + tc_scope.remove_slot::<*const Self>(); + if instantiate_result.is_none() { + let exception = tc_scope.exception().unwrap(); + return Err(v8::Global::new(tc_scope, exception)); + } + + Ok(()) + } + + /// Called by V8 during `JsRuntime::instantiate_module`. This is only used internally, so we use the Isolate's annex + /// to propagate a &Self. + fn module_resolve_callback<'s>( + context: v8::Local<'s, v8::Context>, + specifier: v8::Local<'s, v8::String>, + import_assertions: v8::Local<'s, v8::FixedArray>, + referrer: v8::Local<'s, v8::Module>, + ) -> Option> { + // SAFETY: `CallbackScope` can be safely constructed from `Local` + let scope = &mut unsafe { v8::CallbackScope::new(context) }; + + let module_map = + // SAFETY: We retrieve the pointer from the slot, having just set it a few stack frames up + unsafe { scope.get_slot::<*const Self>().unwrap().as_ref().unwrap() }; + + let referrer_global = v8::Global::new(scope, referrer); + + let referrer_info = module_map + .get_info(&referrer_global) + .expect("ModuleInfo not found"); + let referrer_name = referrer_info.name.as_str(); + + let specifier_str = specifier.to_rust_string_lossy(scope); + + let assertions = parse_import_assertions( + scope, + import_assertions, + ImportAssertionsKind::StaticImport, + ); + let maybe_module = module_map.resolve_callback( + scope, + &specifier_str, + referrer_name, + assertions, + ); + if let Some(module) = maybe_module { + return Some(module); + } + + let msg = format!( + r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""# + ); + throw_type_error(scope, msg); + None + } + + /// Called by `module_resolve_callback` during module instantiation. + fn resolve_callback<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + specifier: &str, + referrer: &str, + import_assertions: HashMap, + ) -> Option> { + let resolved_specifier = self + .loader + .resolve(specifier, referrer, ResolutionKind::Import) + .expect("Module should have been already resolved"); + + let module_type = + get_asserted_module_type_from_assertions(&import_assertions); + + if let Some(id) = self.get_id(resolved_specifier.as_str(), module_type) { + if let Some(handle) = self.get_handle(id) { + return Some(v8::Local::new(scope, handle)); + } + } + + None + } + pub(crate) fn clear(&mut self) { *self = Self::new(self.loader.clone()) } @@ -753,29 +854,102 @@ impl ModuleMap { && self.pending_dynamic_imports.is_empty()) } - /// Called by `module_resolve_callback` during module instantiation. - pub(crate) fn resolve_callback<'s>( + /// 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( &self, - scope: &mut v8::HandleScope<'s>, - specifier: &str, - referrer: &str, - import_assertions: HashMap, - ) -> Option> { - let resolved_specifier = self - .loader - .resolve(specifier, referrer, ResolutionKind::Import) - .expect("Module should have been already resolved"); + scope: &mut v8::HandleScope, + module_id: ModuleId, + ) -> Result, Error> { + let module_handle = + self.get_handle(module_id).expect("ModuleInfo not found"); - let module_type = - get_asserted_module_type_from_assertions(&import_assertions); + let module = module_handle.open(scope); - if let Some(id) = self.get_id(resolved_specifier.as_str(), module_type) { - if let Some(handle) = self.get_handle(id) { - return Some(v8::Local::new(scope, handle)); + if module.get_status() == v8::ModuleStatus::Errored { + let exception = module.get_exception(); + return exception_to_err_result(scope, exception, false); + } + + assert!(matches!( + module.get_status(), + v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated + )); + + let module_namespace: v8::Local = + v8::Local::try_from(module.get_module_namespace()) + .map_err(|err: v8::DataError| generic_error(err.to_string()))?; + + Ok(v8::Global::new(scope, module_namespace)) + } + + /// Clear the module map, meant to be used after initializing extensions. + /// Optionally pass a list of exceptions `(old_name, new_name)` representing + /// specifiers which will be renamed and preserved in the module map. + pub fn clear_module_map( + &mut self, + exceptions: impl Iterator, + ) { + let handles = exceptions + .map(|(old_name, new_name)| { + (self.get_handle_by_name(old_name).unwrap(), new_name) + }) + .collect::>(); + self.clear(); + for (handle, new_name) in handles { + self.inject_handle( + ModuleName::from_static(new_name), + ModuleType::JavaScript, + handle, + ) + } + } + + fn get_stalled_top_level_await_message_for_module( + &self, + scope: &mut v8::HandleScope, + module_id: ModuleId, + ) -> Vec> { + let module_handle = self.handles.get(module_id).unwrap(); + + let module = v8::Local::new(scope, module_handle); + let stalled = module.get_stalled_top_level_await_message(scope); + let mut messages = vec![]; + for (_, message) in stalled { + messages.push(v8::Global::new(scope, message)); + } + messages + } + + pub(crate) fn find_stalled_top_level_await( + &self, + scope: &mut v8::HandleScope, + ) -> Vec> { + // First check if that's root module + let root_module_id = + self.info.iter().filter(|m| m.main).map(|m| m.id).next(); + + if let Some(root_module_id) = root_module_id { + let messages = self + .get_stalled_top_level_await_message_for_module(scope, root_module_id); + if !messages.is_empty() { + return messages; } } - None + // It wasn't a top module, so iterate over all modules and try to find + // any with stalled top level await + for module_id in 0..self.handles.len() { + let messages = + self.get_stalled_top_level_await_message_for_module(scope, module_id); + if !messages.is_empty() { + return messages; + } + } + + unreachable!() } } diff --git a/core/runtime/bindings.rs b/core/runtime/bindings.rs index 4cc27592f0..2eb804e736 100644 --- a/core/runtime/bindings.rs +++ b/core/runtime/bindings.rs @@ -7,6 +7,7 @@ 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; @@ -542,57 +543,3 @@ fn call_console( inspector_console_method.call(scope, receiver.into(), &call_args); deno_console_method.call(scope, receiver.into(), &call_args); } - -/// Called by V8 during `JsRuntime::instantiate_module`. -/// -/// This function borrows `ModuleMap` from the isolate slot, -/// so it is crucial to ensure there are no existing borrows -/// of `ModuleMap` when `JsRuntime::instantiate_module` is called. -pub fn module_resolve_callback<'s>( - context: v8::Local<'s, v8::Context>, - specifier: v8::Local<'s, v8::String>, - import_assertions: v8::Local<'s, v8::FixedArray>, - referrer: v8::Local<'s, v8::Module>, -) -> Option> { - // SAFETY: `CallbackScope` can be safely constructed from `Local` - let scope = &mut unsafe { v8::CallbackScope::new(context) }; - - let module_map_rc = JsRuntime::module_map_from(scope); - let module_map = module_map_rc.borrow(); - - let referrer_global = v8::Global::new(scope, referrer); - - let referrer_info = module_map - .get_info(&referrer_global) - .expect("ModuleInfo not found"); - let referrer_name = referrer_info.name.as_str(); - - let specifier_str = specifier.to_rust_string_lossy(scope); - - let assertions = parse_import_assertions( - scope, - import_assertions, - ImportAssertionsKind::StaticImport, - ); - let maybe_module = module_map.resolve_callback( - scope, - &specifier_str, - referrer_name, - assertions, - ); - if let Some(module) = maybe_module { - return Some(module); - } - - let msg = format!( - r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""# - ); - throw_type_error(scope, msg); - None -} - -pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef) { - let message = v8::String::new(scope, message.as_ref()).unwrap(); - let exception = v8::Exception::type_error(scope, message); - scope.throw_exception(exception); -} diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs index 3b41a90f19..2e473e7c90 100644 --- a/core/runtime/jsruntime.rs +++ b/core/runtime/jsruntime.rs @@ -21,14 +21,12 @@ use crate::modules::ModuleId; use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; use crate::modules::ModuleMap; -use crate::modules::ModuleName; use crate::ops::*; use crate::runtime::ContextState; use crate::runtime::JsRealm; use crate::source_map::SourceMapCache; use crate::source_map::SourceMapGetter; use crate::Extension; -use crate::ModuleType; use crate::NoopModuleLoader; use crate::OpMiddlewareFn; use crate::OpResult; @@ -391,6 +389,11 @@ pub struct RuntimeOptions { /// JavaScript sources in the extensions. pub extensions: Vec, + /// 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>, + /// V8 snapshot that should be loaded on startup. pub startup_snapshot: Option, @@ -694,6 +697,15 @@ impl JsRuntime { 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 } @@ -1143,31 +1155,11 @@ impl JsRuntime { &mut self, module_id: ModuleId, ) -> Result, Error> { - let module_handle = self + self .module_map + .clone() .borrow() - .get_handle(module_id) - .expect("ModuleInfo not found"); - - let scope = &mut self.handle_scope(); - - let module = module_handle.open(scope); - - if module.get_status() == v8::ModuleStatus::Errored { - let exception = module.get_exception(); - return exception_to_err_result(scope, exception, false); - } - - assert!(matches!( - module.get_status(), - v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated - )); - - let module_namespace: v8::Local = - v8::Local::try_from(module.get_module_namespace()) - .map_err(|err: v8::DataError| generic_error(err.to_string()))?; - - Ok(v8::Global::new(scope, module_namespace)) + .get_module_namespace(&mut self.handle_scope(), module_id) } /// Registers a callback on the isolate when the memory limits are approached. @@ -1323,6 +1315,7 @@ impl JsRuntime { 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()" @@ -1426,7 +1419,7 @@ impl JsRuntime { // pass, will be polled again } else { let scope = &mut self.handle_scope(); - let messages = find_stalled_top_level_await(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 @@ -1448,7 +1441,7 @@ impl JsRuntime { } else if self.inner.state.borrow().dyn_module_evaluate_idle_counter >= 1 { let scope = &mut self.handle_scope(); - let messages = find_stalled_top_level_await(scope); + 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 @@ -1537,58 +1530,6 @@ impl JsRuntimeForSnapshot { } } -fn get_stalled_top_level_await_message_for_module( - scope: &mut v8::HandleScope, - module_id: ModuleId, -) -> Vec> { - let module_map = JsRuntime::module_map_from(scope); - let module_map = module_map.borrow(); - let module_handle = module_map.handles.get(module_id).unwrap(); - - let module = v8::Local::new(scope, module_handle); - let stalled = module.get_stalled_top_level_await_message(scope); - let mut messages = vec![]; - for (_, message) in stalled { - messages.push(v8::Global::new(scope, message)); - } - messages -} - -fn find_stalled_top_level_await( - scope: &mut v8::HandleScope, -) -> Vec> { - let module_map = JsRuntime::module_map_from(scope); - let module_map = module_map.borrow(); - - // First check if that's root module - let root_module_id = module_map - .info - .iter() - .filter(|m| m.main) - .map(|m| m.id) - .next(); - - if let Some(root_module_id) = root_module_id { - let messages = - get_stalled_top_level_await_message_for_module(scope, root_module_id); - if !messages.is_empty() { - return messages; - } - } - - // It wasn't a top module, so iterate over all modules and try to find - // any with stalled top level await - for module_id in 0..module_map.handles.len() { - let messages = - get_stalled_top_level_await_message_for_module(scope, module_id); - if !messages.is_empty() { - return messages; - } - } - - unreachable!() -} - #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) struct EventLoopPendingState { has_pending_refed_ops: bool, @@ -1666,32 +1607,11 @@ impl JsRuntime { &mut self, id: ModuleId, ) -> Result<(), v8::Global> { - let module_map_rc = self.module_map.clone(); - let scope = &mut self.handle_scope(); - let tc_scope = &mut v8::TryCatch::new(scope); - - let module = module_map_rc - .borrow() - .get_handle(id) - .map(|handle| v8::Local::new(tc_scope, handle)) - .expect("ModuleInfo not found"); - - if module.get_status() == v8::ModuleStatus::Errored { - return Err(v8::Global::new(tc_scope, module.get_exception())); - } - - // IMPORTANT: No borrows to `ModuleMap` can be held at this point because - // `module_resolve_callback` will be calling into `ModuleMap` from within - // the isolate. - let instantiate_result = - module.instantiate_module(tc_scope, bindings::module_resolve_callback); - - if instantiate_result.is_none() { - let exception = tc_scope.exception().unwrap(); - return Err(v8::Global::new(tc_scope, exception)); - } - - Ok(()) + self + .module_map + .clone() + .borrow_mut() + .instantiate_module(&mut self.handle_scope(), id) } fn dynamic_import_module_evaluate( @@ -1903,29 +1823,6 @@ impl JsRuntime { receiver } - /// Clear the module map, meant to be used after initializing extensions. - /// Optionally pass a list of exceptions `(old_name, new_name)` representing - /// specifiers which will be renamed and preserved in the module map. - pub fn clear_module_map( - &self, - exceptions: impl Iterator, - ) { - let mut module_map = self.module_map.borrow_mut(); - let handles = exceptions - .map(|(old_name, new_name)| { - (module_map.get_handle_by_name(old_name).unwrap(), new_name) - }) - .collect::>(); - module_map.clear(); - for (handle, new_name) in handles { - module_map.inject_handle( - ModuleName::from_static(new_name), - ModuleType::JavaScript, - handle, - ) - } - } - fn dynamic_import_reject( &mut self, id: ModuleLoadId, diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs index 2bd3ea9feb..aa546b8c78 100644 --- a/core/runtime/mod.rs +++ b/core/runtime/mod.rs @@ -32,4 +32,3 @@ pub use snapshot_util::FilterFn; pub(crate) use snapshot_util::SnapshottedData; pub use bindings::script_origin; -pub use bindings::throw_type_error; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 36f9718b51..2dde5a3696 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -4,7 +4,6 @@ use crate::inspector_server::InspectorServer; use crate::ops; use crate::permissions::PermissionsContainer; use crate::tokio_util::create_and_run_current_thread; -use crate::worker::init_runtime_module_map; use crate::worker::FormatJsErrorFn; use crate::BootstrapOptions; use deno_broadcast_channel::InMemoryBroadcastChannel; @@ -485,6 +484,15 @@ impl WebWorker { let startup_snapshot = options.startup_snapshot .expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature."); + // Clear extension modules from the module map, except preserve `ext:deno_node` + // modules as `node:` specifiers. + let rename_modules = Some( + deno_node::SUPPORTED_BUILTIN_NODE_MODULES + .iter() + .map(|p| (p.ext_specifier, p.specifier)) + .collect(), + ); + let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(options.module_loader.clone()), startup_snapshot: Some(startup_snapshot), @@ -494,9 +502,9 @@ impl WebWorker { compiled_wasm_module_store: options.compiled_wasm_module_store.clone(), extensions, inspector: options.maybe_inspector_server.is_some(), + rename_modules, ..Default::default() }); - init_runtime_module_map(&mut js_runtime); if let Some(server) = options.maybe_inspector_server.clone() { server.register_inspector( diff --git a/runtime/worker.rs b/runtime/worker.rs index 10375818d0..0293c332ae 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -58,16 +58,6 @@ impl ExitCode { } } -/// Clear extension modules from the module map, except preserve `ext:deno_node` -/// modules as `node:` specifiers. -pub fn init_runtime_module_map(js_runtime: &mut JsRuntime) { - js_runtime.clear_module_map( - deno_node::SUPPORTED_BUILTIN_NODE_MODULES - .iter() - .map(|p| (p.ext_specifier, p.specifier)), - ); -} - /// This worker is created and used by almost all /// subcommands in Deno executable. /// @@ -323,6 +313,15 @@ impl MainWorker { let startup_snapshot = options.startup_snapshot .expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature."); + // Clear extension modules from the module map, except preserve `ext:deno_node` + // modules as `node:` specifiers. + let rename_modules = Some( + deno_node::SUPPORTED_BUILTIN_NODE_MODULES + .iter() + .map(|p| (p.ext_specifier, p.specifier)) + .collect(), + ); + let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(options.module_loader.clone()), startup_snapshot: Some(startup_snapshot), @@ -332,11 +331,11 @@ impl MainWorker { shared_array_buffer_store: options.shared_array_buffer_store.clone(), compiled_wasm_module_store: options.compiled_wasm_module_store.clone(), extensions, + rename_modules, inspector: options.maybe_inspector_server.is_some(), is_main: true, ..Default::default() }); - init_runtime_module_map(&mut js_runtime); if let Some(server) = options.maybe_inspector_server.clone() { server.register_inspector(