diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/bindings.rs | 8 | ||||
-rw-r--r-- | core/modules.rs | 91 | ||||
-rw-r--r-- | core/runtime.rs | 291 |
3 files changed, 176 insertions, 214 deletions
diff --git a/core/bindings.rs b/core/bindings.rs index 157b58a9d..7fb8aac70 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -246,7 +246,7 @@ pub extern "C" fn host_initialize_import_meta_object_callback( let module_global = v8::Global::new(scope, module); let info = state - .modules + .module_map .get_info(&module_global) .expect("Module not found"); @@ -794,7 +794,7 @@ pub fn module_resolve_callback<'s>( let referrer_global = v8::Global::new(scope, referrer); let referrer_info = state - .modules + .module_map .get_info(&referrer_global) .expect("ModuleInfo not found"); let referrer_name = referrer_info.name.to_string(); @@ -811,8 +811,8 @@ pub fn module_resolve_callback<'s>( ) .expect("Module should have been already resolved"); - if let Some(id) = state.modules.get_id(resolved_specifier.as_str()) { - if let Some(handle) = state.modules.get_handle(id) { + if let Some(id) = state.module_map.get_id(resolved_specifier.as_str()) { + if let Some(handle) = state.module_map.get_handle(id) { return Some(v8::Local::new(scope, handle)); } } diff --git a/core/modules.rs b/core/modules.rs index aefb3e491..ea772a8b2 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -398,76 +398,40 @@ enum SymbolicModule { Mod(ModuleId), } -#[derive(Default)] -/// Alias-able module name map -struct ModuleNameMap { - inner: HashMap<String, SymbolicModule>, -} - -impl ModuleNameMap { - pub fn new() -> Self { - ModuleNameMap { - inner: HashMap::new(), - } - } - - /// Get the id of a module. - /// If this module is internally represented as an alias, - /// follow the alias chain to get the final module id. - pub fn get(&self, name: &str) -> Option<ModuleId> { - let mut mod_name = name; - loop { - let symbolic_module = self.inner.get(mod_name)?; - match symbolic_module { - SymbolicModule::Alias(target) => { - mod_name = target; - } - SymbolicModule::Mod(mod_id) => return Some(*mod_id), - } - } - } - - /// Insert a name associated module id. - pub fn insert(&mut self, name: String, id: ModuleId) { - self.inner.insert(name, SymbolicModule::Mod(id)); - } - - /// Create an alias to another module. - pub fn alias(&mut self, name: String, target: String) { - self.inner.insert(name, SymbolicModule::Alias(target)); - } - - /// Check if a name is an alias to another module. - #[cfg(test)] - pub fn is_alias(&self, name: &str) -> bool { - let cond = self.inner.get(name); - matches!(cond, Some(SymbolicModule::Alias(_))) - } -} - /// A collection of JS modules. #[derive(Default)] -pub struct Modules { +pub struct ModuleMap { ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>, handles_by_id: HashMap<ModuleId, v8::Global<v8::Module>>, info: HashMap<ModuleId, ModuleInfo>, - by_name: ModuleNameMap, + by_name: HashMap<String, SymbolicModule>, next_module_id: ModuleId, } -impl Modules { - pub fn new() -> Modules { +impl ModuleMap { + pub fn new() -> ModuleMap { Self { handles_by_id: HashMap::new(), ids_by_handle: HashMap::new(), info: HashMap::new(), - by_name: ModuleNameMap::new(), + by_name: HashMap::new(), next_module_id: 1, } } + /// Get module id, following all aliases in case of module specifier + /// that had been redirected. pub fn get_id(&self, name: &str) -> Option<ModuleId> { - self.by_name.get(name) + let mut mod_name = name; + loop { + let symbolic_module = self.by_name.get(mod_name)?; + match symbolic_module { + SymbolicModule::Alias(target) => { + mod_name = target; + } + SymbolicModule::Mod(mod_id) => return Some(*mod_id), + } + } } pub fn get_children(&self, id: ModuleId) -> Option<&Vec<ModuleSpecifier>> { @@ -475,7 +439,7 @@ impl Modules { } pub fn is_registered(&self, specifier: &ModuleSpecifier) -> bool { - self.by_name.get(&specifier.to_string()).is_some() + self.get_id(&specifier.to_string()).is_some() } pub fn register( @@ -488,7 +452,9 @@ impl Modules { let name = String::from(name); let id = self.next_module_id; self.next_module_id += 1; - self.by_name.insert(name.clone(), id); + self + .by_name + .insert(name.to_string(), SymbolicModule::Mod(id)); self.handles_by_id.insert(id, handle.clone()); self.ids_by_handle.insert(handle, id); self.info.insert( @@ -504,12 +470,15 @@ impl Modules { } pub fn alias(&mut self, name: &str, target: &str) { - self.by_name.alias(name.to_owned(), target.to_owned()); + self + .by_name + .insert(name.to_string(), SymbolicModule::Alias(target.to_string())); } #[cfg(test)] pub fn is_alias(&self, name: &str) -> bool { - self.by_name.is_alias(name) + let cond = self.by_name.get(name); + matches!(cond, Some(SymbolicModule::Alias(_))) } pub fn get_handle(&self, id: ModuleId) -> Option<v8::Global<v8::Module>> { @@ -732,7 +701,7 @@ mod tests { let state_rc = JsRuntime::state(runtime.v8_isolate()); let state = state_rc.borrow(); - let modules = &state.modules; + let modules = &state.module_map; assert_eq!(modules.get_id("file:///a.js"), Some(a_id)); let b_id = modules.get_id("file:///b.js").unwrap(); let c_id = modules.get_id("file:///c.js").unwrap(); @@ -799,7 +768,7 @@ mod tests { let state_rc = JsRuntime::state(runtime.v8_isolate()); let state = state_rc.borrow(); - let modules = &state.modules; + let modules = &state.module_map; assert_eq!(modules.get_id("file:///circular1.js"), Some(circular1_id)); let circular2_id = modules.get_id("file:///circular2.js").unwrap(); @@ -871,7 +840,7 @@ mod tests { let state_rc = JsRuntime::state(runtime.v8_isolate()); let state = state_rc.borrow(); - let modules = &state.modules; + let modules = &state.module_map; assert_eq!(modules.get_id("file:///redirect1.js"), Some(redirect1_id)); @@ -1017,7 +986,7 @@ mod tests { let state_rc = JsRuntime::state(runtime.v8_isolate()); let state = state_rc.borrow(); - let modules = &state.modules; + let modules = &state.module_map; assert_eq!(modules.get_id("file:///main_with_code.js"), Some(main_id)); let b_id = modules.get_id("file:///b.js").unwrap(); diff --git a/core/runtime.rs b/core/runtime.rs index 67161d5e7..595a7733b 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -14,8 +14,8 @@ use crate::modules::LoadState; use crate::modules::ModuleId; use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; +use crate::modules::ModuleMap; use crate::modules::ModuleSource; -use crate::modules::Modules; use crate::modules::NoopModuleLoader; use crate::modules::PrepareLoadFuture; use crate::modules::RecursiveModuleLoad; @@ -112,7 +112,7 @@ pub(crate) struct JsRuntimeState { pub(crate) have_unpolled_ops: bool, pub(crate) op_state: Rc<RefCell<OpState>>, pub loader: Rc<dyn ModuleLoader>, - pub modules: Modules, + pub module_map: ModuleMap, pub(crate) dyn_import_map: HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>, preparing_dyn_imports: FuturesUnordered<Pin<Box<PrepareLoadFuture>>>, @@ -286,7 +286,7 @@ impl JsRuntime { pending_unref_ops: FuturesUnordered::new(), op_state: Rc::new(RefCell::new(op_state)), have_unpolled_ops: false, - modules: Modules::new(), + module_map: ModuleMap::new(), loader, dyn_import_map: HashMap::new(), preparing_dyn_imports: FuturesUnordered::new(), @@ -427,7 +427,7 @@ impl JsRuntime { // TODO(piscisaureus): The rusty_v8 type system should enforce this. state.borrow_mut().global_context.take(); - std::mem::take(&mut state.borrow_mut().modules); + std::mem::take(&mut state.borrow_mut().module_map); let snapshot_creator = self.snapshot_creator.as_mut().unwrap(); let snapshot = snapshot_creator @@ -717,7 +717,7 @@ impl JsRuntime { import_specifiers.push(module_specifier); } - let id = state_rc.borrow_mut().modules.register( + let id = state_rc.borrow_mut().module_map.register( name, main, v8::Global::<v8::Module>::new(tc_scope, module), @@ -732,51 +732,36 @@ impl JsRuntime { /// `AnyError` can be downcast to a type that exposes additional information /// about the V8 exception. By default this type is `JsError`, however it may /// be a different type if `RuntimeOptions::js_error_create_fn` has been set. - fn mod_instantiate( - &mut self, - id: ModuleId, - dyn_import_id: Option<ModuleLoadId>, - ) -> Result<(), AnyError> { + fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), AnyError> { let state_rc = Self::state(self.v8_isolate()); let context = self.global_context(); - let result = { - let scope = - &mut v8::HandleScope::with_context(self.v8_isolate(), context); - let tc_scope = &mut v8::TryCatch::new(scope); + let scope = &mut v8::HandleScope::with_context(self.v8_isolate(), context); + let tc_scope = &mut v8::TryCatch::new(scope); - let module = state_rc - .borrow() - .modules - .get_handle(id) - .map(|handle| v8::Local::new(tc_scope, handle)) - .expect("ModuleInfo not found"); + let module = state_rc + .borrow() + .module_map + .get_handle(id) + .map(|handle| v8::Local::new(tc_scope, handle)) + .expect("ModuleInfo not found"); - if module.get_status() == v8::ModuleStatus::Errored { - let exception = module.get_exception(); - exception_to_err_result(tc_scope, exception, false) - .map_err(|err| attach_handle_to_error(tc_scope, err, exception)) - } else { - let instantiate_result = module - .instantiate_module(tc_scope, bindings::module_resolve_callback); - match instantiate_result { - Some(_) => Ok(()), - None => { - let exception = tc_scope.exception().unwrap(); - exception_to_err_result(tc_scope, exception, false) - .map_err(|err| attach_handle_to_error(tc_scope, err, exception)) - } + if module.get_status() == v8::ModuleStatus::Errored { + let exception = module.get_exception(); + exception_to_err_result(tc_scope, exception, false) + .map_err(|err| attach_handle_to_error(tc_scope, err, exception)) + } else { + let instantiate_result = + module.instantiate_module(tc_scope, bindings::module_resolve_callback); + match instantiate_result { + Some(_) => Ok(()), + None => { + let exception = tc_scope.exception().unwrap(); + exception_to_err_result(tc_scope, exception, false) + .map_err(|err| attach_handle_to_error(tc_scope, err, exception)) } } - }; - - if let Some(dyn_import_id) = dyn_import_id { - if let Err(err) = result { - self.dyn_import_error(dyn_import_id, err); - return Ok(()); - } } - result } /// Evaluates an already instantiated ES module. @@ -795,7 +780,7 @@ impl JsRuntime { let module_handle = state_rc .borrow() - .modules + .module_map .get_handle(id) .expect("ModuleInfo not found"); @@ -806,59 +791,63 @@ impl JsRuntime { module.get_status() }; - if status == v8::ModuleStatus::Instantiated { - // 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 scope = - &mut v8::HandleScope::with_context(self.v8_isolate(), context1); - let module = v8::Local::new(scope, &module_handle); - let maybe_value = module.evaluate(scope); - - // Update status after evaluating. - let status = module.get_status(); + // Since the same module might be dynamically imported more than once, + // we short-circuit is it is already evaluated. + if status == v8::ModuleStatus::Evaluated { + self.dyn_import_done(load_id, id); + return Ok(()); + } - 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 = |_scope: &mut v8::HandleScope, - _args: v8::FunctionCallbackArguments, - _rv: v8::ReturnValue| {}; - let empty_fn = v8::FunctionTemplate::new(scope, empty_fn); - let empty_fn = empty_fn.get_function(scope).unwrap(); - promise.catch(scope, empty_fn); - let mut state = state_rc.borrow_mut(); - let promise_global = v8::Global::new(scope, promise); - let module_global = v8::Global::new(scope, module); + if status != v8::ModuleStatus::Instantiated { + return Ok(()); + } - let dyn_import_mod_evaluate = DynImportModEvaluate { - module_id: id, - promise: promise_global, - module: module_global, - }; + // 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 scope = &mut v8::HandleScope::with_context(self.v8_isolate(), context1); + let module = v8::Local::new(scope, &module_handle); + let maybe_value = module.evaluate(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 = |_scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue| {}; + let empty_fn = v8::FunctionTemplate::new(scope, empty_fn); + let empty_fn = empty_fn.get_function(scope).unwrap(); + promise.catch(scope, empty_fn); + let mut state = state_rc.borrow_mut(); + let promise_global = v8::Global::new(scope, promise); + let module_global = v8::Global::new(scope, module); - state - .pending_dyn_mod_evaluate - .insert(load_id, dyn_import_mod_evaluate); - } else { - assert!(status == v8::ModuleStatus::Errored); - } - } + let dyn_import_mod_evaluate = DynImportModEvaluate { + module_id: id, + promise: promise_global, + module: module_global, + }; - if status == v8::ModuleStatus::Evaluated { - self.dyn_import_done(load_id, id); + state + .pending_dyn_mod_evaluate + .insert(load_id, dyn_import_mod_evaluate); + } else { + assert!(status == v8::ModuleStatus::Errored); } Ok(()) @@ -869,6 +858,8 @@ impl JsRuntime { /// `AnyError` can be downcast to a type that exposes additional information /// about the V8 exception. By default this type is `JsError`, however it may /// be a different type if `RuntimeOptions::js_error_create_fn` has been set. + /// + /// This function panics if module has not been instantiated. fn mod_evaluate_inner( &mut self, id: ModuleId, @@ -880,60 +871,59 @@ impl JsRuntime { let module = state_rc .borrow() - .modules + .module_map .get_handle(id) .map(|handle| v8::Local::new(scope, handle)) .expect("ModuleInfo not found"); let mut status = module.get_status(); + assert_eq!(status, v8::ModuleStatus::Instantiated); let (sender, receiver) = mpsc::channel(1); - if status == v8::ModuleStatus::Instantiated { - // 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 maybe_value = module.evaluate(scope); - - // Update status after evaluating. - 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 promise_global = v8::Global::new(scope, promise); - let mut state = state_rc.borrow_mut(); - state.pending_promise_exceptions.remove(&promise_global); - let promise_global = v8::Global::new(scope, promise); - assert!( - state.pending_mod_evaluate.is_none(), - "There is already pending top level module evaluation" - ); + // 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 maybe_value = module.evaluate(scope); + + // Update status after evaluating. + 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 promise_global = v8::Global::new(scope, promise); + let mut state = state_rc.borrow_mut(); + state.pending_promise_exceptions.remove(&promise_global); + let promise_global = v8::Global::new(scope, promise); + assert!( + state.pending_mod_evaluate.is_none(), + "There is already pending top level module evaluation" + ); - state.pending_mod_evaluate = Some(ModEvaluate { - promise: promise_global, - sender, - }); - scope.perform_microtask_checkpoint(); - } else { - assert!(status == v8::ModuleStatus::Errored); - } + state.pending_mod_evaluate = Some(ModEvaluate { + promise: promise_global, + sender, + }); + scope.perform_microtask_checkpoint(); + } else { + assert!(status == v8::ModuleStatus::Errored); } receiver @@ -1000,7 +990,7 @@ impl JsRuntime { let module = { let state = state_rc.borrow(); state - .modules + .module_map .get_handle(mod_id) .map(|handle| v8::Local::new(scope, handle)) .expect("Dyn import module info not found") @@ -1103,7 +1093,10 @@ impl JsRuntime { // The top-level module from a dynamic import has been instantiated. // Load is done. let module_id = load.root_module_id.unwrap(); - self.mod_instantiate(module_id, Some(dyn_import_id))?; + let result = self.mod_instantiate(module_id); + if let Err(err) = result { + self.dyn_import_error(dyn_import_id, err); + } self.dyn_mod_evaluate(dyn_import_id, module_id)?; } } @@ -1256,13 +1249,13 @@ impl JsRuntime { if module_url_specified != module_url_found { let mut state = state_rc.borrow_mut(); state - .modules + .module_map .alias(&module_url_specified, &module_url_found); } let maybe_mod_id = { let state = state_rc.borrow(); - state.modules.get_id(&module_url_found) + state.module_map.get_id(&module_url_found) }; let module_id = match maybe_mod_id { @@ -1282,14 +1275,14 @@ impl JsRuntime { let imports = { let state_rc = Self::state(self.v8_isolate()); let state = state_rc.borrow(); - state.modules.get_children(module_id).unwrap().clone() + state.module_map.get_children(module_id).unwrap().clone() }; for module_specifier in imports { let is_registered = { let state_rc = Self::state(self.v8_isolate()); let state = state_rc.borrow(); - state.modules.is_registered(&module_specifier) + state.module_map.is_registered(&module_specifier) }; if !is_registered { load @@ -1341,7 +1334,7 @@ impl JsRuntime { } let root_id = load.root_module_id.expect("Root module id empty"); - self.mod_instantiate(root_id, None).map(|_| root_id) + self.mod_instantiate(root_id).map(|_| root_id) } fn poll_pending_ops(&mut self, cx: &mut Context) -> Vec<(OpId, Box<[u8]>)> { @@ -2336,7 +2329,7 @@ pub mod tests { let state_rc = JsRuntime::state(runtime.v8_isolate()); { let state = state_rc.borrow(); - let imports = state.modules.get_children(mod_a); + let imports = state.module_map.get_children(mod_a); assert_eq!( imports, Some(&vec![crate::resolve_url("file:///b.js").unwrap()]) @@ -2347,15 +2340,15 @@ pub mod tests { .unwrap(); { let state = state_rc.borrow(); - let imports = state.modules.get_children(mod_b).unwrap(); + let imports = state.module_map.get_children(mod_b).unwrap(); assert_eq!(imports.len(), 0); } - runtime.mod_instantiate(mod_b, None).unwrap(); + runtime.mod_instantiate(mod_b).unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); assert_eq!(resolve_count.load(Ordering::SeqCst), 1); - runtime.mod_instantiate(mod_a, None).unwrap(); + runtime.mod_instantiate(mod_a).unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); runtime.mod_evaluate_inner(mod_a); |