diff options
Diffstat (limited to 'core/modules/map.rs')
-rw-r--r-- | core/modules/map.rs | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/core/modules/map.rs b/core/modules/map.rs new file mode 100644 index 000000000..4ab146659 --- /dev/null +++ b/core/modules/map.rs @@ -0,0 +1,822 @@ +use crate::JsRuntime; +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::bindings; +use crate::error::generic_error; +use crate::fast_string::FastString; +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::ModuleCode; +use crate::modules::ModuleError; +use crate::modules::ModuleId; +use crate::modules::ModuleInfo; +use crate::modules::ModuleLoadId; +use crate::modules::ModuleLoader; +use crate::modules::ModuleName; +use crate::modules::ModuleRequest; +use crate::modules::ModuleType; +use crate::modules::NoopModuleLoader; +use crate::modules::PrepareLoadFuture; +use crate::modules::RecursiveModuleLoad; +use crate::modules::ResolutionKind; +use crate::snapshot_util::SnapshottedData; +use anyhow::Error; +use futures::future::FutureExt; +use futures::stream::FuturesUnordered; +use futures::stream::StreamFuture; +use std::cell::RefCell; +use std::collections::HashMap; +use std::pin::Pin; +use std::rc::Rc; + +use super::AssertedModuleType; + +pub const BOM_CHAR: &[u8] = &[0xef, 0xbb, 0xbf]; + +/// Strips the byte order mark from the provided text if it exists. +fn strip_bom(source_code: &[u8]) -> &[u8] { + if source_code.starts_with(BOM_CHAR) { + &source_code[BOM_CHAR.len()..] + } else { + source_code + } +} + +/// A symbolic module entity. +#[derive(Debug, PartialEq)] +pub(crate) 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). + Alias(ModuleName), + /// This module associates with a V8 module by id. + Mod(ModuleId), +} + +/// A collection of JS modules. +pub(crate) struct ModuleMap { + // Handling of specifiers and v8 objects + pub handles: Vec<v8::Global<v8::Module>>, + pub info: Vec<ModuleInfo>, + pub(crate) by_name_js: HashMap<ModuleName, SymbolicModule>, + pub(crate) by_name_json: HashMap<ModuleName, SymbolicModule>, + pub(crate) next_load_id: ModuleLoadId, + + // Handling of futures for loading module sources + pub loader: Rc<dyn ModuleLoader>, + pub(crate) dynamic_import_map: + HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>, + pub(crate) preparing_dynamic_imports: + FuturesUnordered<Pin<Box<PrepareLoadFuture>>>, + pub(crate) pending_dynamic_imports: + FuturesUnordered<StreamFuture<RecursiveModuleLoad>>, + + // This store is used temporarly, to forward parsed JSON + // value from `new_json_module` to `json_module_evaluation_steps` + json_value_store: HashMap<v8::Global<v8::Module>, v8::Global<v8::Value>>, +} + +impl ModuleMap { + pub fn collect_modules( + &self, + ) -> Vec<(AssertedModuleType, &ModuleName, &SymbolicModule)> { + let mut output = vec![]; + for module_type in [ + AssertedModuleType::JavaScriptOrWasm, + AssertedModuleType::Json, + ] { + output.extend( + self + .by_name(module_type) + .iter() + .map(|x| (module_type, x.0, x.1)), + ) + } + output + } + + #[cfg(debug_assertions)] + pub(crate) fn assert_all_modules_evaluated( + &self, + scope: &mut v8::HandleScope, + ) { + let mut not_evaluated = vec![]; + + for (i, handle) in self.handles.iter().enumerate() { + let module = v8::Local::new(scope, handle); + if !matches!(module.get_status(), v8::ModuleStatus::Evaluated) { + not_evaluated.push(self.info[i].name.as_str().to_string()); + } + } + + if !not_evaluated.is_empty() { + let mut msg = "Following modules were not evaluated; make sure they are imported from other code:\n".to_string(); + for m in not_evaluated { + msg.push_str(&format!(" - {}\n", m)); + } + panic!("{}", msg); + } + } + + pub fn serialize_for_snapshotting( + &self, + scope: &mut v8::HandleScope, + ) -> SnapshottedData { + let array = v8::Array::new(scope, 3); + + let next_load_id = v8::Integer::new(scope, self.next_load_id); + array.set_index(scope, 0, next_load_id.into()); + + let info_arr = v8::Array::new(scope, self.info.len() as i32); + for (i, info) in self.info.iter().enumerate() { + let module_info_arr = v8::Array::new(scope, 5); + + let id = v8::Integer::new(scope, info.id as i32); + module_info_arr.set_index(scope, 0, id.into()); + + let main = v8::Boolean::new(scope, info.main); + module_info_arr.set_index(scope, 1, main.into()); + + let name = info.name.v8(scope); + module_info_arr.set_index(scope, 2, name.into()); + + let array_len = 2 * info.requests.len() as i32; + let requests_arr = v8::Array::new(scope, array_len); + for (i, request) in info.requests.iter().enumerate() { + let specifier = v8::String::new_from_one_byte( + scope, + request.specifier.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap(); + requests_arr.set_index(scope, 2 * i as u32, specifier.into()); + + let asserted_module_type = + v8::Integer::new(scope, request.asserted_module_type as i32); + requests_arr.set_index( + scope, + (2 * i) as u32 + 1, + asserted_module_type.into(), + ); + } + module_info_arr.set_index(scope, 3, requests_arr.into()); + + let module_type = v8::Integer::new(scope, info.module_type as i32); + module_info_arr.set_index(scope, 4, module_type.into()); + + info_arr.set_index(scope, i as u32, module_info_arr.into()); + } + array.set_index(scope, 1, info_arr.into()); + + let by_name = self.collect_modules(); + let by_name_array = v8::Array::new(scope, by_name.len() as i32); + { + for (i, (module_type, name, module)) in by_name.into_iter().enumerate() { + let arr = v8::Array::new(scope, 3); + + let specifier = name.v8(scope); + arr.set_index(scope, 0, specifier.into()); + + let asserted_module_type = v8::Integer::new(scope, module_type as i32); + arr.set_index(scope, 1, asserted_module_type.into()); + + let symbolic_module: v8::Local<v8::Value> = match module { + SymbolicModule::Alias(alias) => { + let alias = v8::String::new_from_one_byte( + scope, + alias.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap(); + alias.into() + } + SymbolicModule::Mod(id) => { + let id = v8::Integer::new(scope, *id as i32); + id.into() + } + }; + arr.set_index(scope, 2, symbolic_module); + + by_name_array.set_index(scope, i as u32, arr.into()); + } + } + array.set_index(scope, 2, by_name_array.into()); + + let array_global = v8::Global::new(scope, array); + + let handles = self.handles.clone(); + SnapshottedData { + module_map_data: array_global, + module_handles: handles, + } + } + + pub fn update_with_snapshotted_data( + &mut self, + scope: &mut v8::HandleScope, + snapshotted_data: SnapshottedData, + ) { + let local_data: v8::Local<v8::Array> = + v8::Local::new(scope, snapshotted_data.module_map_data); + + { + let next_load_id = local_data.get_index(scope, 0).unwrap(); + assert!(next_load_id.is_int32()); + let integer = next_load_id.to_integer(scope).unwrap(); + let val = integer.int32_value(scope).unwrap(); + self.next_load_id = val; + } + + { + let info_val = local_data.get_index(scope, 1).unwrap(); + + let info_arr: v8::Local<v8::Array> = info_val.try_into().unwrap(); + let len = info_arr.length() as usize; + // Over allocate so executing a few scripts doesn't have to resize this vec. + let mut info = Vec::with_capacity(len + 16); + + for i in 0..len { + let module_info_arr: v8::Local<v8::Array> = info_arr + .get_index(scope, i as u32) + .unwrap() + .try_into() + .unwrap(); + let id = module_info_arr + .get_index(scope, 0) + .unwrap() + .to_integer(scope) + .unwrap() + .value() as ModuleId; + + let main = module_info_arr + .get_index(scope, 1) + .unwrap() + .to_boolean(scope) + .is_true(); + + let name = module_info_arr + .get_index(scope, 2) + .unwrap() + .to_rust_string_lossy(scope) + .into(); + + let requests_arr: v8::Local<v8::Array> = module_info_arr + .get_index(scope, 3) + .unwrap() + .try_into() + .unwrap(); + let len = (requests_arr.length() as usize) / 2; + let mut requests = Vec::with_capacity(len); + for i in 0..len { + let specifier = requests_arr + .get_index(scope, (2 * i) as u32) + .unwrap() + .to_rust_string_lossy(scope); + let asserted_module_type_no = requests_arr + .get_index(scope, (2 * i + 1) as u32) + .unwrap() + .to_integer(scope) + .unwrap() + .value(); + let asserted_module_type = match asserted_module_type_no { + 0 => AssertedModuleType::JavaScriptOrWasm, + 1 => AssertedModuleType::Json, + _ => unreachable!(), + }; + requests.push(ModuleRequest { + specifier, + asserted_module_type, + }); + } + + let module_type_no = module_info_arr + .get_index(scope, 4) + .unwrap() + .to_integer(scope) + .unwrap() + .value(); + let module_type = match module_type_no { + 0 => ModuleType::JavaScript, + 1 => ModuleType::Json, + _ => unreachable!(), + }; + + let module_info = ModuleInfo { + id, + main, + name, + requests, + module_type, + }; + info.push(module_info); + } + + self.info = info; + } + + self + .by_name_mut(AssertedModuleType::JavaScriptOrWasm) + .clear(); + self.by_name_mut(AssertedModuleType::Json).clear(); + + { + let by_name_arr: v8::Local<v8::Array> = + local_data.get_index(scope, 2).unwrap().try_into().unwrap(); + let len = by_name_arr.length() as usize; + + for i in 0..len { + let arr: v8::Local<v8::Array> = by_name_arr + .get_index(scope, i as u32) + .unwrap() + .try_into() + .unwrap(); + + let specifier = + arr.get_index(scope, 0).unwrap().to_rust_string_lossy(scope); + let asserted_module_type = match arr + .get_index(scope, 1) + .unwrap() + .to_integer(scope) + .unwrap() + .value() + { + 0 => AssertedModuleType::JavaScriptOrWasm, + 1 => AssertedModuleType::Json, + _ => unreachable!(), + }; + + let symbolic_module_val = arr.get_index(scope, 2).unwrap(); + let val = if symbolic_module_val.is_number() { + SymbolicModule::Mod( + symbolic_module_val + .to_integer(scope) + .unwrap() + .value() + .try_into() + .unwrap(), + ) + } else { + SymbolicModule::Alias( + symbolic_module_val.to_rust_string_lossy(scope).into(), + ) + }; + + self + .by_name_mut(asserted_module_type) + .insert(specifier.into(), val); + } + } + + self.handles = snapshotted_data.module_handles; + } + + pub(crate) fn new(loader: Rc<dyn ModuleLoader>) -> ModuleMap { + Self { + handles: vec![], + info: vec![], + by_name_js: HashMap::new(), + by_name_json: HashMap::new(), + next_load_id: 1, + loader, + dynamic_import_map: HashMap::new(), + preparing_dynamic_imports: FuturesUnordered::new(), + pending_dynamic_imports: FuturesUnordered::new(), + json_value_store: HashMap::new(), + } + } + + /// Get module id, following all aliases in case of module specifier + /// that had been redirected. + pub(crate) fn get_id( + &self, + name: impl AsRef<str>, + asserted_module_type: AssertedModuleType, + ) -> Option<ModuleId> { + let map = self.by_name(asserted_module_type); + let first_symbolic_module = map.get(name.as_ref())?; + let mut mod_name = match first_symbolic_module { + SymbolicModule::Mod(mod_id) => return Some(*mod_id), + SymbolicModule::Alias(target) => target, + }; + loop { + let symbolic_module = map.get(mod_name.as_ref())?; + match symbolic_module { + SymbolicModule::Alias(target) => { + debug_assert!(mod_name != target); + mod_name = target; + } + SymbolicModule::Mod(mod_id) => return Some(*mod_id), + } + } + } + + pub(crate) fn new_json_module( + &mut self, + scope: &mut v8::HandleScope, + name: ModuleName, + source: ModuleCode, + ) -> Result<ModuleId, ModuleError> { + let name_str = name.v8(scope); + let source_str = v8::String::new_from_utf8( + scope, + strip_bom(source.as_bytes()), + v8::NewStringType::Normal, + ) + .unwrap(); + + let tc_scope = &mut v8::TryCatch::new(scope); + + let parsed_json = match v8::json::parse(tc_scope, source_str) { + Some(parsed_json) => parsed_json, + None => { + assert!(tc_scope.has_caught()); + let exception = tc_scope.exception().unwrap(); + let exception = v8::Global::new(tc_scope, exception); + return Err(ModuleError::Exception(exception)); + } + }; + + let export_names = [v8::String::new(tc_scope, "default").unwrap()]; + let module = v8::Module::create_synthetic_module( + tc_scope, + name_str, + &export_names, + json_module_evaluation_steps, + ); + + let handle = v8::Global::<v8::Module>::new(tc_scope, module); + let value_handle = v8::Global::<v8::Value>::new(tc_scope, parsed_json); + self.json_value_store.insert(handle.clone(), value_handle); + + let id = + self.create_module_info(name, ModuleType::Json, handle, false, vec![]); + + Ok(id) + } + + /// Create and compile an ES module. + pub(crate) fn new_es_module( + &mut self, + scope: &mut v8::HandleScope, + main: bool, + name: ModuleName, + source: ModuleCode, + is_dynamic_import: bool, + ) -> Result<ModuleId, ModuleError> { + let name_str = name.v8(scope); + let source_str = source.v8(scope); + + let origin = bindings::module_origin(scope, name_str); + let source = v8::script_compiler::Source::new(source_str, Some(&origin)); + + let tc_scope = &mut v8::TryCatch::new(scope); + + let maybe_module = v8::script_compiler::compile_module(tc_scope, source); + + if tc_scope.has_caught() { + assert!(maybe_module.is_none()); + let exception = tc_scope.exception().unwrap(); + let exception = v8::Global::new(tc_scope, exception); + return Err(ModuleError::Exception(exception)); + } + + let module = maybe_module.unwrap(); + + let mut requests: Vec<ModuleRequest> = vec![]; + let module_requests = module.get_module_requests(); + for i in 0..module_requests.length() { + let module_request = v8::Local::<v8::ModuleRequest>::try_from( + module_requests.get(tc_scope, i).unwrap(), + ) + .unwrap(); + let import_specifier = module_request + .get_specifier() + .to_rust_string_lossy(tc_scope); + + let import_assertions = module_request.get_import_assertions(); + + let assertions = parse_import_assertions( + tc_scope, + import_assertions, + ImportAssertionsKind::StaticImport, + ); + + // FIXME(bartomieju): there are no stack frames if exception + // is thrown here + validate_import_assertions(tc_scope, &assertions); + if tc_scope.has_caught() { + let exception = tc_scope.exception().unwrap(); + let exception = v8::Global::new(tc_scope, exception); + return Err(ModuleError::Exception(exception)); + } + + let module_specifier = match self.loader.resolve( + &import_specifier, + name.as_ref(), + if is_dynamic_import { + ResolutionKind::DynamicImport + } else { + ResolutionKind::Import + }, + ) { + Ok(s) => s, + Err(e) => return Err(ModuleError::Other(e)), + }; + let asserted_module_type = + get_asserted_module_type_from_assertions(&assertions); + let request = ModuleRequest { + specifier: module_specifier.to_string(), + asserted_module_type, + }; + requests.push(request); + } + + if main { + let maybe_main_module = self.info.iter().find(|module| module.main); + if let Some(main_module) = maybe_main_module { + return Err(ModuleError::Other(generic_error( + format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})", + name.as_ref(), + main_module.name, + )))); + } + } + + let handle = v8::Global::<v8::Module>::new(tc_scope, module); + let id = self.create_module_info( + name, + ModuleType::JavaScript, + handle, + main, + requests, + ); + + Ok(id) + } + + pub(crate) fn clear(&mut self) { + *self = Self::new(self.loader.clone()) + } + + pub(crate) fn get_handle_by_name( + &self, + name: impl AsRef<str>, + ) -> Option<v8::Global<v8::Module>> { + let id = self + .get_id(name.as_ref(), AssertedModuleType::JavaScriptOrWasm) + .or_else(|| self.get_id(name.as_ref(), AssertedModuleType::Json))?; + self.get_handle(id) + } + + pub(crate) fn inject_handle( + &mut self, + name: ModuleName, + module_type: ModuleType, + handle: v8::Global<v8::Module>, + ) { + self.create_module_info(name, module_type, handle, false, vec![]); + } + + fn create_module_info( + &mut self, + name: FastString, + module_type: ModuleType, + handle: v8::Global<v8::Module>, + main: bool, + requests: Vec<ModuleRequest>, + ) -> ModuleId { + let id = self.handles.len(); + let (name1, name2) = name.into_cheap_copy(); + self + .by_name_mut(module_type.into()) + .insert(name1, SymbolicModule::Mod(id)); + self.handles.push(handle); + self.info.push(ModuleInfo { + id, + main, + name: name2, + requests, + module_type, + }); + + id + } + + pub(crate) fn get_requested_modules( + &self, + id: ModuleId, + ) -> Option<&Vec<ModuleRequest>> { + self.info.get(id).map(|i| &i.requests) + } + + fn is_registered( + &self, + specifier: impl AsRef<str>, + asserted_module_type: AssertedModuleType, + ) -> bool { + if let Some(id) = self.get_id(specifier.as_ref(), asserted_module_type) { + let info = self.get_info_by_id(id).unwrap(); + return asserted_module_type == info.module_type.into(); + } + + false + } + + pub(crate) fn by_name( + &self, + asserted_module_type: AssertedModuleType, + ) -> &HashMap<ModuleName, SymbolicModule> { + match asserted_module_type { + AssertedModuleType::Json => &self.by_name_json, + AssertedModuleType::JavaScriptOrWasm => &self.by_name_js, + } + } + + pub(crate) fn by_name_mut( + &mut self, + asserted_module_type: AssertedModuleType, + ) -> &mut HashMap<ModuleName, SymbolicModule> { + match asserted_module_type { + AssertedModuleType::Json => &mut self.by_name_json, + AssertedModuleType::JavaScriptOrWasm => &mut self.by_name_js, + } + } + + pub(crate) fn alias( + &mut self, + name: FastString, + asserted_module_type: AssertedModuleType, + target: FastString, + ) { + debug_assert_ne!(name, target); + self + .by_name_mut(asserted_module_type) + .insert(name, SymbolicModule::Alias(target)); + } + + #[cfg(test)] + pub(crate) fn is_alias( + &self, + name: &str, + asserted_module_type: AssertedModuleType, + ) -> bool { + let cond = self.by_name(asserted_module_type).get(name); + matches!(cond, Some(SymbolicModule::Alias(_))) + } + + pub(crate) fn get_handle( + &self, + id: ModuleId, + ) -> Option<v8::Global<v8::Module>> { + self.handles.get(id).cloned() + } + + pub(crate) fn get_info( + &self, + global: &v8::Global<v8::Module>, + ) -> Option<&ModuleInfo> { + if let Some(id) = self.handles.iter().position(|module| module == global) { + return self.info.get(id); + } + + None + } + + pub(crate) fn get_info_by_id(&self, id: ModuleId) -> Option<&ModuleInfo> { + self.info.get(id) + } + + pub(crate) async fn load_main( + module_map_rc: Rc<RefCell<ModuleMap>>, + specifier: impl AsRef<str>, + ) -> Result<RecursiveModuleLoad, Error> { + let load = + RecursiveModuleLoad::main(specifier.as_ref(), module_map_rc.clone()); + load.prepare().await?; + Ok(load) + } + + pub(crate) async fn load_side( + module_map_rc: Rc<RefCell<ModuleMap>>, + specifier: impl AsRef<str>, + ) -> Result<RecursiveModuleLoad, Error> { + let load = + RecursiveModuleLoad::side(specifier.as_ref(), module_map_rc.clone()); + load.prepare().await?; + Ok(load) + } + + // Initiate loading of a module graph imported using `import()`. + pub(crate) fn load_dynamic_import( + module_map_rc: Rc<RefCell<ModuleMap>>, + specifier: &str, + referrer: &str, + asserted_module_type: AssertedModuleType, + resolver_handle: v8::Global<v8::PromiseResolver>, + ) { + let load = RecursiveModuleLoad::dynamic_import( + specifier, + referrer, + asserted_module_type, + module_map_rc.clone(), + ); + module_map_rc + .borrow_mut() + .dynamic_import_map + .insert(load.id, resolver_handle); + + let loader = module_map_rc.borrow().loader.clone(); + let resolve_result = + loader.resolve(specifier, referrer, ResolutionKind::DynamicImport); + let fut = match resolve_result { + Ok(module_specifier) => { + if module_map_rc + .borrow() + .is_registered(module_specifier, asserted_module_type) + { + async move { (load.id, Ok(load)) }.boxed_local() + } else { + async move { (load.id, load.prepare().await.map(|()| load)) } + .boxed_local() + } + } + Err(error) => async move { (load.id, Err(error)) }.boxed_local(), + }; + module_map_rc + .borrow_mut() + .preparing_dynamic_imports + .push(fut); + } + + pub(crate) fn has_pending_dynamic_imports(&self) -> bool { + !(self.preparing_dynamic_imports.is_empty() + && self.pending_dynamic_imports.is_empty()) + } + + /// Called by `module_resolve_callback` during module instantiation. + pub(crate) fn resolve_callback<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + specifier: &str, + referrer: &str, + import_assertions: HashMap<String, String>, + ) -> Option<v8::Local<'s, v8::Module>> { + 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 + } +} + +impl Default for ModuleMap { + fn default() -> Self { + Self::new(Rc::new(NoopModuleLoader)) + } +} + +// Clippy thinks the return value doesn't need to be an Option, it's unaware +// of the mapping that MapFnFrom<F> does for ResolveModuleCallback. +#[allow(clippy::unnecessary_wraps)] +fn json_module_evaluation_steps<'a>( + context: v8::Local<'a, v8::Context>, + module: v8::Local<v8::Module>, +) -> Option<v8::Local<'a, v8::Value>> { + // SAFETY: `CallbackScope` can be safely constructed from `Local<Context>` + let scope = &mut unsafe { v8::CallbackScope::new(context) }; + let tc_scope = &mut v8::TryCatch::new(scope); + let module_map = JsRuntime::module_map_from(tc_scope); + + let handle = v8::Global::<v8::Module>::new(tc_scope, module); + let value_handle = module_map + .borrow_mut() + .json_value_store + .remove(&handle) + .unwrap(); + let value_local = v8::Local::new(tc_scope, value_handle); + + let name = v8::String::new(tc_scope, "default").unwrap(); + // This should never fail + assert!( + module.set_synthetic_module_export(tc_scope, name, value_local) + == Some(true) + ); + assert!(!tc_scope.has_caught()); + + // Since TLA is active we need to return a promise. + let resolver = v8::PromiseResolver::new(tc_scope).unwrap(); + let undefined = v8::undefined(tc_scope); + resolver.resolve(tc_scope, undefined.into()); + Some(resolver.get_promise(tc_scope).into()) +} |