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
.iter()
.map(|s| s.name)
.map(|p| p.module_name())
.collect::<Vec<&str>>();
let build_libs = state.borrow::<Vec<&str>>();
json!({

View file

@ -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(

View file

@ -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 {

View file

@ -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

View file

@ -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())
}

View file

@ -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())

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");
^
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",
"https",
]
at [WILDCARD]
at [WILDCARD]/extension_import.ts:1:8

View file

@ -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

View file

@ -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

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::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;

View file

@ -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<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>;
pub struct ExtModuleLoader {
module_loader: Rc<dyn ModuleLoader>,
extensions: Rc<RefCell<Vec<Extension>>>,
ext_resolution_allowed: RefCell<bool>,
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,
}
}
pub(crate) struct ExtModuleLoader {
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
sources: RefCell<HashMap<String, ExtensionFileSource>>,
used_specifiers: RefCell<HashSet<String>>,
}
impl ExtModuleLoader {
pub fn new(
module_loader: Option<Rc<dyn ModuleLoader>>,
extensions: Rc<RefCell<Vec<Extension>>>,
maybe_load_callback: Option<ExtModuleLoaderCb>,
extensions: &[Extension],
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
) -> Self {
let used_esm_sources: HashMap<String, bool> = 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<ModuleSpecifier, Error> {
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<Box<ModuleSourceFuture>> {
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<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
maybe_referrer: Option<String>,
is_dyn_import: bool,
_op_state: Rc<RefCell<OpState>>,
_specifier: &ModuleSpecifier,
_maybe_referrer: Option<String>,
_is_dyn_import: bool,
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
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<RefCell<OpState>>,
loader: Rc<ExtModuleLoader>,
loader: Rc<dyn ModuleLoader>,
}
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<ExtModuleLoader>,
pub loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>,
pub(crate) dynamic_import_map:
HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
@ -1369,7 +1305,7 @@ impl ModuleMap {
}
pub(crate) fn new(
loader: Rc<ExtModuleLoader>,
loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>,
) -> 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<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,
@ -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())
);
}
}

View file

@ -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<v8::OwnedIsolate>,
snapshot_options: snapshot_util::SnapshotOptions,
snapshot_module_load_cb: Option<Rc<ExtModuleLoaderCb>>,
allocations: IsolateAllocations,
extensions: Rc<RefCell<Vec<Extension>>>,
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(
@ -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<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(
&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<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)]
#[test]
#[should_panic(expected = "Found ops with duplicate names:")]

View file

@ -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;

View file

@ -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<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> {
/// 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",
},
];

View file

@ -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(

View file

@ -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(