diff --git a/core/modules/map.rs b/core/modules/map.rs index 6b80316b5..08abbe6ab 100644 --- a/core/modules/map.rs +++ b/core/modules/map.rs @@ -4,7 +4,7 @@ use super::IntoModuleCodeString; use super::IntoModuleName; use super::ModuleConcreteError; use super::loaders::ModuleLoadOptions; -use super::module_map_data::ModuleMapSnapshotData; +use super::module_map_data::{ModuleMapSnapshotData, SymbolicModule}; use super::recursive_load::SideModuleKind; use crate::FastStaticString; use crate::JsRuntime; @@ -57,10 +57,11 @@ use super::LazyEsmModuleLoader; use super::RequestedModuleType; use super::module_map_data::ModuleMapData; use deno_core::error::CoreError; -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::cell::Cell; use std::cell::RefCell; use std::collections::HashMap; +use std::hash::Hash; use std::ops::DerefMut; use std::pin::Pin; use std::rc::Rc; @@ -129,7 +130,7 @@ struct DynImportState { } /// A collection of JS modules. -pub(crate) struct ModuleMap { +pub struct ModuleMap { // Handling of futures for loading module sources // TODO(mmastrac): we should not be swapping this loader out pub(crate) loader: RefCell>, @@ -252,14 +253,93 @@ impl ModuleMap { /// Get module id, following all aliases in case of module specifier /// that had been redirected. - pub(crate) fn get_id( + pub fn get_id( &self, - name: &str, + name: &Q, requested_module_type: impl AsRef, - ) -> Option { + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { self.data.borrow().get_id(name, requested_module_type) } + pub fn get( + &self, + name: &Q, + requested_module_type: impl AsRef, + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { + self.data.borrow().get(name, requested_module_type).cloned() + } + + pub fn set( + &self, + name: ModuleName, + symbolic_module: SymbolicModule, + requested_module_type: RequestedModuleType, + ) -> Option { + self + .data + .borrow_mut() + .set(name, symbolic_module, requested_module_type) + } + + // set so import(`name`) will be the namespace of Module with `id` + pub fn set_id( + &self, + name: ModuleName, + id: ModuleId, + requested_module_type: RequestedModuleType, + ) -> Option { + self + .data + .borrow_mut() + .set_id(name, id, requested_module_type) + } + + // drop so now import(`name`) will evaluate the module again + pub fn delete( + &self, + name: &Q, + requested_module_type: impl AsRef, + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { + self + .data + .borrow_mut() + .delete(name, requested_module_type.as_ref()) + } + + // alias so now import(`name`) will have the same result of import(`alias`) + pub fn alias_id( + &self, + name: ModuleName, + alias: ModuleName, + requested_module_type: impl AsRef, + ) -> Option { + self + .data + .borrow_mut() + .alias(name, requested_module_type.as_ref(), alias) + } + pub fn with_map( + &self, + requested_module_type: impl AsRef, + f: impl FnOnce(Option<&HashMap>), + ) { + let data = self.data.borrow(); + let map = data.get_map(requested_module_type.as_ref()); + f(map); + } + pub(crate) fn is_main_module(&self, global: &v8::Global) -> bool { self.data.borrow().is_main_module(global) } @@ -268,15 +348,25 @@ impl ModuleMap { self.data.borrow().main_module_id == Some(id) } - pub(crate) fn get_name_by_module( + pub fn get_name_by_module( &self, global: &v8::Global, ) -> Option { - self.data.borrow().get_name_by_module(global) + // todo(CyanChanges): do not clone here + self + .data + .borrow() + .get_name_by_module(global) + .map(|name| name.as_str().to_owned()) } - pub(crate) fn get_name_by_id(&self, id: ModuleId) -> Option { - self.data.borrow().get_name_by_id(id) + pub fn get_name_by_id(&self, id: ModuleId) -> Option { + // todo(CyanChanges): do not clone here + self + .data + .borrow() + .get_name_by_id(id) + .map(|name| name.as_str().to_owned()) } pub(crate) fn get_type_by_module( @@ -315,7 +405,7 @@ impl ModuleMap { } #[cfg(test)] - pub fn assert_module_map(&self, modules: &Vec) { + pub(crate) fn assert_module_map(&self, modules: &Vec) { self.data.borrow().assert_module_map(modules); } @@ -612,7 +702,7 @@ impl ModuleMap { if main { let data = self.data.borrow(); if let Some(main_module) = data.main_module_id { - let main_name = self.data.borrow().get_name_by_id(main_module).unwrap(); + let main_name = data.get_name_by_id(main_module).unwrap(); return Err(ModuleError::Concrete( ModuleConcreteError::MainModuleAlreadyExists { main_module: main_name.to_string(), @@ -1001,6 +1091,7 @@ impl ModuleMap { .data .borrow() .get_name_by_module(&referrer_global) + .map(|name| name.as_str().to_string()) .expect("ModuleInfo not found"); let specifier_str = specifier.to_rust_string_lossy(scope); @@ -1114,7 +1205,7 @@ impl ModuleMap { None } - pub(crate) fn get_requested_modules( + pub fn get_requested_modules( &self, id: ModuleId, ) -> Option> { diff --git a/core/modules/mod.rs b/core/modules/mod.rs index fe8b08e90..b26a284b0 100644 --- a/core/modules/mod.rs +++ b/core/modules/mod.rs @@ -676,7 +676,7 @@ impl std::fmt::Display for RequestedModuleType { /// import assertions explicitly constrains an import to JSON, in /// which case this will have a `RequestedModuleType::Json`. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub(crate) struct ModuleReference { +pub struct ModuleReference { pub specifier: ModuleSpecifier, pub requested_module_type: RequestedModuleType, } @@ -686,7 +686,7 @@ pub(crate) struct ModuleReference { /// import assertions explicitly constrains an import to JSON, in /// which case this will have a `RequestedModuleType::Json`. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub(crate) struct ModuleRequest { +pub struct ModuleRequest { pub reference: ModuleReference, /// None if this is a root request. pub referrer_source_offset: Option, diff --git a/core/modules/module_map_data.rs b/core/modules/module_map_data.rs index f28be1d6e..c30d52f3b 100644 --- a/core/modules/module_map_data.rs +++ b/core/modules/module_map_data.rs @@ -14,13 +14,16 @@ use crate::runtime::SnapshotLoadDataStore; use crate::runtime::SnapshotStoreDataStore; use serde::Deserialize; use serde::Serialize; +use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; use std::rc::Rc; /// A symbolic module entity. #[derive(Debug, PartialEq, Serialize, Deserialize)] -pub(crate) enum SymbolicModule { +pub enum SymbolicModule { /// This module is an alias to another module. /// This is useful such that multiple names could point to /// the same underlying module (particularly due to redirects). @@ -29,6 +32,18 @@ pub(crate) enum SymbolicModule { Mod(ModuleId), } +impl Clone for SymbolicModule { + fn clone(&self) -> Self { + match self { + SymbolicModule::Alias(name) => match name.try_clone() { + Some(name) => SymbolicModule::Alias(name), + None => SymbolicModule::Alias(ModuleName::from(name.to_string())), + }, + SymbolicModule::Mod(module_id) => SymbolicModule::Mod(*module_id), + } + } +} + /// Map of [`ModuleName`] and [`RequestedModuleType`] to a data field. struct ModuleNameTypeMap { submaps: Vec>, @@ -57,8 +72,8 @@ impl ModuleNameTypeMap { pub fn get(&self, ty: &RequestedModuleType, name: &Q) -> Option<&T> where - ModuleName: std::borrow::Borrow, - Q: std::cmp::Eq + std::hash::Hash + std::fmt::Debug + ?Sized, + FastString: Borrow, + Q: Eq + Hash + ?Sized, { let index = self.map_index(ty)?; let map = self.submaps.get(index)?; @@ -68,9 +83,9 @@ impl ModuleNameTypeMap { pub fn insert( &mut self, module_type: &RequestedModuleType, - name: FastString, + name: ModuleName, module: T, - ) { + ) -> Option { let index = match self.map_index(module_type) { Some(index) => index, None => { @@ -81,17 +96,47 @@ impl ModuleNameTypeMap { } }; - if self - .submaps - .get_mut(index) - .unwrap() - .insert(name, module) - .is_none() - { - self.len += 1; + match self.submaps.get_mut(index).unwrap().insert(name, module) { + None => { + self.len += 1; + None + } + Some(module) => Some(module), } } + pub fn delete( + &mut self, + module_type: &RequestedModuleType, + name: &Q, + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { + let index = self.map_index(module_type)?; + + match self.submaps.get_mut(index).unwrap().remove(name) { + Some(module) => { + self.len -= 1; + Some(module) + } + None => None, + } + } + + pub fn get_map( + &self, + module_type: &RequestedModuleType, + ) -> Option<&HashMap> { + let index = match self.map_index(module_type) { + Some(index) => index, + None => todo!(), + }; + + self.submaps.get(index) + } + /// Rather than providing an iterator, we provide a drain method. This is mainly because Rust /// doesn't have generators. pub fn drain( @@ -128,7 +173,7 @@ pub(crate) type SyntheticModuleExportsStore = #[derive(Default)] pub(crate) struct ModuleMapData { /// Inverted index from module to index in `info`. - pub(crate) handles_inverted: HashMap, usize>, + pub(crate) handles_inverted: HashMap, ModuleId>, /// The handles we have loaded so far, corresponding with the [`ModuleInfo`] in `info`. pub(crate) handles: Vec>, pub(crate) main_module_callbacks: Vec>, @@ -193,11 +238,15 @@ impl ModuleMapData { /// Get module id, following all aliases in case of module specifier /// that had been redirected. - pub fn get_id( + pub fn get_id( &self, - name: &str, + name: &Q, requested_module_type: impl AsRef, - ) -> Option { + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { let map = &self.by_name; let first_symbolic_module = map.get(requested_module_type.as_ref(), name)?; @@ -207,7 +256,7 @@ impl ModuleMapData { }; loop { let symbolic_module = - map.get(requested_module_type.as_ref(), mod_name)?; + map.get(requested_module_type.as_ref(), mod_name.borrow())?; match symbolic_module { SymbolicModule::Alias(target) => { debug_assert!(mod_name != target); @@ -218,42 +267,100 @@ impl ModuleMapData { } } - pub(crate) fn alias( + pub fn get( + &self, + name: &Q, + requested_module_type: impl AsRef, + ) -> Option<&SymbolicModule> + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { + let map = &self.by_name; + map.get(requested_module_type.as_ref(), name) + } + + pub fn set( &mut self, - name: FastString, + name: ModuleName, + symbolic_module: SymbolicModule, + requested_module_type: impl AsRef, + ) -> Option { + let map = &mut self.by_name; + map.insert(requested_module_type.as_ref(), name, symbolic_module) + } + + pub fn set_id( + &mut self, + name: ModuleName, + id: ModuleId, + requested_module_type: impl AsRef, + ) -> Option { + let map = &mut self.by_name; + map.insert( + requested_module_type.as_ref(), + name, + SymbolicModule::Mod(id), + ) + } + + pub fn delete( + &mut self, + name: &Q, + requested_module_type: impl AsRef, + ) -> Option + where + ModuleName: Borrow, + Q: Eq + Hash + ?Sized, + { + let map = &mut self.by_name; + map.delete(requested_module_type.as_ref(), name) + } + + pub fn alias( + &mut self, + name: ModuleName, requested_module_type: &RequestedModuleType, - target: FastString, - ) { + target: ModuleName, + ) -> Option { debug_assert_ne!(name, target); self.by_name.insert( requested_module_type, name, SymbolicModule::Alias(target), - ); + ) + } + + pub fn get_map( + &self, + requested_module_type: impl AsRef, + ) -> Option<&HashMap> { + self.by_name.get_map(requested_module_type.as_ref()) } #[cfg(test)] - pub(crate) fn is_alias( + pub(crate) fn is_alias( &self, - name: &str, + name: &Q, requested_module_type: impl AsRef, - ) -> bool { + ) -> bool + where + ModuleName: Borrow, + Q: Eq + Hash + Debug + ?Sized, + { let map = &self.by_name; let entry = map.get(requested_module_type.as_ref(), name); matches!(entry, Some(SymbolicModule::Alias(_))) } - pub(crate) fn get_handle( - &self, - id: ModuleId, - ) -> Option> { + pub fn get_handle(&self, id: ModuleId) -> Option> { self.handles.get(id).cloned() } - pub(crate) fn get_name_by_module( + pub fn get_name_by_module( &self, global: &v8::Global, - ) -> Option { + ) -> Option<&ModuleName> { match self.handles_inverted.get(global) { Some(id) => self.get_name_by_id(*id), _ => None, @@ -280,9 +387,8 @@ impl ModuleMapData { .unwrap_or_default() } - pub(crate) fn get_name_by_id(&self, id: ModuleId) -> Option { - // TODO(mmastrac): Don't clone - self.info.get(id).map(|info| info.name.as_str().to_owned()) + pub fn get_name_by_id(&self, id: ModuleId) -> Option<&ModuleName> { + self.info.get(id).map(|info| &info.name) } pub fn serialize_for_snapshotting( @@ -342,7 +448,7 @@ impl ModuleMapData { } for (name, module_type, module) in data.by_name { - self.by_name.insert(&module_type, name, module) + self.by_name.insert(&module_type, name, module); } } diff --git a/core/modules/tests.rs b/core/modules/tests.rs index 439c4011d..a931838c0 100644 --- a/core/modules/tests.rs +++ b/core/modules/tests.rs @@ -2,7 +2,6 @@ #![allow(clippy::print_stderr)] -use crate::FastString; use crate::ModuleCodeString; use crate::ModuleSource; use crate::ModuleSpecifier; @@ -30,6 +29,7 @@ use crate::resolve_import; use crate::resolve_url; use crate::runtime::JsRuntime; use crate::runtime::JsRuntimeForSnapshot; +use crate::{FastString, ModuleName}; use deno_error::JsErrorBox; use deno_error::JsErrorClass; use deno_ops::op2; @@ -53,6 +53,7 @@ use url::Url; // deno_ops macros generate code assuming deno_core in scope. use crate::deno_core; +use crate::modules::module_map_data::SymbolicModule; #[derive(Default)] struct MockLoader { @@ -362,6 +363,10 @@ fn test_recursive_load() { let module_map_rc = runtime.module_map(); let modules = module_map_rc; + assert_eq!( + modules.get("file:///a.js", RequestedModuleType::None), + Some(SymbolicModule::Mod(a_id)) + ); assert_eq!( modules.get_id("file:///a.js", RequestedModuleType::None), Some(a_id) @@ -2461,3 +2466,69 @@ throwError(); err_str ); } + +#[test] +fn test_map() { + let loader = MockLoader::new(); + let mut runtime = JsRuntime::new(RuntimeOptions { + module_loader: Some(loader), + ..Default::default() + }); + let modules = runtime.module_map(); + + let fut = async move { + let spec = resolve_url("file:///a.js").unwrap(); + const A_NAME: ModuleName = ModuleName::from_static("file:///a.js"); + let a_id = runtime.load_main_es_module(&spec).await.unwrap(); + modules.alias_id( + ModuleName::from_static("#alias0"), + A_NAME, + RequestedModuleType::None, + ); + modules.set( + ModuleName::from_static("#alias1"), + SymbolicModule::Alias(A_NAME), + RequestedModuleType::None, + ); + assert_eq!( + modules.get( + &ModuleName::from_static("#alias0"), + RequestedModuleType::None + ), + modules.get( + &ModuleName::from_static("#alias1"), + RequestedModuleType::None + ), + ); + modules.set_id( + ModuleName::from_static("#id0"), + a_id, + RequestedModuleType::None, + ); + modules.set( + ModuleName::from_static("#id1"), + SymbolicModule::Mod(a_id), + RequestedModuleType::None, + ); + assert_eq!( + modules.get(&ModuleName::from_static("#id0"), RequestedModuleType::None), + modules.get(&ModuleName::from_static("#id1"), RequestedModuleType::None), + ); + assert_eq!( + modules.get("#alias0", RequestedModuleType::None), + Some(SymbolicModule::Alias(A_NAME)) + ); + assert_eq!( + modules.get("#id0", RequestedModuleType::None), + Some(SymbolicModule::Mod(a_id)) + ); + modules.delete( + &ModuleName::from_static("#alias0"), + RequestedModuleType::None, + ); + assert_eq!(modules.get("#alias0", RequestedModuleType::None), None); + } + .boxed_local(); + + futures::executor::block_on(fut); +} diff --git a/core/runtime/jsruntime.rs b/core/runtime/jsruntime.rs index 4b63b163e..a76531f3c 100644 --- a/core/runtime/jsruntime.rs +++ b/core/runtime/jsruntime.rs @@ -2152,6 +2152,15 @@ impl JsRuntime { Poll::Pending } + + /// # SAFETY + /// try to acquire [`ModuleMap`] with HandleScope from a Isolate + /// that is not initialized with `deno_core::JsRuntime` will be undefined behavior + /// + /// manipulating internal module may cause problems + pub unsafe fn module_map_from(scope: &mut v8::PinScope) -> Rc { + JsRealm::module_map_from(scope) + } } fn find_and_report_stalled_level_await_in_any_realm(