1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

refactor(core): some runtime methods should live on the module map (#19502)

A few easy migrations of module code from the runtime to the module map.

The module map already has a few places where it needs a handle scope,
so we're not coupling it any further with the v8 runtime.

`init_runtime_module_map` is replaced with an option to reduce API
surface of JsRuntime.

`module_resolve_callback` now lives in the `ModuleMap` and we use a
annex data to avoid having to go through the `Rc<RefCell<...>>` stored
in the `JsRuntime`'s isolate.
This commit is contained in:
Matt Mastracci 2023-06-14 10:45:59 -06:00 committed by GitHub
parent cd27757184
commit 88e6e9c1e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 214 deletions

View file

@ -695,6 +695,12 @@ pub(crate) fn exception_to_err_result<T>(
Err(js_error.into())
}
pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
let message = v8::String::new(scope, message.as_ref()).unwrap();
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -127,6 +127,7 @@ pub fn v8_version() -> &'static str {
/// An internal module re-exporting functions used by the #[op] (`deno_ops`) macro
#[doc(hidden)]
pub mod _ops {
pub use super::error::throw_type_error;
pub use super::error_codes::get_error_code;
pub use super::ops::to_op_result;
pub use super::ops::OpCtx;
@ -137,7 +138,6 @@ pub mod _ops {
pub use super::runtime::ops::map_async_op4;
pub use super::runtime::ops::queue_async_op;
pub use super::runtime::ops::queue_fast_async_op;
pub use super::runtime::throw_type_error;
pub use super::runtime::V8_WRAPPER_OBJECT_INDEX;
pub use super::runtime::V8_WRAPPER_TYPE_INDEX;
}

View file

@ -1,5 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use crate::error::exception_to_err_result;
use crate::error::generic_error;
use crate::error::throw_type_error;
use crate::fast_string::FastString;
use crate::modules::get_asserted_module_type_from_assertions;
use crate::modules::parse_import_assertions;
@ -554,6 +556,105 @@ impl ModuleMap {
Ok(id)
}
pub(crate) fn instantiate_module(
&mut self,
scope: &mut v8::HandleScope,
id: ModuleId,
) -> Result<(), v8::Global<v8::Value>> {
let tc_scope = &mut v8::TryCatch::new(scope);
let module = self
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
if module.get_status() == v8::ModuleStatus::Errored {
return Err(v8::Global::new(tc_scope, module.get_exception()));
}
tc_scope.set_slot(self as *const _);
let instantiate_result =
module.instantiate_module(tc_scope, Self::module_resolve_callback);
tc_scope.remove_slot::<*const Self>();
if instantiate_result.is_none() {
let exception = tc_scope.exception().unwrap();
return Err(v8::Global::new(tc_scope, exception));
}
Ok(())
}
/// Called by V8 during `JsRuntime::instantiate_module`. This is only used internally, so we use the Isolate's annex
/// to propagate a &Self.
fn module_resolve_callback<'s>(
context: v8::Local<'s, v8::Context>,
specifier: v8::Local<'s, v8::String>,
import_assertions: v8::Local<'s, v8::FixedArray>,
referrer: v8::Local<'s, v8::Module>,
) -> Option<v8::Local<'s, v8::Module>> {
// SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let module_map =
// SAFETY: We retrieve the pointer from the slot, having just set it a few stack frames up
unsafe { scope.get_slot::<*const Self>().unwrap().as_ref().unwrap() };
let referrer_global = v8::Global::new(scope, referrer);
let referrer_info = module_map
.get_info(&referrer_global)
.expect("ModuleInfo not found");
let referrer_name = referrer_info.name.as_str();
let specifier_str = specifier.to_rust_string_lossy(scope);
let assertions = parse_import_assertions(
scope,
import_assertions,
ImportAssertionsKind::StaticImport,
);
let maybe_module = module_map.resolve_callback(
scope,
&specifier_str,
referrer_name,
assertions,
);
if let Some(module) = maybe_module {
return Some(module);
}
let msg = format!(
r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""#
);
throw_type_error(scope, msg);
None
}
/// Called by `module_resolve_callback` during module instantiation.
fn resolve_callback<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
specifier: &str,
referrer: &str,
import_assertions: HashMap<String, String>,
) -> Option<v8::Local<'s, v8::Module>> {
let resolved_specifier = self
.loader
.resolve(specifier, referrer, ResolutionKind::Import)
.expect("Module should have been already resolved");
let module_type =
get_asserted_module_type_from_assertions(&import_assertions);
if let Some(id) = self.get_id(resolved_specifier.as_str(), module_type) {
if let Some(handle) = self.get_handle(id) {
return Some(v8::Local::new(scope, handle));
}
}
None
}
pub(crate) fn clear(&mut self) {
*self = Self::new(self.loader.clone())
}
@ -753,29 +854,102 @@ impl ModuleMap {
&& self.pending_dynamic_imports.is_empty())
}
/// Called by `module_resolve_callback` during module instantiation.
pub(crate) fn resolve_callback<'s>(
/// Returns the namespace object of a module.
///
/// This is only available after module evaluation has completed.
/// This function panics if module has not been instantiated.
pub fn get_module_namespace(
&self,
scope: &mut v8::HandleScope<'s>,
specifier: &str,
referrer: &str,
import_assertions: HashMap<String, String>,
) -> Option<v8::Local<'s, v8::Module>> {
let resolved_specifier = self
.loader
.resolve(specifier, referrer, ResolutionKind::Import)
.expect("Module should have been already resolved");
scope: &mut v8::HandleScope,
module_id: ModuleId,
) -> Result<v8::Global<v8::Object>, Error> {
let module_handle =
self.get_handle(module_id).expect("ModuleInfo not found");
let module_type =
get_asserted_module_type_from_assertions(&import_assertions);
let module = module_handle.open(scope);
if let Some(id) = self.get_id(resolved_specifier.as_str(), module_type) {
if let Some(handle) = self.get_handle(id) {
return Some(v8::Local::new(scope, handle));
if module.get_status() == v8::ModuleStatus::Errored {
let exception = module.get_exception();
return exception_to_err_result(scope, exception, false);
}
assert!(matches!(
module.get_status(),
v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated
));
let module_namespace: v8::Local<v8::Object> =
v8::Local::try_from(module.get_module_namespace())
.map_err(|err: v8::DataError| generic_error(err.to_string()))?;
Ok(v8::Global::new(scope, module_namespace))
}
/// 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(
&mut self,
exceptions: impl Iterator<Item = (&'static str, &'static str)>,
) {
let handles = exceptions
.map(|(old_name, new_name)| {
(self.get_handle_by_name(old_name).unwrap(), new_name)
})
.collect::<Vec<_>>();
self.clear();
for (handle, new_name) in handles {
self.inject_handle(
ModuleName::from_static(new_name),
ModuleType::JavaScript,
handle,
)
}
}
fn get_stalled_top_level_await_message_for_module(
&self,
scope: &mut v8::HandleScope,
module_id: ModuleId,
) -> Vec<v8::Global<v8::Message>> {
let module_handle = self.handles.get(module_id).unwrap();
let module = v8::Local::new(scope, module_handle);
let stalled = module.get_stalled_top_level_await_message(scope);
let mut messages = vec![];
for (_, message) in stalled {
messages.push(v8::Global::new(scope, message));
}
messages
}
pub(crate) fn find_stalled_top_level_await(
&self,
scope: &mut v8::HandleScope,
) -> Vec<v8::Global<v8::Message>> {
// First check if that's root module
let root_module_id =
self.info.iter().filter(|m| m.main).map(|m| m.id).next();
if let Some(root_module_id) = root_module_id {
let messages = self
.get_stalled_top_level_await_message_for_module(scope, root_module_id);
if !messages.is_empty() {
return messages;
}
}
None
// It wasn't a top module, so iterate over all modules and try to find
// any with stalled top level await
for module_id in 0..self.handles.len() {
let messages =
self.get_stalled_top_level_await_message_for_module(scope, module_id);
if !messages.is_empty() {
return messages;
}
}
unreachable!()
}
}

View file

@ -7,6 +7,7 @@ use std::os::raw::c_void;
use v8::MapFnTo;
use crate::error::is_instance_of_error;
use crate::error::throw_type_error;
use crate::error::JsStackFrame;
use crate::modules::get_asserted_module_type_from_assertions;
use crate::modules::parse_import_assertions;
@ -542,57 +543,3 @@ fn call_console(
inspector_console_method.call(scope, receiver.into(), &call_args);
deno_console_method.call(scope, receiver.into(), &call_args);
}
/// Called by V8 during `JsRuntime::instantiate_module`.
///
/// This function borrows `ModuleMap` from the isolate slot,
/// so it is crucial to ensure there are no existing borrows
/// of `ModuleMap` when `JsRuntime::instantiate_module` is called.
pub fn module_resolve_callback<'s>(
context: v8::Local<'s, v8::Context>,
specifier: v8::Local<'s, v8::String>,
import_assertions: v8::Local<'s, v8::FixedArray>,
referrer: v8::Local<'s, v8::Module>,
) -> Option<v8::Local<'s, v8::Module>> {
// SAFETY: `CallbackScope` can be safely constructed from `Local<Context>`
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let module_map_rc = JsRuntime::module_map_from(scope);
let module_map = module_map_rc.borrow();
let referrer_global = v8::Global::new(scope, referrer);
let referrer_info = module_map
.get_info(&referrer_global)
.expect("ModuleInfo not found");
let referrer_name = referrer_info.name.as_str();
let specifier_str = specifier.to_rust_string_lossy(scope);
let assertions = parse_import_assertions(
scope,
import_assertions,
ImportAssertionsKind::StaticImport,
);
let maybe_module = module_map.resolve_callback(
scope,
&specifier_str,
referrer_name,
assertions,
);
if let Some(module) = maybe_module {
return Some(module);
}
let msg = format!(
r#"Cannot resolve module "{specifier_str}" from "{referrer_name}""#
);
throw_type_error(scope, msg);
None
}
pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef<str>) {
let message = v8::String::new(scope, message.as_ref()).unwrap();
let exception = v8::Exception::type_error(scope, message);
scope.throw_exception(exception);
}

View file

@ -21,14 +21,12 @@ 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::runtime::ContextState;
use crate::runtime::JsRealm;
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;
@ -391,6 +389,11 @@ pub struct RuntimeOptions {
/// JavaScript sources in the extensions.
pub extensions: Vec<Extension>,
/// If provided, the module map will be cleared and left only with the specifiers
/// in this list, with the new names provided. If not provided, the module map is
/// left intact.
pub rename_modules: Option<Vec<(&'static str, &'static str)>>,
/// V8 snapshot that should be loaded on startup.
pub startup_snapshot: Option<Snapshot>,
@ -694,6 +697,15 @@ impl JsRuntime {
js_runtime
.init_extension_js(&realm, maybe_load_callback)
.unwrap();
// If the user has requested that we rename modules
if let Some(rename_modules) = options.rename_modules {
js_runtime
.module_map
.borrow_mut()
.clear_module_map(rename_modules.into_iter());
}
js_runtime
}
@ -1143,31 +1155,11 @@ impl JsRuntime {
&mut self,
module_id: ModuleId,
) -> Result<v8::Global<v8::Object>, Error> {
let module_handle = self
self
.module_map
.clone()
.borrow()
.get_handle(module_id)
.expect("ModuleInfo not found");
let scope = &mut self.handle_scope();
let module = module_handle.open(scope);
if module.get_status() == v8::ModuleStatus::Errored {
let exception = module.get_exception();
return exception_to_err_result(scope, exception, false);
}
assert!(matches!(
module.get_status(),
v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated
));
let module_namespace: v8::Local<v8::Object> =
v8::Local::try_from(module.get_module_namespace())
.map_err(|err: v8::DataError| generic_error(err.to_string()))?;
Ok(v8::Global::new(scope, module_namespace))
.get_module_namespace(&mut self.handle_scope(), module_id)
}
/// Registers a callback on the isolate when the memory limits are approached.
@ -1323,6 +1315,7 @@ impl JsRuntime {
let _ = self.inspector().borrow().poll_sessions(Some(cx)).unwrap();
}
let module_map = self.module_map.clone();
self.pump_v8_message_loop()?;
// Dynamic module loading - ie. modules loaded using "import()"
@ -1426,7 +1419,7 @@ impl JsRuntime {
// pass, will be polled again
} else {
let scope = &mut self.handle_scope();
let messages = find_stalled_top_level_await(scope);
let messages = module_map.borrow().find_stalled_top_level_await(scope);
// We are gonna print only a single message to provide a nice formatting
// with source line of offending promise shown. Once user fixed it, then
// they will get another error message for the next promise (but this
@ -1448,7 +1441,7 @@ impl JsRuntime {
} else if self.inner.state.borrow().dyn_module_evaluate_idle_counter >= 1
{
let scope = &mut self.handle_scope();
let messages = find_stalled_top_level_await(scope);
let messages = module_map.borrow().find_stalled_top_level_await(scope);
// We are gonna print only a single message to provide a nice formatting
// with source line of offending promise shown. Once user fixed it, then
// they will get another error message for the next promise (but this
@ -1537,58 +1530,6 @@ impl JsRuntimeForSnapshot {
}
}
fn get_stalled_top_level_await_message_for_module(
scope: &mut v8::HandleScope,
module_id: ModuleId,
) -> Vec<v8::Global<v8::Message>> {
let module_map = JsRuntime::module_map_from(scope);
let module_map = module_map.borrow();
let module_handle = module_map.handles.get(module_id).unwrap();
let module = v8::Local::new(scope, module_handle);
let stalled = module.get_stalled_top_level_await_message(scope);
let mut messages = vec![];
for (_, message) in stalled {
messages.push(v8::Global::new(scope, message));
}
messages
}
fn find_stalled_top_level_await(
scope: &mut v8::HandleScope,
) -> Vec<v8::Global<v8::Message>> {
let module_map = JsRuntime::module_map_from(scope);
let module_map = module_map.borrow();
// First check if that's root module
let root_module_id = module_map
.info
.iter()
.filter(|m| m.main)
.map(|m| m.id)
.next();
if let Some(root_module_id) = root_module_id {
let messages =
get_stalled_top_level_await_message_for_module(scope, root_module_id);
if !messages.is_empty() {
return messages;
}
}
// It wasn't a top module, so iterate over all modules and try to find
// any with stalled top level await
for module_id in 0..module_map.handles.len() {
let messages =
get_stalled_top_level_await_message_for_module(scope, module_id);
if !messages.is_empty() {
return messages;
}
}
unreachable!()
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct EventLoopPendingState {
has_pending_refed_ops: bool,
@ -1666,32 +1607,11 @@ impl JsRuntime {
&mut self,
id: ModuleId,
) -> Result<(), v8::Global<v8::Value>> {
let module_map_rc = self.module_map.clone();
let scope = &mut self.handle_scope();
let tc_scope = &mut v8::TryCatch::new(scope);
let module = module_map_rc
.borrow()
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
if module.get_status() == v8::ModuleStatus::Errored {
return Err(v8::Global::new(tc_scope, module.get_exception()));
}
// IMPORTANT: No borrows to `ModuleMap` can be held at this point because
// `module_resolve_callback` will be calling into `ModuleMap` from within
// the isolate.
let instantiate_result =
module.instantiate_module(tc_scope, bindings::module_resolve_callback);
if instantiate_result.is_none() {
let exception = tc_scope.exception().unwrap();
return Err(v8::Global::new(tc_scope, exception));
}
Ok(())
self
.module_map
.clone()
.borrow_mut()
.instantiate_module(&mut self.handle_scope(), id)
}
fn dynamic_import_module_evaluate(
@ -1903,29 +1823,6 @@ 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.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,

View file

@ -32,4 +32,3 @@ pub use snapshot_util::FilterFn;
pub(crate) use snapshot_util::SnapshottedData;
pub use bindings::script_origin;
pub use bindings::throw_type_error;

View file

@ -4,7 +4,6 @@ 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;
@ -485,6 +484,15 @@ impl WebWorker {
let startup_snapshot = options.startup_snapshot
.expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature.");
// Clear extension modules from the module map, except preserve `ext:deno_node`
// modules as `node:` specifiers.
let rename_modules = Some(
deno_node::SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.map(|p| (p.ext_specifier, p.specifier))
.collect(),
);
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(options.module_loader.clone()),
startup_snapshot: Some(startup_snapshot),
@ -494,9 +502,9 @@ impl WebWorker {
compiled_wasm_module_store: options.compiled_wasm_module_store.clone(),
extensions,
inspector: options.maybe_inspector_server.is_some(),
rename_modules,
..Default::default()
});
init_runtime_module_map(&mut js_runtime);
if let Some(server) = options.maybe_inspector_server.clone() {
server.register_inspector(

View file

@ -58,16 +58,6 @@ impl ExitCode {
}
}
/// 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.
///
@ -323,6 +313,15 @@ impl MainWorker {
let startup_snapshot = options.startup_snapshot
.expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature.");
// Clear extension modules from the module map, except preserve `ext:deno_node`
// modules as `node:` specifiers.
let rename_modules = Some(
deno_node::SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.map(|p| (p.ext_specifier, p.specifier))
.collect(),
);
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(options.module_loader.clone()),
startup_snapshot: Some(startup_snapshot),
@ -332,11 +331,11 @@ impl MainWorker {
shared_array_buffer_store: options.shared_array_buffer_store.clone(),
compiled_wasm_module_store: options.compiled_wasm_module_store.clone(),
extensions,
rename_modules,
inspector: options.maybe_inspector_server.is_some(),
is_main: true,
..Default::default()
});
init_runtime_module_map(&mut js_runtime);
if let Some(server) = options.maybe_inspector_server.clone() {
server.register_inspector(