1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 16:42:21 -05:00

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.
This commit is contained in:
Nayeem Rahman 2023-05-28 19:44:41 +01:00 committed by GitHub
parent bb0676d3e2
commit b6a3f8f722
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 261 additions and 403 deletions

View file

@ -40,7 +40,7 @@ mod ts {
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
.iter() .iter()
.map(|s| s.name) .map(|p| p.module_name())
.collect::<Vec<&str>>(); .collect::<Vec<&str>>();
let build_libs = state.borrow::<Vec<&str>>(); let build_libs = state.borrow::<Vec<&str>>();
json!({ json!({

View file

@ -378,9 +378,8 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String {
pub fn get_resolution_error_bare_node_specifier( pub fn get_resolution_error_bare_node_specifier(
error: &ResolutionError, error: &ResolutionError,
) -> Option<&str> { ) -> Option<&str> {
get_resolution_error_bare_specifier(error).filter(|specifier| { get_resolution_error_bare_specifier(error)
deno_node::resolve_builtin_node_module(specifier).is_ok() .filter(|specifier| deno_node::is_builtin_node_module(specifier))
})
} }
fn get_resolution_error_bare_specifier( fn get_resolution_error_bare_specifier(

View file

@ -1023,7 +1023,7 @@ fn diagnose_resolution(
} }
} else if let Some(module_name) = specifier.as_str().strip_prefix("node:") } 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 diagnostics
.push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())); .push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone()));
} else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver { } else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {

View file

@ -1090,7 +1090,7 @@ impl Documents {
} }
} }
if let Some(module_name) = specifier.strip_prefix("node:") { 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 // return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package // we resolve to the ambient modules in the @types/node package
// rather than deno_std/node // rather than deno_std/node

View file

@ -47,7 +47,6 @@ use deno_graph::Module;
use deno_graph::Resolution; use deno_graph::Resolution;
use deno_lockfile::Lockfile; use deno_lockfile::Lockfile;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node;
use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolution;
use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
@ -496,9 +495,7 @@ impl ModuleLoader for CliModuleLoader {
.shared .shared
.npm_module_loader .npm_module_loader
.resolve_nv_ref(&module.nv_reference, permissions), .resolve_nv_ref(&module.nv_reference, permissions),
Some(Module::Node(module)) => { Some(Module::Node(module)) => Ok(module.specifier.clone()),
deno_node::resolve_builtin_node_module(&module.module_name)
}
Some(Module::Esm(module)) => Ok(module.specifier.clone()), Some(Module::Esm(module)) => Ok(module.specifier.clone()),
Some(Module::Json(module)) => Ok(module.specifier.clone()), Some(Module::Json(module)) => Ok(module.specifier.clone()),
Some(Module::External(module)) => { 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 // 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 // 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. // 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 { if let NodeResolution::CommonJs(specifier) = &response {
// remember that this was a common js resolution // remember that this was a common js resolution
self.cjs_resolutions.insert(specifier.clone()); 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()) Ok(response.into_url())
} }

View file

@ -39,7 +39,6 @@ use deno_core::ModuleType;
use deno_core::ResolutionKind; use deno_core::ResolutionKind;
use deno_npm::NpmSystemInfo; use deno_npm::NpmSystemInfo;
use deno_runtime::deno_fs; use deno_runtime::deno_fs;
use deno_runtime::deno_node;
use deno_runtime::deno_node::analyze::NodeCodeTranslator; use deno_runtime::deno_node::analyze::NodeCodeTranslator;
use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::rustls::RootCertStore;
@ -128,11 +127,6 @@ impl ModuleLoader for EmbeddedModuleLoader {
.resolve_req_reference(&reference, permissions); .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 { match maybe_mapped {
Some(resolved) => Ok(resolved), Some(resolved) => Ok(resolved),
None => deno_core::resolve_import(specifier, referrer.as_str()) None => deno_core::resolve_import(specifier, referrer.as_str())

View file

@ -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"); await import("ext:runtime/01_errors.js");
^ ^
at [WILDCARD]/extension_dynamic_import.ts:1:1 at async [WILDCARD]/extension_dynamic_import.ts:1:1

View file

@ -5,4 +5,4 @@ error: Unsupported scheme "ext" for module "ext:runtime/01_errors.js". Supported
"http", "http",
"https", "https",
] ]
at [WILDCARD] at [WILDCARD]/extension_import.ts:1:8

View file

@ -538,7 +538,7 @@ fn op_resolve(
}; };
for specifier in args.specifiers { for specifier in args.specifiers {
if let Some(module_name) = specifier.strip_prefix("node:") { 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 // return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package // we resolve to the ambient modules in the @types/node package
// rather than deno_std/node // rather than deno_std/node

View file

@ -471,16 +471,6 @@ impl Extension {
pub fn disable(self) -> Self { pub fn disable(self) -> Self {
self.enabled(false) 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 // Provides a convenient builder pattern to declare Extensions

View file

@ -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::resolve_url_or_path;
pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleResolutionError;
pub use crate::module_specifier::ModuleSpecifier; pub use crate::module_specifier::ModuleSpecifier;
pub use crate::modules::ExtModuleLoader;
pub use crate::modules::ExtModuleLoaderCb; pub use crate::modules::ExtModuleLoaderCb;
pub use crate::modules::FsModuleLoader; pub use crate::modules::FsModuleLoader;
pub use crate::modules::ModuleCode; pub use crate::modules::ModuleCode;

View file

@ -12,8 +12,8 @@ use crate::snapshot_util::SnapshottedData;
use crate::Extension; use crate::Extension;
use crate::JsRuntime; use crate::JsRuntime;
use crate::OpState; use crate::OpState;
use anyhow::anyhow;
use anyhow::Error; use anyhow::Error;
use core::panic;
use futures::future::FutureExt; use futures::future::FutureExt;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::stream::Stream; use futures::stream::Stream;
@ -385,154 +385,90 @@ impl ModuleLoader for NoopModuleLoader {
pub type ExtModuleLoaderCb = pub type ExtModuleLoaderCb =
Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>; Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>;
pub struct ExtModuleLoader { pub(crate) struct ExtModuleLoader {
module_loader: Rc<dyn ModuleLoader>, maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
extensions: Rc<RefCell<Vec<Extension>>>, sources: RefCell<HashMap<String, ExtensionFileSource>>,
ext_resolution_allowed: RefCell<bool>, used_specifiers: RefCell<HashSet<String>>,
used_esm_sources: RefCell<HashMap<String, bool>>,
maybe_load_callback: Option<ExtModuleLoaderCb>,
}
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,
}
}
} }
impl ExtModuleLoader { impl ExtModuleLoader {
pub fn new( pub fn new(
module_loader: Option<Rc<dyn ModuleLoader>>, extensions: &[Extension],
extensions: Rc<RefCell<Vec<Extension>>>, maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
maybe_load_callback: Option<ExtModuleLoaderCb>,
) -> Self { ) -> Self {
let used_esm_sources: HashMap<String, bool> = extensions let mut sources = HashMap::new();
.borrow() sources.extend(
.iter() extensions
.flat_map(|e| e.get_esm_sources()) .iter()
.flatten() .flat_map(|e| e.get_esm_sources())
.map(|file_source| (file_source.specifier.to_string(), false)) .flatten()
.collect(); .map(|s| (s.specifier.to_string(), s.clone())),
);
ExtModuleLoader { 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, maybe_load_callback,
sources: RefCell::new(sources),
used_specifiers: Default::default(),
} }
} }
}
pub fn resolve( impl ModuleLoader for ExtModuleLoader {
fn resolve(
&self, &self,
specifier: &str, specifier: &str,
referrer: &str, referrer: &str,
kind: ResolutionKind, _kind: ResolutionKind,
) -> Result<ModuleSpecifier, Error> { ) -> Result<ModuleSpecifier, Error> {
if specifier.starts_with("ext:") { Ok(resolve_import(specifier, referrer)?)
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)
} }
pub fn load( fn load(
&self, &self,
module_specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>, _maybe_referrer: Option<&ModuleSpecifier>,
is_dyn_import: bool, _is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> { ) -> Pin<Box<ModuleSourceFuture>> {
if module_specifier.scheme() != "ext" { let sources = self.sources.borrow();
return self.module_loader.load( let source = match sources.get(specifier.as_str()) {
module_specifier, Some(source) => source,
maybe_referrer, None => return futures::future::err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier)).boxed_local(),
is_dyn_import, };
); self
} .used_specifiers
.borrow_mut()
let specifier = module_specifier.to_string(); .insert(specifier.to_string());
let extensions = self.extensions.borrow(); let result = if let Some(load_callback) = &self.maybe_load_callback {
let maybe_file_source = extensions load_callback(source)
.iter() } else {
.find_map(|e| e.find_esm(module_specifier.as_str())); source.load()
};
if let Some(file_source) = maybe_file_source { match result {
{ Ok(code) => {
let mut used_esm_sources = self.used_esm_sources.borrow_mut(); let res = ModuleSource::new(ModuleType::JavaScript, code, specifier);
let used = used_esm_sources.get_mut(file_source.specifier).unwrap(); return futures::future::ok(res).boxed_local();
*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(),
} }
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, &self,
op_state: Rc<RefCell<OpState>>, _op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier, _specifier: &ModuleSpecifier,
maybe_referrer: Option<String>, _maybe_referrer: Option<String>,
is_dyn_import: bool, _is_dyn_import: bool,
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> { ) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
if module_specifier.scheme() == "ext" { async { Ok(()) }.boxed_local()
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;
} }
} }
impl Drop for ExtModuleLoader { impl Drop for ExtModuleLoader {
fn drop(&mut self) { fn drop(&mut self) {
let used_esm_sources = self.used_esm_sources.get_mut(); let sources = self.sources.get_mut();
let unused_modules: Vec<_> = used_esm_sources let used_specifiers = self.used_specifiers.get_mut();
let unused_modules: Vec<_> = sources
.iter() .iter()
.filter(|(_s, v)| !*v) .filter(|(k, _)| !used_specifiers.contains(k.as_str()))
.map(|(s, _)| s)
.collect(); .collect();
if !unused_modules.is_empty() { if !unused_modules.is_empty() {
@ -541,7 +477,7 @@ impl Drop for ExtModuleLoader {
.to_string(); .to_string();
for m in unused_modules { for m in unused_modules {
msg.push_str(" - "); msg.push_str(" - ");
msg.push_str(m); msg.push_str(m.0);
msg.push('\n'); msg.push('\n');
} }
panic!("{}", msg); panic!("{}", msg);
@ -634,7 +570,7 @@ pub(crate) struct RecursiveModuleLoad {
// These three fields are copied from `module_map_rc`, but they are cloned // These three fields are copied from `module_map_rc`, but they are cloned
// ahead of time to avoid already-borrowed errors. // ahead of time to avoid already-borrowed errors.
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
loader: Rc<ExtModuleLoader>, loader: Rc<dyn ModuleLoader>,
} }
impl RecursiveModuleLoad { impl RecursiveModuleLoad {
@ -1060,7 +996,7 @@ pub(crate) struct ModuleMap {
pub(crate) next_load_id: ModuleLoadId, pub(crate) next_load_id: ModuleLoadId,
// Handling of futures for loading module sources // Handling of futures for loading module sources
pub loader: Rc<ExtModuleLoader>, pub loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
pub(crate) dynamic_import_map: pub(crate) dynamic_import_map:
HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>, HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
@ -1369,7 +1305,7 @@ impl ModuleMap {
} }
pub(crate) fn new( pub(crate) fn new(
loader: Rc<ExtModuleLoader>, loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
) -> ModuleMap { ) -> ModuleMap {
Self { Self {
@ -1556,6 +1492,29 @@ impl ModuleMap {
Ok(id) 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<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( fn create_module_info(
&mut self, &mut self,
name: FastString, name: FastString,
@ -3005,37 +2964,4 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
) )
.unwrap(); .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())
);
}
} }

View file

@ -9,6 +9,7 @@ use crate::extensions::OpEventLoopFn;
use crate::inspector::JsRuntimeInspector; use crate::inspector::JsRuntimeInspector;
use crate::module_specifier::ModuleSpecifier; use crate::module_specifier::ModuleSpecifier;
use crate::modules::AssertedModuleType; use crate::modules::AssertedModuleType;
use crate::modules::ExtModuleLoader;
use crate::modules::ExtModuleLoaderCb; use crate::modules::ExtModuleLoaderCb;
use crate::modules::ModuleCode; use crate::modules::ModuleCode;
use crate::modules::ModuleError; use crate::modules::ModuleError;
@ -16,6 +17,7 @@ use crate::modules::ModuleId;
use crate::modules::ModuleLoadId; use crate::modules::ModuleLoadId;
use crate::modules::ModuleLoader; use crate::modules::ModuleLoader;
use crate::modules::ModuleMap; use crate::modules::ModuleMap;
use crate::modules::ModuleName;
use crate::ops::*; use crate::ops::*;
use crate::realm::ContextState; use crate::realm::ContextState;
use crate::realm::JsRealm; use crate::realm::JsRealm;
@ -24,6 +26,7 @@ use crate::snapshot_util;
use crate::source_map::SourceMapCache; use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter; use crate::source_map::SourceMapGetter;
use crate::Extension; use crate::Extension;
use crate::ModuleType;
use crate::NoopModuleLoader; use crate::NoopModuleLoader;
use crate::OpMiddlewareFn; use crate::OpMiddlewareFn;
use crate::OpResult; use crate::OpResult;
@ -88,6 +91,7 @@ pub struct JsRuntime {
// a safety issue with SnapshotCreator. See JsRuntime::drop. // a safety issue with SnapshotCreator. See JsRuntime::drop.
v8_isolate: Option<v8::OwnedIsolate>, v8_isolate: Option<v8::OwnedIsolate>,
snapshot_options: snapshot_util::SnapshotOptions, snapshot_options: snapshot_util::SnapshotOptions,
snapshot_module_load_cb: Option<Rc<ExtModuleLoaderCb>>,
allocations: IsolateAllocations, allocations: IsolateAllocations,
extensions: Rc<RefCell<Vec<Extension>>>, extensions: Rc<RefCell<Vec<Extension>>>,
event_loop_middlewares: Vec<Box<OpEventLoopFn>>, event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
@ -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( let global_realm = JsRealmInner::new(
@ -530,8 +527,7 @@ impl JsRuntime {
Self::STATE_DATA_OFFSET, Self::STATE_DATA_OFFSET,
Rc::into_raw(state_rc.clone()) as *mut c_void, Rc::into_raw(state_rc.clone()) as *mut c_void,
); );
let module_map_rc = let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state)));
Rc::new(RefCell::new(ModuleMap::new(ext_loader, op_state)));
if let Some(snapshotted_data) = maybe_snapshotted_data { if let Some(snapshotted_data) = maybe_snapshotted_data {
let scope = let scope =
&mut v8::HandleScope::with_context(&mut isolate, global_context); &mut v8::HandleScope::with_context(&mut isolate, global_context);
@ -546,11 +542,12 @@ impl JsRuntime {
let mut js_runtime = Self { let mut js_runtime = Self {
v8_isolate: Some(isolate), v8_isolate: Some(isolate),
snapshot_options, snapshot_options,
snapshot_module_load_cb: options.snapshot_module_load_cb.map(Rc::new),
allocations: IsolateAllocations::default(), allocations: IsolateAllocations::default(),
event_loop_middlewares: Vec::with_capacity(num_extensions), event_loop_middlewares: Vec::with_capacity(options.extensions.len()),
extensions, extensions: Rc::new(RefCell::new(options.extensions)),
state: state_rc, state: state_rc,
module_map: Some(module_map_rc.clone()), module_map: Some(module_map_rc),
is_main: options.is_main, is_main: options.is_main,
}; };
@ -558,9 +555,7 @@ impl JsRuntime {
// available during the initialization process. // available during the initialization process.
js_runtime.init_extension_ops().unwrap(); js_runtime.init_extension_ops().unwrap();
let realm = js_runtime.global_realm(); let realm = js_runtime.global_realm();
module_map_rc.borrow().loader.allow_ext_resolution();
js_runtime.init_extension_js(&realm).unwrap(); js_runtime.init_extension_js(&realm).unwrap();
module_map_rc.borrow().loader.disallow_ext_resolution();
js_runtime js_runtime
} }
@ -666,21 +661,7 @@ impl JsRuntime {
JsRealm::new(realm) JsRealm::new(realm)
}; };
self
.module_map
.as_ref()
.unwrap()
.borrow()
.loader
.allow_ext_resolution();
self.init_extension_js(&realm)?; self.init_extension_js(&realm)?;
self
.module_map
.as_ref()
.unwrap()
.borrow()
.loader
.disallow_ext_resolution();
Ok(realm) Ok(realm)
} }
@ -735,6 +716,15 @@ impl JsRuntime {
// 2. Iterate through all extensions: // 2. Iterate through all extensions:
// a. If an extension has a `esm_entry_point`, execute it. // 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![]; let mut esm_entrypoints = vec![];
// Take extensions to avoid double-borrow // Take extensions to avoid double-borrow
@ -816,6 +806,7 @@ impl JsRuntime {
// Restore extensions // Restore extensions
self.extensions = extensions; self.extensions = extensions;
self.module_map.as_ref().unwrap().borrow_mut().loader = loader;
Ok(()) Ok(())
} }
@ -1865,6 +1856,29 @@ impl JsRuntime {
receiver 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<Item = (&'static str, &'static str)>,
) {
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::<Vec<_>>();
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( fn dynamic_import_reject(
&mut self, &mut self,
id: ModuleLoadId, id: ModuleLoadId,
@ -4774,67 +4788,6 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
.is_ok()); .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<ModuleSpecifier, Error> {
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<Box<ModuleSourceFuture>> {
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)] #[cfg(debug_assertions)]
#[test] #[test]
#[should_panic(expected = "Found ops with duplicate names:")] #[should_panic(expected = "Found ops with duplicate names:")]

View file

@ -33,7 +33,6 @@ mod resolution;
pub use package_json::PackageJson; pub use package_json::PackageJson;
pub use path::PathClean; pub use path::PathClean;
pub use polyfill::is_builtin_node_module; pub use polyfill::is_builtin_node_module;
pub use polyfill::resolve_builtin_node_module;
pub use polyfill::NodeModulePolyfill; pub use polyfill::NodeModulePolyfill;
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES;
pub use resolution::NodeModuleKind; pub use resolution::NodeModuleKind;

View file

@ -1,229 +1,217 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::error::generic_error; /// e.g. `is_builtin_node_module("assert")`
use deno_core::error::AnyError; pub fn is_builtin_node_module(module_name: &str) -> bool {
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<Url, AnyError> {
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> {
SUPPORTED_BUILTIN_NODE_MODULES SUPPORTED_BUILTIN_NODE_MODULES
.iter() .iter()
.find(|m| m.name == module_name) .any(|m| m.module_name() == module_name)
}
pub fn is_builtin_node_module(module_name: &str) -> bool {
find_builtin_node_module(module_name).is_some()
} }
pub struct NodeModulePolyfill { pub struct NodeModulePolyfill {
/// Name of the module like "assert" or "timers/promises" /// Name of the module like "assert" or "timers/promises"
pub name: &'static str,
pub specifier: &'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` // NOTE(bartlomieju): keep this list in sync with `ext/node/polyfills/01_require.js`
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[
NodeModulePolyfill { NodeModulePolyfill {
name: "assert", specifier: "node:assert",
specifier: "ext:deno_node/assert.ts", ext_specifier: "ext:deno_node/assert.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "assert/strict", specifier: "node:assert/strict",
specifier: "ext:deno_node/assert/strict.ts", ext_specifier: "ext:deno_node/assert/strict.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "async_hooks", specifier: "node:async_hooks",
specifier: "ext:deno_node/async_hooks.ts", ext_specifier: "ext:deno_node/async_hooks.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "buffer", specifier: "node:buffer",
specifier: "ext:deno_node/buffer.ts", ext_specifier: "ext:deno_node/buffer.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "child_process", specifier: "node:child_process",
specifier: "ext:deno_node/child_process.ts", ext_specifier: "ext:deno_node/child_process.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "cluster", specifier: "node:cluster",
specifier: "ext:deno_node/cluster.ts", ext_specifier: "ext:deno_node/cluster.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "console", specifier: "node:console",
specifier: "ext:deno_node/console.ts", ext_specifier: "ext:deno_node/console.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "constants", specifier: "node:constants",
specifier: "ext:deno_node/constants.ts", ext_specifier: "ext:deno_node/constants.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "crypto", specifier: "node:crypto",
specifier: "ext:deno_node/crypto.ts", ext_specifier: "ext:deno_node/crypto.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "dgram", specifier: "node:dgram",
specifier: "ext:deno_node/dgram.ts", ext_specifier: "ext:deno_node/dgram.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "diagnostics_channel", specifier: "node:diagnostics_channel",
specifier: "ext:deno_node/diagnostics_channel.ts", ext_specifier: "ext:deno_node/diagnostics_channel.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "dns", specifier: "node:dns",
specifier: "ext:deno_node/dns.ts", ext_specifier: "ext:deno_node/dns.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "dns/promises", specifier: "node:dns/promises",
specifier: "ext:deno_node/dns/promises.ts", ext_specifier: "ext:deno_node/dns/promises.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "domain", specifier: "node:domain",
specifier: "ext:deno_node/domain.ts", ext_specifier: "ext:deno_node/domain.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "events", specifier: "node:events",
specifier: "ext:deno_node/events.ts", ext_specifier: "ext:deno_node/events.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "fs", specifier: "node:fs",
specifier: "ext:deno_node/fs.ts", ext_specifier: "ext:deno_node/fs.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "fs/promises", specifier: "node:fs/promises",
specifier: "ext:deno_node/fs/promises.ts", ext_specifier: "ext:deno_node/fs/promises.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "http", specifier: "node:http",
specifier: "ext:deno_node/http.ts", ext_specifier: "ext:deno_node/http.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "http2", specifier: "node:http2",
specifier: "ext:deno_node/http2.ts", ext_specifier: "ext:deno_node/http2.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "https", specifier: "node:https",
specifier: "ext:deno_node/https.ts", ext_specifier: "ext:deno_node/https.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "module", specifier: "node:module",
specifier: "ext:deno_node/01_require.js", ext_specifier: "ext:deno_node/01_require.js",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "net", specifier: "node:net",
specifier: "ext:deno_node/net.ts", ext_specifier: "ext:deno_node/net.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "os", specifier: "node:os",
specifier: "ext:deno_node/os.ts", ext_specifier: "ext:deno_node/os.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "path", specifier: "node:path",
specifier: "ext:deno_node/path.ts", ext_specifier: "ext:deno_node/path.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "path/posix", specifier: "node:path/posix",
specifier: "ext:deno_node/path/posix.ts", ext_specifier: "ext:deno_node/path/posix.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "path/win32", specifier: "node:path/win32",
specifier: "ext:deno_node/path/win32.ts", ext_specifier: "ext:deno_node/path/win32.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "perf_hooks", specifier: "node:perf_hooks",
specifier: "ext:deno_node/perf_hooks.ts", ext_specifier: "ext:deno_node/perf_hooks.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "process", specifier: "node:process",
specifier: "ext:deno_node/process.ts", ext_specifier: "ext:deno_node/process.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "punycode", specifier: "node:punycode",
specifier: "ext:deno_node/punycode.ts", ext_specifier: "ext:deno_node/punycode.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "querystring", specifier: "node:querystring",
specifier: "ext:deno_node/querystring.ts", ext_specifier: "ext:deno_node/querystring.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "readline", specifier: "node:readline",
specifier: "ext:deno_node/readline.ts", ext_specifier: "ext:deno_node/readline.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "stream", specifier: "node:stream",
specifier: "ext:deno_node/stream.ts", ext_specifier: "ext:deno_node/stream.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "stream/consumers", specifier: "node:stream/consumers",
specifier: "ext:deno_node/stream/consumers.mjs", ext_specifier: "ext:deno_node/stream/consumers.mjs",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "stream/promises", specifier: "node:stream/promises",
specifier: "ext:deno_node/stream/promises.mjs", ext_specifier: "ext:deno_node/stream/promises.mjs",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "stream/web", specifier: "node:stream/web",
specifier: "ext:deno_node/stream/web.ts", ext_specifier: "ext:deno_node/stream/web.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "string_decoder", specifier: "node:string_decoder",
specifier: "ext:deno_node/string_decoder.ts", ext_specifier: "ext:deno_node/string_decoder.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "sys", specifier: "node:sys",
specifier: "ext:deno_node/sys.ts", ext_specifier: "ext:deno_node/sys.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "timers", specifier: "node:timers",
specifier: "ext:deno_node/timers.ts", ext_specifier: "ext:deno_node/timers.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "timers/promises", specifier: "node:timers/promises",
specifier: "ext:deno_node/timers/promises.ts", ext_specifier: "ext:deno_node/timers/promises.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "tls", specifier: "node:tls",
specifier: "ext:deno_node/tls.ts", ext_specifier: "ext:deno_node/tls.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "tty", specifier: "node:tty",
specifier: "ext:deno_node/tty.ts", ext_specifier: "ext:deno_node/tty.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "url", specifier: "node:url",
specifier: "ext:deno_node/url.ts", ext_specifier: "ext:deno_node/url.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "util", specifier: "node:util",
specifier: "ext:deno_node/util.ts", ext_specifier: "ext:deno_node/util.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "util/types", specifier: "node:util/types",
specifier: "ext:deno_node/util/types.ts", ext_specifier: "ext:deno_node/util/types.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "v8", specifier: "node:v8",
specifier: "ext:deno_node/v8.ts", ext_specifier: "ext:deno_node/v8.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "vm", specifier: "node:vm",
specifier: "ext:deno_node/vm.ts", ext_specifier: "ext:deno_node/vm.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "worker_threads", specifier: "node:worker_threads",
specifier: "ext:deno_node/worker_threads.ts", ext_specifier: "ext:deno_node/worker_threads.ts",
}, },
NodeModulePolyfill { NodeModulePolyfill {
name: "zlib", specifier: "node:zlib",
specifier: "ext:deno_node/zlib.ts", ext_specifier: "ext:deno_node/zlib.ts",
}, },
]; ];

View file

@ -4,6 +4,7 @@ use crate::inspector_server::InspectorServer;
use crate::ops; use crate::ops;
use crate::permissions::PermissionsContainer; use crate::permissions::PermissionsContainer;
use crate::tokio_util::create_and_run_current_thread; use crate::tokio_util::create_and_run_current_thread;
use crate::worker::init_runtime_module_map;
use crate::worker::FormatJsErrorFn; use crate::worker::FormatJsErrorFn;
use crate::BootstrapOptions; use crate::BootstrapOptions;
use deno_broadcast_channel::InMemoryBroadcastChannel; use deno_broadcast_channel::InMemoryBroadcastChannel;
@ -495,6 +496,7 @@ impl WebWorker {
inspector: options.maybe_inspector_server.is_some(), inspector: options.maybe_inspector_server.is_some(),
..Default::default() ..Default::default()
}); });
init_runtime_module_map(&mut js_runtime);
if let Some(server) = options.maybe_inspector_server.clone() { if let Some(server) = options.maybe_inspector_server.clone() {
server.register_inspector( server.register_inspector(

View file

@ -57,6 +57,17 @@ impl ExitCode {
self.0.store(code, Relaxed); 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 /// This worker is created and used by almost all
/// subcommands in Deno executable. /// subcommands in Deno executable.
/// ///
@ -319,6 +330,7 @@ impl MainWorker {
is_main: true, is_main: true,
..Default::default() ..Default::default()
}); });
init_runtime_module_map(&mut js_runtime);
if let Some(server) = options.maybe_inspector_server.clone() { if let Some(server) = options.maybe_inspector_server.clone() {
server.register_inspector( server.register_inspector(