From b6a3f8f722db89bc136e91da598f581c5838d38e Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Sun, 28 May 2023 19:44:41 +0100 Subject: [PATCH] refactor(core): remove ext: modules from the module map (#19040) Rather than disallowing `ext:` resolution, clear the module map after initializing extensions so extension modules are anonymized. This operation is explicitly called in `deno_runtime`. Re-inject `node:` specifiers into the module map after doing this. Fixes #17717. --- cli/build.rs | 2 +- cli/graph_util.rs | 5 +- cli/lsp/diagnostics.rs | 2 +- cli/lsp/documents.rs | 2 +- cli/module_loader.rs | 12 +- cli/standalone/mod.rs | 6 - .../run/extension_dynamic_import.ts.out | 10 +- .../testdata/run/extension_import.ts.out | 2 +- cli/tsc/mod.rs | 2 +- core/extensions.rs | 10 - core/lib.rs | 1 - core/modules.rs | 238 ++++++------------ core/runtime.rs | 131 ++++------ ext/node/lib.rs | 1 - ext/node/polyfill.rs | 226 ++++++++--------- runtime/web_worker.rs | 2 + runtime/worker.rs | 12 + 17 files changed, 261 insertions(+), 403 deletions(-) diff --git a/cli/build.rs b/cli/build.rs index 2b2181bae4..72b8e78183 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -40,7 +40,7 @@ mod ts { let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES .iter() - .map(|s| s.name) + .map(|p| p.module_name()) .collect::>(); let build_libs = state.borrow::>(); json!({ diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 90c4f8b388..976c2aeca5 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -378,9 +378,8 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String { pub fn get_resolution_error_bare_node_specifier( error: &ResolutionError, ) -> Option<&str> { - get_resolution_error_bare_specifier(error).filter(|specifier| { - deno_node::resolve_builtin_node_module(specifier).is_ok() - }) + get_resolution_error_bare_specifier(error) + .filter(|specifier| deno_node::is_builtin_node_module(specifier)) } fn get_resolution_error_bare_specifier( diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index a5e9d7bf8e..6af6c92b33 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1023,7 +1023,7 @@ fn diagnose_resolution( } } else if let Some(module_name) = specifier.as_str().strip_prefix("node:") { - if deno_node::resolve_builtin_node_module(module_name).is_err() { + if !deno_node::is_builtin_node_module(module_name) { diagnostics .push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())); } else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver { diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 4bfb9342a1..d088e01c07 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -1090,7 +1090,7 @@ impl Documents { } } if let Some(module_name) = specifier.strip_prefix("node:") { - if deno_node::resolve_builtin_node_module(module_name).is_ok() { + if deno_node::is_builtin_node_module(module_name) { // return itself for node: specifiers because during type checking // we resolve to the ambient modules in the @types/node package // rather than deno_std/node diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 73fd2f5f40..cc416970fc 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -47,7 +47,6 @@ use deno_graph::Module; use deno_graph::Resolution; use deno_lockfile::Lockfile; use deno_runtime::deno_fs; -use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; @@ -496,9 +495,7 @@ impl ModuleLoader for CliModuleLoader { .shared .npm_module_loader .resolve_nv_ref(&module.nv_reference, permissions), - Some(Module::Node(module)) => { - deno_node::resolve_builtin_node_module(&module.module_name) - } + Some(Module::Node(module)) => Ok(module.specifier.clone()), Some(Module::Esm(module)) => Ok(module.specifier.clone()), Some(Module::Json(module)) => Ok(module.specifier.clone()), Some(Module::External(module)) => { @@ -517,11 +514,6 @@ impl ModuleLoader for CliModuleLoader { } } - // Built-in Node modules - if let Some(module_name) = specifier.strip_prefix("node:") { - return deno_node::resolve_builtin_node_module(module_name); - } - // FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL // and `Deno.core.evalContext` API. Ideally we should always have a referrer filled // but sadly that's not the case due to missing APIs in V8. @@ -802,8 +794,6 @@ impl NpmModuleLoader { if let NodeResolution::CommonJs(specifier) = &response { // remember that this was a common js resolution self.cjs_resolutions.insert(specifier.clone()); - } else if let NodeResolution::BuiltIn(specifier) = &response { - return deno_node::resolve_builtin_node_module(specifier); } Ok(response.into_url()) } diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 16f4c9e650..c7f595e178 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -39,7 +39,6 @@ use deno_core::ModuleType; use deno_core::ResolutionKind; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; -use deno_runtime::deno_node; use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_tls::rustls::RootCertStore; @@ -128,11 +127,6 @@ impl ModuleLoader for EmbeddedModuleLoader { .resolve_req_reference(&reference, permissions); } - // Built-in Node modules - if let Some(module_name) = specifier_text.strip_prefix("node:") { - return deno_node::resolve_builtin_node_module(module_name); - } - match maybe_mapped { Some(resolved) => Ok(resolved), None => deno_core::resolve_import(specifier, referrer.as_str()) diff --git a/cli/tests/testdata/run/extension_dynamic_import.ts.out b/cli/tests/testdata/run/extension_dynamic_import.ts.out index 081318960e..4414ad9235 100644 --- a/cli/tests/testdata/run/extension_dynamic_import.ts.out +++ b/cli/tests/testdata/run/extension_dynamic_import.ts.out @@ -1,4 +1,10 @@ -error: Uncaught (in promise) TypeError: Cannot load extension module from external code +error: Uncaught (in promise) TypeError: Unsupported scheme "ext" for module "ext:runtime/01_errors.js". Supported schemes: [ + "data", + "blob", + "file", + "http", + "https", +] await import("ext:runtime/01_errors.js"); ^ - at [WILDCARD]/extension_dynamic_import.ts:1:1 + at async [WILDCARD]/extension_dynamic_import.ts:1:1 diff --git a/cli/tests/testdata/run/extension_import.ts.out b/cli/tests/testdata/run/extension_import.ts.out index f1d9d5eb20..88039a9ce8 100644 --- a/cli/tests/testdata/run/extension_import.ts.out +++ b/cli/tests/testdata/run/extension_import.ts.out @@ -5,4 +5,4 @@ error: Unsupported scheme "ext" for module "ext:runtime/01_errors.js". Supported "http", "https", ] - at [WILDCARD] + at [WILDCARD]/extension_import.ts:1:8 diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index b77f39fd52..2b8a210ab0 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -538,7 +538,7 @@ fn op_resolve( }; for specifier in args.specifiers { if let Some(module_name) = specifier.strip_prefix("node:") { - if deno_node::resolve_builtin_node_module(module_name).is_ok() { + if deno_node::is_builtin_node_module(module_name) { // return itself for node: specifiers because during type checking // we resolve to the ambient modules in the @types/node package // rather than deno_std/node diff --git a/core/extensions.rs b/core/extensions.rs index ba151da3d3..a8b52eb3b6 100644 --- a/core/extensions.rs +++ b/core/extensions.rs @@ -471,16 +471,6 @@ impl Extension { pub fn disable(self) -> Self { self.enabled(false) } - - pub(crate) fn find_esm( - &self, - specifier: &str, - ) -> Option<&ExtensionFileSource> { - self - .get_esm_sources()? - .iter() - .find(|s| s.specifier == specifier) - } } // Provides a convenient builder pattern to declare Extensions diff --git a/core/lib.rs b/core/lib.rs index 58140bb227..8edc8be18b 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -79,7 +79,6 @@ pub use crate::module_specifier::resolve_url; pub use crate::module_specifier::resolve_url_or_path; pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; -pub use crate::modules::ExtModuleLoader; pub use crate::modules::ExtModuleLoaderCb; pub use crate::modules::FsModuleLoader; pub use crate::modules::ModuleCode; diff --git a/core/modules.rs b/core/modules.rs index 2acc146840..5a9226b6cd 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -12,8 +12,8 @@ use crate::snapshot_util::SnapshottedData; use crate::Extension; use crate::JsRuntime; use crate::OpState; +use anyhow::anyhow; use anyhow::Error; -use core::panic; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::Stream; @@ -385,154 +385,90 @@ impl ModuleLoader for NoopModuleLoader { pub type ExtModuleLoaderCb = Box Result>; -pub struct ExtModuleLoader { - module_loader: Rc, - extensions: Rc>>, - ext_resolution_allowed: RefCell, - used_esm_sources: RefCell>, - maybe_load_callback: Option, -} - -impl Default for ExtModuleLoader { - fn default() -> Self { - Self { - module_loader: Rc::new(NoopModuleLoader), - extensions: Default::default(), - ext_resolution_allowed: Default::default(), - used_esm_sources: RefCell::new(HashMap::default()), - maybe_load_callback: None, - } - } +pub(crate) struct ExtModuleLoader { + maybe_load_callback: Option>, + sources: RefCell>, + used_specifiers: RefCell>, } impl ExtModuleLoader { pub fn new( - module_loader: Option>, - extensions: Rc>>, - maybe_load_callback: Option, + extensions: &[Extension], + maybe_load_callback: Option>, ) -> Self { - let used_esm_sources: HashMap = extensions - .borrow() - .iter() - .flat_map(|e| e.get_esm_sources()) - .flatten() - .map(|file_source| (file_source.specifier.to_string(), false)) - .collect(); - + let mut sources = HashMap::new(); + sources.extend( + extensions + .iter() + .flat_map(|e| e.get_esm_sources()) + .flatten() + .map(|s| (s.specifier.to_string(), s.clone())), + ); ExtModuleLoader { - module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)), - extensions, - ext_resolution_allowed: Default::default(), - used_esm_sources: RefCell::new(used_esm_sources), maybe_load_callback, + sources: RefCell::new(sources), + used_specifiers: Default::default(), } } +} - pub fn resolve( +impl ModuleLoader for ExtModuleLoader { + fn resolve( &self, specifier: &str, referrer: &str, - kind: ResolutionKind, + _kind: ResolutionKind, ) -> Result { - if specifier.starts_with("ext:") { - if !referrer.starts_with("ext:") && referrer != "." - || !*self.ext_resolution_allowed.borrow() - { - return Err(generic_error( - "Cannot load extension module from external code", - )); - } - return Ok(ModuleSpecifier::parse(specifier)?); - } - self.module_loader.resolve(specifier, referrer, kind) + Ok(resolve_import(specifier, referrer)?) } - pub fn load( + fn load( &self, - module_specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - is_dyn_import: bool, + specifier: &ModuleSpecifier, + _maybe_referrer: Option<&ModuleSpecifier>, + _is_dyn_import: bool, ) -> Pin> { - if module_specifier.scheme() != "ext" { - return self.module_loader.load( - module_specifier, - maybe_referrer, - is_dyn_import, - ); - } - - let specifier = module_specifier.to_string(); - let extensions = self.extensions.borrow(); - let maybe_file_source = extensions - .iter() - .find_map(|e| e.find_esm(module_specifier.as_str())); - - if let Some(file_source) = maybe_file_source { - { - let mut used_esm_sources = self.used_esm_sources.borrow_mut(); - let used = used_esm_sources.get_mut(file_source.specifier).unwrap(); - *used = true; - } - - let result = if let Some(load_callback) = &self.maybe_load_callback { - load_callback(file_source) - } else { - file_source.load() - }; - - match result { - Ok(code) => { - let res = - ModuleSource::new(ModuleType::JavaScript, code, module_specifier); - return futures::future::ok(res).boxed_local(); - } - Err(err) => return futures::future::err(err).boxed_local(), + let sources = self.sources.borrow(); + let source = match sources.get(specifier.as_str()) { + Some(source) => source, + None => return futures::future::err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier)).boxed_local(), + }; + self + .used_specifiers + .borrow_mut() + .insert(specifier.to_string()); + let result = if let Some(load_callback) = &self.maybe_load_callback { + load_callback(source) + } else { + source.load() + }; + match result { + Ok(code) => { + let res = ModuleSource::new(ModuleType::JavaScript, code, specifier); + return futures::future::ok(res).boxed_local(); } + Err(err) => return futures::future::err(err).boxed_local(), } - - async move { - Err(generic_error(format!( - "Cannot find extension module source for specifier {specifier}" - ))) - } - .boxed_local() } - pub fn prepare_load( + fn prepare_load( &self, - op_state: Rc>, - module_specifier: &ModuleSpecifier, - maybe_referrer: Option, - is_dyn_import: bool, + _op_state: Rc>, + _specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, ) -> Pin>>> { - if module_specifier.scheme() == "ext" { - return async { Ok(()) }.boxed_local(); - } - - self.module_loader.prepare_load( - op_state, - module_specifier, - maybe_referrer, - is_dyn_import, - ) - } - - pub fn allow_ext_resolution(&self) { - *self.ext_resolution_allowed.borrow_mut() = true; - } - - pub fn disallow_ext_resolution(&self) { - *self.ext_resolution_allowed.borrow_mut() = false; + async { Ok(()) }.boxed_local() } } impl Drop for ExtModuleLoader { fn drop(&mut self) { - let used_esm_sources = self.used_esm_sources.get_mut(); - let unused_modules: Vec<_> = used_esm_sources + let sources = self.sources.get_mut(); + let used_specifiers = self.used_specifiers.get_mut(); + let unused_modules: Vec<_> = sources .iter() - .filter(|(_s, v)| !*v) - .map(|(s, _)| s) + .filter(|(k, _)| !used_specifiers.contains(k.as_str())) .collect(); if !unused_modules.is_empty() { @@ -541,7 +477,7 @@ impl Drop for ExtModuleLoader { .to_string(); for m in unused_modules { msg.push_str(" - "); - msg.push_str(m); + msg.push_str(m.0); msg.push('\n'); } panic!("{}", msg); @@ -634,7 +570,7 @@ pub(crate) struct RecursiveModuleLoad { // These three fields are copied from `module_map_rc`, but they are cloned // ahead of time to avoid already-borrowed errors. op_state: Rc>, - loader: Rc, + loader: Rc, } impl RecursiveModuleLoad { @@ -1060,7 +996,7 @@ pub(crate) struct ModuleMap { pub(crate) next_load_id: ModuleLoadId, // Handling of futures for loading module sources - pub loader: Rc, + pub loader: Rc, op_state: Rc>, pub(crate) dynamic_import_map: HashMap>, @@ -1369,7 +1305,7 @@ impl ModuleMap { } pub(crate) fn new( - loader: Rc, + loader: Rc, op_state: Rc>, ) -> ModuleMap { Self { @@ -1556,6 +1492,29 @@ impl ModuleMap { Ok(id) } + pub(crate) fn clear(&mut self) { + *self = Self::new(self.loader.clone(), self.op_state.clone()) + } + + pub(crate) fn get_handle_by_name( + &self, + name: impl AsRef, + ) -> Option> { + 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, + ) { + self.create_module_info(name, module_type, handle, false, vec![]); + } + fn create_module_info( &mut self, name: FastString, @@ -3005,37 +2964,4 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error(); ) .unwrap(); } - - #[test] - fn ext_resolution() { - let loader = ExtModuleLoader::default(); - loader.allow_ext_resolution(); - loader - .resolve("ext:core.js", "ext:referrer.js", ResolutionKind::Import) - .unwrap(); - loader - .resolve("ext:core.js", ".", ResolutionKind::Import) - .unwrap(); - } - - #[test] - fn ext_resolution_failure() { - let loader = ExtModuleLoader::default(); - loader.allow_ext_resolution(); - assert_eq!( - loader - .resolve("ext:core.js", "file://bar", ResolutionKind::Import,) - .err() - .map(|e| e.to_string()), - Some("Cannot load extension module from external code".to_string()) - ); - loader.disallow_ext_resolution(); - assert_eq!( - loader - .resolve("ext:core.js", "ext:referrer.js", ResolutionKind::Import,) - .err() - .map(|e| e.to_string()), - Some("Cannot load extension module from external code".to_string()) - ); - } } diff --git a/core/runtime.rs b/core/runtime.rs index dcadd6639a..be3ae4355b 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -9,6 +9,7 @@ use crate::extensions::OpEventLoopFn; 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; @@ -16,6 +17,7 @@ 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::realm::ContextState; use crate::realm::JsRealm; @@ -24,6 +26,7 @@ use crate::snapshot_util; 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; @@ -88,6 +91,7 @@ pub struct JsRuntime { // a safety issue with SnapshotCreator. See JsRuntime::drop. v8_isolate: Option, snapshot_options: snapshot_util::SnapshotOptions, + snapshot_module_load_cb: Option>, allocations: IsolateAllocations, extensions: Rc>>, event_loop_middlewares: Vec>, @@ -506,13 +510,6 @@ impl JsRuntime { } } } - let num_extensions = options.extensions.len(); - let extensions = Rc::new(RefCell::new(options.extensions)); - let ext_loader = Rc::new(crate::modules::ExtModuleLoader::new( - Some(loader.clone()), - extensions.clone(), - options.snapshot_module_load_cb, - )); { let global_realm = JsRealmInner::new( @@ -530,8 +527,7 @@ impl JsRuntime { Self::STATE_DATA_OFFSET, Rc::into_raw(state_rc.clone()) as *mut c_void, ); - let module_map_rc = - Rc::new(RefCell::new(ModuleMap::new(ext_loader, op_state))); + let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state))); if let Some(snapshotted_data) = maybe_snapshotted_data { let scope = &mut v8::HandleScope::with_context(&mut isolate, global_context); @@ -546,11 +542,12 @@ impl JsRuntime { let mut js_runtime = Self { v8_isolate: Some(isolate), snapshot_options, + snapshot_module_load_cb: options.snapshot_module_load_cb.map(Rc::new), allocations: IsolateAllocations::default(), - event_loop_middlewares: Vec::with_capacity(num_extensions), - extensions, + event_loop_middlewares: Vec::with_capacity(options.extensions.len()), + extensions: Rc::new(RefCell::new(options.extensions)), state: state_rc, - module_map: Some(module_map_rc.clone()), + module_map: Some(module_map_rc), is_main: options.is_main, }; @@ -558,9 +555,7 @@ impl JsRuntime { // available during the initialization process. js_runtime.init_extension_ops().unwrap(); let realm = js_runtime.global_realm(); - module_map_rc.borrow().loader.allow_ext_resolution(); js_runtime.init_extension_js(&realm).unwrap(); - module_map_rc.borrow().loader.disallow_ext_resolution(); js_runtime } @@ -666,21 +661,7 @@ impl JsRuntime { JsRealm::new(realm) }; - self - .module_map - .as_ref() - .unwrap() - .borrow() - .loader - .allow_ext_resolution(); self.init_extension_js(&realm)?; - self - .module_map - .as_ref() - .unwrap() - .borrow() - .loader - .disallow_ext_resolution(); Ok(realm) } @@ -735,6 +716,15 @@ impl JsRuntime { // 2. Iterate through all extensions: // a. If an extension has a `esm_entry_point`, execute it. + // TODO(nayeemrmn): Module maps should be per-realm. + let module_map = self.module_map.as_ref().unwrap(); + let loader = module_map.borrow().loader.clone(); + let ext_loader = Rc::new(ExtModuleLoader::new( + &self.extensions.borrow(), + self.snapshot_module_load_cb.clone(), + )); + module_map.borrow_mut().loader = ext_loader; + let mut esm_entrypoints = vec![]; // Take extensions to avoid double-borrow @@ -816,6 +806,7 @@ impl JsRuntime { // Restore extensions self.extensions = extensions; + self.module_map.as_ref().unwrap().borrow_mut().loader = loader; Ok(()) } @@ -1865,6 +1856,29 @@ 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.as_ref().unwrap().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, @@ -4774,67 +4788,6 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { .is_ok()); } - #[tokio::test] - async fn cant_load_internal_module_when_snapshot_is_loaded_and_not_snapshotting( - ) { - #[derive(Default)] - struct ModsLoader; - - impl ModuleLoader for ModsLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _kind: ResolutionKind, - ) -> Result { - 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> { - let code = r#" - // This module doesn't really exist, just verifying that we'll get - // an error when specifier starts with "ext:". - import { core } from "ext:core.js"; - "#; - - async move { Ok(ModuleSource::for_test(code, "file:///main.js")) } - .boxed_local() - } - } - - let snapshot = { - let runtime = JsRuntime::new(RuntimeOptions { - will_snapshot: true, - ..Default::default() - }); - let snap: &[u8] = &runtime.snapshot(); - Vec::from(snap).into_boxed_slice() - }; - - let mut runtime2 = JsRuntime::new(RuntimeOptions { - module_loader: Some(Rc::new(ModsLoader)), - startup_snapshot: Some(Snapshot::Boxed(snapshot)), - ..Default::default() - }); - - let err = runtime2 - .load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - "Cannot load extension module from external code" - ); - } - #[cfg(debug_assertions)] #[test] #[should_panic(expected = "Found ops with duplicate names:")] diff --git a/ext/node/lib.rs b/ext/node/lib.rs index c8242992a0..6ac64eb2eb 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -33,7 +33,6 @@ mod resolution; pub use package_json::PackageJson; pub use path::PathClean; pub use polyfill::is_builtin_node_module; -pub use polyfill::resolve_builtin_node_module; pub use polyfill::NodeModulePolyfill; pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; pub use resolution::NodeModuleKind; diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs index 3d4a1a9fc7..434c20b03d 100644 --- a/ext/node/polyfill.rs +++ b/ext/node/polyfill.rs @@ -1,229 +1,217 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use deno_core::error::generic_error; -use deno_core::error::AnyError; -use deno_core::url::Url; -use deno_core::ModuleSpecifier; - -// TODO(bartlomieju): seems super wasteful to parse the specifier each time -pub fn resolve_builtin_node_module(module_name: &str) -> Result { - if let Some(module) = find_builtin_node_module(module_name) { - return Ok(ModuleSpecifier::parse(module.specifier).unwrap()); - } - - Err(generic_error(format!( - "Unknown built-in \"node:\" module: {module_name}" - ))) -} - -fn find_builtin_node_module(module_name: &str) -> Option<&NodeModulePolyfill> { +/// e.g. `is_builtin_node_module("assert")` +pub fn is_builtin_node_module(module_name: &str) -> bool { SUPPORTED_BUILTIN_NODE_MODULES .iter() - .find(|m| m.name == module_name) -} - -pub fn is_builtin_node_module(module_name: &str) -> bool { - find_builtin_node_module(module_name).is_some() + .any(|m| m.module_name() == module_name) } pub struct NodeModulePolyfill { /// Name of the module like "assert" or "timers/promises" - pub name: &'static str, pub specifier: &'static str, + pub ext_specifier: &'static str, +} + +impl NodeModulePolyfill { + pub fn module_name(&self) -> &'static str { + debug_assert!(self.specifier.starts_with("node:")); + &self.specifier[5..] + } } // NOTE(bartlomieju): keep this list in sync with `ext/node/polyfills/01_require.js` pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ NodeModulePolyfill { - name: "assert", - specifier: "ext:deno_node/assert.ts", + specifier: "node:assert", + ext_specifier: "ext:deno_node/assert.ts", }, NodeModulePolyfill { - name: "assert/strict", - specifier: "ext:deno_node/assert/strict.ts", + specifier: "node:assert/strict", + ext_specifier: "ext:deno_node/assert/strict.ts", }, NodeModulePolyfill { - name: "async_hooks", - specifier: "ext:deno_node/async_hooks.ts", + specifier: "node:async_hooks", + ext_specifier: "ext:deno_node/async_hooks.ts", }, NodeModulePolyfill { - name: "buffer", - specifier: "ext:deno_node/buffer.ts", + specifier: "node:buffer", + ext_specifier: "ext:deno_node/buffer.ts", }, NodeModulePolyfill { - name: "child_process", - specifier: "ext:deno_node/child_process.ts", + specifier: "node:child_process", + ext_specifier: "ext:deno_node/child_process.ts", }, NodeModulePolyfill { - name: "cluster", - specifier: "ext:deno_node/cluster.ts", + specifier: "node:cluster", + ext_specifier: "ext:deno_node/cluster.ts", }, NodeModulePolyfill { - name: "console", - specifier: "ext:deno_node/console.ts", + specifier: "node:console", + ext_specifier: "ext:deno_node/console.ts", }, NodeModulePolyfill { - name: "constants", - specifier: "ext:deno_node/constants.ts", + specifier: "node:constants", + ext_specifier: "ext:deno_node/constants.ts", }, NodeModulePolyfill { - name: "crypto", - specifier: "ext:deno_node/crypto.ts", + specifier: "node:crypto", + ext_specifier: "ext:deno_node/crypto.ts", }, NodeModulePolyfill { - name: "dgram", - specifier: "ext:deno_node/dgram.ts", + specifier: "node:dgram", + ext_specifier: "ext:deno_node/dgram.ts", }, NodeModulePolyfill { - name: "diagnostics_channel", - specifier: "ext:deno_node/diagnostics_channel.ts", + specifier: "node:diagnostics_channel", + ext_specifier: "ext:deno_node/diagnostics_channel.ts", }, NodeModulePolyfill { - name: "dns", - specifier: "ext:deno_node/dns.ts", + specifier: "node:dns", + ext_specifier: "ext:deno_node/dns.ts", }, NodeModulePolyfill { - name: "dns/promises", - specifier: "ext:deno_node/dns/promises.ts", + specifier: "node:dns/promises", + ext_specifier: "ext:deno_node/dns/promises.ts", }, NodeModulePolyfill { - name: "domain", - specifier: "ext:deno_node/domain.ts", + specifier: "node:domain", + ext_specifier: "ext:deno_node/domain.ts", }, NodeModulePolyfill { - name: "events", - specifier: "ext:deno_node/events.ts", + specifier: "node:events", + ext_specifier: "ext:deno_node/events.ts", }, NodeModulePolyfill { - name: "fs", - specifier: "ext:deno_node/fs.ts", + specifier: "node:fs", + ext_specifier: "ext:deno_node/fs.ts", }, NodeModulePolyfill { - name: "fs/promises", - specifier: "ext:deno_node/fs/promises.ts", + specifier: "node:fs/promises", + ext_specifier: "ext:deno_node/fs/promises.ts", }, NodeModulePolyfill { - name: "http", - specifier: "ext:deno_node/http.ts", + specifier: "node:http", + ext_specifier: "ext:deno_node/http.ts", }, NodeModulePolyfill { - name: "http2", - specifier: "ext:deno_node/http2.ts", + specifier: "node:http2", + ext_specifier: "ext:deno_node/http2.ts", }, NodeModulePolyfill { - name: "https", - specifier: "ext:deno_node/https.ts", + specifier: "node:https", + ext_specifier: "ext:deno_node/https.ts", }, NodeModulePolyfill { - name: "module", - specifier: "ext:deno_node/01_require.js", + specifier: "node:module", + ext_specifier: "ext:deno_node/01_require.js", }, NodeModulePolyfill { - name: "net", - specifier: "ext:deno_node/net.ts", + specifier: "node:net", + ext_specifier: "ext:deno_node/net.ts", }, NodeModulePolyfill { - name: "os", - specifier: "ext:deno_node/os.ts", + specifier: "node:os", + ext_specifier: "ext:deno_node/os.ts", }, NodeModulePolyfill { - name: "path", - specifier: "ext:deno_node/path.ts", + specifier: "node:path", + ext_specifier: "ext:deno_node/path.ts", }, NodeModulePolyfill { - name: "path/posix", - specifier: "ext:deno_node/path/posix.ts", + specifier: "node:path/posix", + ext_specifier: "ext:deno_node/path/posix.ts", }, NodeModulePolyfill { - name: "path/win32", - specifier: "ext:deno_node/path/win32.ts", + specifier: "node:path/win32", + ext_specifier: "ext:deno_node/path/win32.ts", }, NodeModulePolyfill { - name: "perf_hooks", - specifier: "ext:deno_node/perf_hooks.ts", + specifier: "node:perf_hooks", + ext_specifier: "ext:deno_node/perf_hooks.ts", }, NodeModulePolyfill { - name: "process", - specifier: "ext:deno_node/process.ts", + specifier: "node:process", + ext_specifier: "ext:deno_node/process.ts", }, NodeModulePolyfill { - name: "punycode", - specifier: "ext:deno_node/punycode.ts", + specifier: "node:punycode", + ext_specifier: "ext:deno_node/punycode.ts", }, NodeModulePolyfill { - name: "querystring", - specifier: "ext:deno_node/querystring.ts", + specifier: "node:querystring", + ext_specifier: "ext:deno_node/querystring.ts", }, NodeModulePolyfill { - name: "readline", - specifier: "ext:deno_node/readline.ts", + specifier: "node:readline", + ext_specifier: "ext:deno_node/readline.ts", }, NodeModulePolyfill { - name: "stream", - specifier: "ext:deno_node/stream.ts", + specifier: "node:stream", + ext_specifier: "ext:deno_node/stream.ts", }, NodeModulePolyfill { - name: "stream/consumers", - specifier: "ext:deno_node/stream/consumers.mjs", + specifier: "node:stream/consumers", + ext_specifier: "ext:deno_node/stream/consumers.mjs", }, NodeModulePolyfill { - name: "stream/promises", - specifier: "ext:deno_node/stream/promises.mjs", + specifier: "node:stream/promises", + ext_specifier: "ext:deno_node/stream/promises.mjs", }, NodeModulePolyfill { - name: "stream/web", - specifier: "ext:deno_node/stream/web.ts", + specifier: "node:stream/web", + ext_specifier: "ext:deno_node/stream/web.ts", }, NodeModulePolyfill { - name: "string_decoder", - specifier: "ext:deno_node/string_decoder.ts", + specifier: "node:string_decoder", + ext_specifier: "ext:deno_node/string_decoder.ts", }, NodeModulePolyfill { - name: "sys", - specifier: "ext:deno_node/sys.ts", + specifier: "node:sys", + ext_specifier: "ext:deno_node/sys.ts", }, NodeModulePolyfill { - name: "timers", - specifier: "ext:deno_node/timers.ts", + specifier: "node:timers", + ext_specifier: "ext:deno_node/timers.ts", }, NodeModulePolyfill { - name: "timers/promises", - specifier: "ext:deno_node/timers/promises.ts", + specifier: "node:timers/promises", + ext_specifier: "ext:deno_node/timers/promises.ts", }, NodeModulePolyfill { - name: "tls", - specifier: "ext:deno_node/tls.ts", + specifier: "node:tls", + ext_specifier: "ext:deno_node/tls.ts", }, NodeModulePolyfill { - name: "tty", - specifier: "ext:deno_node/tty.ts", + specifier: "node:tty", + ext_specifier: "ext:deno_node/tty.ts", }, NodeModulePolyfill { - name: "url", - specifier: "ext:deno_node/url.ts", + specifier: "node:url", + ext_specifier: "ext:deno_node/url.ts", }, NodeModulePolyfill { - name: "util", - specifier: "ext:deno_node/util.ts", + specifier: "node:util", + ext_specifier: "ext:deno_node/util.ts", }, NodeModulePolyfill { - name: "util/types", - specifier: "ext:deno_node/util/types.ts", + specifier: "node:util/types", + ext_specifier: "ext:deno_node/util/types.ts", }, NodeModulePolyfill { - name: "v8", - specifier: "ext:deno_node/v8.ts", + specifier: "node:v8", + ext_specifier: "ext:deno_node/v8.ts", }, NodeModulePolyfill { - name: "vm", - specifier: "ext:deno_node/vm.ts", + specifier: "node:vm", + ext_specifier: "ext:deno_node/vm.ts", }, NodeModulePolyfill { - name: "worker_threads", - specifier: "ext:deno_node/worker_threads.ts", + specifier: "node:worker_threads", + ext_specifier: "ext:deno_node/worker_threads.ts", }, NodeModulePolyfill { - name: "zlib", - specifier: "ext:deno_node/zlib.ts", + specifier: "node:zlib", + ext_specifier: "ext:deno_node/zlib.ts", }, ]; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 01262abcf2..36f9718b51 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -4,6 +4,7 @@ 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; @@ -495,6 +496,7 @@ impl WebWorker { inspector: options.maybe_inspector_server.is_some(), ..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 ae6bd717f6..0e62decb46 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -57,6 +57,17 @@ impl ExitCode { self.0.store(code, Relaxed); } } + +/// 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. /// @@ -319,6 +330,7 @@ impl MainWorker { is_main: true, ..Default::default() }); + init_runtime_module_map(&mut js_runtime); if let Some(server) = options.maybe_inspector_server.clone() { server.register_inspector(