diff --git a/cli/global_state.rs b/cli/global_state.rs index cb265d9e59..79618b741a 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -96,7 +96,7 @@ impl GlobalState { } /// This function is called when new module load is - /// initialized by the EsIsolate. Its resposibility is to collect + /// initialized by the CoreIsolate. Its resposibility is to collect /// all dependencies and if it is required then also perform TS typecheck /// and traspilation. pub async fn prepare_module_load( @@ -180,7 +180,7 @@ impl GlobalState { } // TODO(bartlomieju): this method doesn't need to be async anymore - /// This method is used after `prepare_module_load` finishes and EsIsolate + /// This method is used after `prepare_module_load` finishes and CoreIsolate /// starts loading source and executing source code. This method shouldn't /// perform any IO (besides $DENO_DIR) and only operate on sources collected /// during `prepare_module_load`. diff --git a/cli/main.rs b/cli/main.rs index 49efbfed58..2e2dc71a26 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -79,9 +79,9 @@ use crate::permissions::Permissions; use crate::tsc::TargetLib; use crate::worker::MainWorker; use deno_core::v8_set_flags; +use deno_core::CoreIsolate; use deno_core::Deps; use deno_core::ErrBox; -use deno_core::EsIsolate; use deno_core::ModuleSpecifier; use deno_doc as doc; use deno_doc::parser::DocFileLoader; @@ -217,7 +217,7 @@ async fn print_file_info( { output.map = source_map.filename.to_str().map(|s| s.to_owned()); } - let es_state_rc = EsIsolate::state(&worker.isolate); + let es_state_rc = CoreIsolate::state(&worker.isolate); let es_state = es_state_rc.borrow(); if let Some(deps) = es_state.modules.deps(&module_specifier) { diff --git a/cli/worker.rs b/cli/worker.rs index 3aaf92f48f..ac0ef0d26f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -91,7 +91,7 @@ fn create_channels() -> (WorkerChannelsInternal, WorkerHandle) { /// - `WebWorker` pub struct Worker { pub name: String, - pub isolate: deno_core::EsIsolate, + pub isolate: deno_core::CoreIsolate, pub inspector: Option>, pub state: Rc, pub waker: AtomicWaker, @@ -105,7 +105,7 @@ impl Worker { startup_data: StartupData, state: &Rc, ) -> Self { - let mut isolate = deno_core::EsIsolate::new( + let mut isolate = deno_core::CoreIsolate::new_with_loader( state.clone(), state.clone(), startup_data, @@ -235,7 +235,7 @@ impl Future for Worker { } impl Deref for Worker { - type Target = deno_core::EsIsolate; + type Target = deno_core::CoreIsolate; fn deref(&self) -> &Self::Target { &self.isolate } diff --git a/core/bindings.rs b/core/bindings.rs index 166c0ee6e5..c399f265fa 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -3,7 +3,6 @@ use crate::CoreIsolate; use crate::CoreIsolateState; use crate::ErrBox; -use crate::EsIsolate; use crate::JSError; use crate::Op; use crate::OpId; @@ -240,7 +239,7 @@ pub extern "C" fn host_import_module_dynamically_callback( let resolver_handle = v8::Global::new(scope, resolver); { - let state_rc = EsIsolate::state(scope); + let state_rc = CoreIsolate::state(scope); let mut state = state_rc.borrow_mut(); state.dyn_import_cb(resolver_handle, &specifier_str, &referrer_name_str); } @@ -254,7 +253,7 @@ pub extern "C" fn host_initialize_import_meta_object_callback( meta: v8::Local, ) { let scope = &mut unsafe { v8::CallbackScope::new(context) }; - let state_rc = EsIsolate::state(scope); + let state_rc = CoreIsolate::state(scope); let state = state_rc.borrow(); let id = module.get_identity_hash(); @@ -713,7 +712,7 @@ pub fn module_resolve_callback<'s>( ) -> Option> { let scope = &mut unsafe { v8::CallbackScope::new(context) }; - let state_rc = EsIsolate::state(scope); + let state_rc = CoreIsolate::state(scope); let mut state = state_rc.borrow_mut(); let referrer_id = referrer.get_identity_hash(); diff --git a/core/core_isolate.rs b/core/core_isolate.rs index 52e856174f..b8922b982e 100644 --- a/core/core_isolate.rs +++ b/core/core_isolate.rs @@ -8,13 +8,28 @@ use rusty_v8 as v8; use crate::bindings; +use crate::errors::attach_handle_to_error; +use crate::errors::ErrWithV8Handle; +use crate::futures::FutureExt; +use crate::module_specifier::ModuleSpecifier; +use crate::modules::LoadState; +use crate::modules::ModuleId; +use crate::modules::ModuleLoadId; +use crate::modules::ModuleLoader; +use crate::modules::ModuleSource; +use crate::modules::Modules; +use crate::modules::NoopModuleLoader; +use crate::modules::PrepareLoadFuture; +use crate::modules::RecursiveModuleLoad; use crate::ops::*; use crate::shared_queue::SharedQueue; use crate::shared_queue::RECOMMENDED_SIZE; use crate::ErrBox; use crate::JSError; +use crate::OpRouter; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; +use futures::stream::StreamFuture; use futures::task::AtomicWaker; use futures::Future; use std::any::Any; @@ -22,6 +37,7 @@ use std::cell::Cell; use std::cell::RefCell; use std::collections::HashMap; use std::convert::From; +use std::convert::TryFrom; use std::ffi::c_void; use std::mem::forget; use std::ops::Deref; @@ -125,6 +141,12 @@ pub struct CoreIsolateState { pub(crate) pending_unref_ops: FuturesUnordered, pub(crate) have_unpolled_ops: Cell, pub(crate) op_router: Rc, + loader: Rc, + pub modules: Modules, + pub(crate) dyn_import_map: + HashMap>, + preparing_dyn_imports: FuturesUnordered>>, + pending_dyn_imports: FuturesUnordered>, waker: AtomicWaker, } @@ -199,6 +221,7 @@ pub struct HeapLimits { } pub(crate) struct IsolateOptions { + loader: Rc, op_router: Rc, startup_script: Option, startup_snapshot: Option, @@ -216,6 +239,28 @@ impl CoreIsolate { ) -> Self { let (startup_script, startup_snapshot) = startup_data.into_options(); let options = IsolateOptions { + loader: Rc::new(NoopModuleLoader), + op_router, + startup_script, + startup_snapshot, + will_snapshot, + heap_limits: None, + }; + + Self::from_options(options) + } + + // TODO(bartlomieju): add `new_with_loader_and_heap_limits` function? + /// Create new isolate that can load and execute ESModules. + pub fn new_with_loader( + loader: Rc, + op_router: Rc, + startup_data: StartupData, + will_snapshot: bool, + ) -> Self { + let (startup_script, startup_snapshot) = startup_data.into_options(); + let options = IsolateOptions { + loader, op_router, startup_script, startup_snapshot, @@ -239,6 +284,7 @@ impl CoreIsolate { ) -> Self { let (startup_script, startup_snapshot) = startup_data.into_options(); let options = IsolateOptions { + loader: Rc::new(NoopModuleLoader), op_router, startup_script, startup_snapshot, @@ -316,6 +362,11 @@ impl CoreIsolate { pending_unref_ops: FuturesUnordered::new(), have_unpolled_ops: Cell::new(false), op_router: options.op_router, + modules: Modules::new(), + loader: options.loader, + dyn_import_map: HashMap::new(), + preparing_dyn_imports: FuturesUnordered::new(), + pending_dyn_imports: FuturesUnordered::new(), waker: AtomicWaker::new(), }))); @@ -332,6 +383,12 @@ impl CoreIsolate { fn setup_isolate(mut isolate: v8::OwnedIsolate) -> v8::OwnedIsolate { isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10); isolate.set_promise_reject_callback(bindings::promise_reject_callback); + isolate.set_host_initialize_import_meta_object_callback( + bindings::host_initialize_import_meta_object_callback, + ); + isolate.set_host_import_module_dynamically_callback( + bindings::host_import_module_dynamically_callback, + ); isolate } @@ -412,6 +469,8 @@ impl CoreIsolate { // TODO(piscisaureus): The rusty_v8 type system should enforce this. state.borrow_mut().global_context.take(); + std::mem::take(&mut state.borrow_mut().modules); + let snapshot_creator = self.snapshot_creator.as_mut().unwrap(); let snapshot = snapshot_creator .create_blob(v8::FunctionCodeHandling::Keep) @@ -489,6 +548,24 @@ impl Future for CoreIsolate { state.waker.register(cx.waker()); } + let has_preparing = { + let state = state_rc.borrow(); + !state.preparing_dyn_imports.is_empty() + }; + if has_preparing { + let poll_imports = core_isolate.prepare_dyn_imports(cx)?; + assert!(poll_imports.is_ready()); + } + + let has_pending = { + let state = state_rc.borrow(); + !state.pending_dyn_imports.is_empty() + }; + if has_pending { + let poll_imports = core_isolate.poll_dyn_imports(cx)?; + assert!(poll_imports.is_ready()); + } + let scope = &mut v8::HandleScope::with_context( &mut **core_isolate, state_rc.borrow().global_context.as_ref().unwrap(), @@ -563,7 +640,10 @@ impl Future for CoreIsolate { let state = state_rc.borrow(); // We're idle if pending_ops is empty. - if state.pending_ops.is_empty() { + if state.pending_ops.is_empty() + && state.pending_dyn_imports.is_empty() + && state.preparing_dyn_imports.is_empty() + { Poll::Ready(Ok(())) } else { if state.have_unpolled_ops.get() { @@ -584,6 +664,40 @@ impl CoreIsolateState { ) { self.js_error_create_fn = Box::new(f); } + + // Called by V8 during `Isolate::mod_instantiate`. + pub fn module_resolve_cb( + &mut self, + specifier: &str, + referrer_id: ModuleId, + ) -> ModuleId { + let referrer = self.modules.get_name(referrer_id).unwrap(); + let specifier = self + .loader + .resolve(specifier, referrer, false) + .expect("Module should have been already resolved"); + self.modules.get_id(specifier.as_str()).unwrap_or(0) + } + + // Called by V8 during `Isolate::mod_instantiate`. + pub fn dyn_import_cb( + &mut self, + resolver_handle: v8::Global, + specifier: &str, + referrer: &str, + ) { + debug!("dyn_import specifier {} referrer {} ", specifier, referrer); + + let load = RecursiveModuleLoad::dynamic_import( + specifier, + referrer, + self.loader.clone(), + ); + self.dyn_import_map.insert(load.id, resolver_handle); + self.waker.wake(); + let fut = load.prepare().boxed_local(); + self.preparing_dyn_imports.push(fut); + } } fn async_op_response<'s>( @@ -728,13 +842,456 @@ fn boxed_slice_to_uint8array<'sc>( .expect("Failed to create UintArray8") } +// Related to module loading +impl CoreIsolate { + /// Low-level module creation. + /// + /// Called during module loading or dynamic import loading. + fn mod_new( + &mut self, + main: bool, + name: &str, + source: &str, + ) -> Result { + let state_rc = Self::state(self); + let scope = &mut v8::HandleScope::with_context( + &mut **self, + state_rc.borrow().global_context.as_ref().unwrap(), + ); + + let name_str = v8::String::new(scope, name).unwrap(); + let source_str = v8::String::new(scope, source).unwrap(); + + let origin = bindings::module_origin(scope, name_str); + let source = v8::script_compiler::Source::new(source_str, &origin); + + let tc_scope = &mut v8::TryCatch::new(scope); + + let maybe_module = v8::script_compiler::compile_module(tc_scope, source); + + if tc_scope.has_caught() { + assert!(maybe_module.is_none()); + let e = tc_scope.exception().unwrap(); + return exception_to_err_result(tc_scope, e); + } + + let module = maybe_module.unwrap(); + let id = module.get_identity_hash(); + + let mut import_specifiers: Vec = vec![]; + for i in 0..module.get_module_requests_length() { + let import_specifier = + module.get_module_request(i).to_rust_string_lossy(tc_scope); + let state = state_rc.borrow(); + let module_specifier = + state.loader.resolve(&import_specifier, name, false)?; + import_specifiers.push(module_specifier); + } + + state_rc.borrow_mut().modules.register( + id, + name, + main, + v8::Global::::new(tc_scope, module), + import_specifiers, + ); + + Ok(id) + } + + /// Instantiates a ES module + /// + /// ErrBox can be downcast to a type that exposes additional information about + /// the V8 exception. By default this type is JSError, however it may be a + /// different type if CoreIsolate::set_js_error_create_fn() has been used. + fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + let scope = &mut v8::HandleScope::with_context( + &mut **self, + state.global_context.as_ref().unwrap(), + ); + let tc_scope = &mut v8::TryCatch::new(scope); + + let module = match state.modules.get_info(id) { + Some(info) => v8::Local::new(tc_scope, &info.handle), + None if id == 0 => return Ok(()), + _ => panic!("module id {} not found in module table", id), + }; + drop(state); + + if module.get_status() == v8::ModuleStatus::Errored { + exception_to_err_result(tc_scope, module.get_exception())? + } + + let result = + module.instantiate_module(tc_scope, bindings::module_resolve_callback); + match result { + Some(_) => Ok(()), + None => { + let exception = tc_scope.exception().unwrap(); + exception_to_err_result(tc_scope, exception) + } + } + } + + /// Evaluates an already instantiated ES module. + /// + /// ErrBox can be downcast to a type that exposes additional information about + /// the V8 exception. By default this type is JSError, however it may be a + /// different type if CoreIsolate::set_js_error_create_fn() has been used. + pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + self.shared_init(); + + let state_rc = Self::state(self); + + let scope = &mut v8::HandleScope::with_context( + &mut **self, + state_rc.borrow().global_context.as_ref().unwrap(), + ); + + let module = state_rc + .borrow() + .modules + .get_info(id) + .map(|info| v8::Local::new(scope, &info.handle)) + .expect("ModuleInfo not found"); + let mut status = module.get_status(); + + if status == v8::ModuleStatus::Instantiated { + // IMPORTANT: Top-level-await is enabled, which means that return value + // of module evaluation is a promise. + // + // Because that promise is created internally by V8, when error occurs during + // module evaluation the promise is rejected, and since the promise has no rejection + // handler it will result in call to `bindings::promise_reject_callback` adding + // the promise to pending promise rejection table - meaning Isolate will return + // error on next poll(). + // + // This situation is not desirable as we want to manually return error at the + // end of this function to handle it further. It means we need to manually + // remove this promise from pending promise rejection table. + // + // For more details see: + // https://github.com/denoland/deno/issues/4908 + // https://v8.dev/features/top-level-await#module-execution-order + let maybe_value = module.evaluate(scope); + + // Update status after evaluating. + status = module.get_status(); + + if let Some(value) = maybe_value { + assert!( + status == v8::ModuleStatus::Evaluated + || status == v8::ModuleStatus::Errored + ); + let promise = v8::Local::::try_from(value) + .expect("Expected to get promise as module evaluation result"); + let promise_id = promise.get_identity_hash(); + let mut state = state_rc.borrow_mut(); + state.pending_promise_exceptions.remove(&promise_id); + } else { + assert!(status == v8::ModuleStatus::Errored); + } + } + + match status { + v8::ModuleStatus::Evaluated => Ok(()), + v8::ModuleStatus::Errored => { + let exception = module.get_exception(); + exception_to_err_result(scope, exception) + .map_err(|err| attach_handle_to_error(scope, err, exception)) + } + other => panic!("Unexpected module status {:?}", other), + } + } + + fn dyn_import_error( + &mut self, + id: ModuleLoadId, + err: ErrBox, + ) -> Result<(), ErrBox> { + let state_rc = Self::state(self); + + let scope = &mut v8::HandleScope::with_context( + &mut **self, + state_rc.borrow().global_context.as_ref().unwrap(), + ); + + let resolver_handle = state_rc + .borrow_mut() + .dyn_import_map + .remove(&id) + .expect("Invalid dyn import id"); + let resolver = resolver_handle.get(scope); + + let exception = err + .downcast_ref::() + .map(|err| err.get_handle(scope)) + .unwrap_or_else(|| { + let message = err.to_string(); + let message = v8::String::new(scope, &message).unwrap(); + v8::Exception::type_error(scope, message) + }); + + resolver.reject(scope, exception).unwrap(); + scope.perform_microtask_checkpoint(); + Ok(()) + } + + fn dyn_import_done( + &mut self, + id: ModuleLoadId, + mod_id: ModuleId, + ) -> Result<(), ErrBox> { + let state_rc = Self::state(self); + + debug!("dyn_import_done {} {:?}", id, mod_id); + assert!(mod_id != 0); + let scope = &mut v8::HandleScope::with_context( + &mut **self, + state_rc.borrow().global_context.as_ref().unwrap(), + ); + + let resolver_handle = state_rc + .borrow_mut() + .dyn_import_map + .remove(&id) + .expect("Invalid dyn import id"); + let resolver = resolver_handle.get(scope); + + let module = { + let state = state_rc.borrow(); + state + .modules + .get_info(mod_id) + .map(|info| v8::Local::new(scope, &info.handle)) + .expect("Dyn import module info not found") + }; + // Resolution success + assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated); + + let module_namespace = module.get_module_namespace(); + resolver.resolve(scope, module_namespace).unwrap(); + scope.perform_microtask_checkpoint(); + Ok(()) + } + + fn prepare_dyn_imports( + &mut self, + cx: &mut Context, + ) -> Poll> { + let state_rc = Self::state(self); + + loop { + let r = { + let mut state = state_rc.borrow_mut(); + state.preparing_dyn_imports.poll_next_unpin(cx) + }; + match r { + Poll::Pending | Poll::Ready(None) => { + // There are no active dynamic import loaders, or none are ready. + return Poll::Ready(Ok(())); + } + Poll::Ready(Some(prepare_poll)) => { + let dyn_import_id = prepare_poll.0; + let prepare_result = prepare_poll.1; + + match prepare_result { + Ok(load) => { + let state = state_rc.borrow_mut(); + state.pending_dyn_imports.push(load.into_future()); + } + Err(err) => { + self.dyn_import_error(dyn_import_id, err)?; + } + } + } + } + } + } + + fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll> { + let state_rc = Self::state(self); + loop { + let poll_result = { + let mut state = state_rc.borrow_mut(); + state.pending_dyn_imports.poll_next_unpin(cx) + }; + + match poll_result { + Poll::Pending | Poll::Ready(None) => { + // There are no active dynamic import loaders, or none are ready. + return Poll::Ready(Ok(())); + } + Poll::Ready(Some(load_stream_poll)) => { + let maybe_result = load_stream_poll.0; + let mut load = load_stream_poll.1; + let dyn_import_id = load.id; + + if let Some(load_stream_result) = maybe_result { + match load_stream_result { + Ok(info) => { + // A module (not necessarily the one dynamically imported) has been + // fetched. Create and register it, and if successful, poll for the + // next recursive-load event related to this dynamic import. + match self.register_during_load(info, &mut load) { + Ok(()) => { + // Keep importing until it's fully drained + let state = state_rc.borrow_mut(); + state.pending_dyn_imports.push(load.into_future()); + } + Err(err) => self.dyn_import_error(dyn_import_id, err)?, + } + } + Err(err) => { + // A non-javascript error occurred; this could be due to a an invalid + // module specifier, or a problem with the source map, or a failure + // to fetch the module source code. + self.dyn_import_error(dyn_import_id, err)? + } + } + } else { + // The top-level module from a dynamic import has been instantiated. + // Load is done. + let module_id = load.root_module_id.unwrap(); + self.mod_instantiate(module_id)?; + match self.mod_evaluate(module_id) { + Ok(()) => self.dyn_import_done(dyn_import_id, module_id)?, + Err(err) => self.dyn_import_error(dyn_import_id, err)?, + }; + } + } + } + } + } + + fn register_during_load( + &mut self, + info: ModuleSource, + load: &mut RecursiveModuleLoad, + ) -> Result<(), ErrBox> { + let ModuleSource { + code, + module_url_specified, + module_url_found, + } = info; + + let is_main = + load.state == LoadState::LoadingRoot && !load.is_dynamic_import(); + let referrer_specifier = + ModuleSpecifier::resolve_url(&module_url_found).unwrap(); + + let state_rc = Self::state(self); + // #A There are 3 cases to handle at this moment: + // 1. Source code resolved result have the same module name as requested + // and is not yet registered + // -> register + // 2. Source code resolved result have a different name as requested: + // 2a. The module with resolved module name has been registered + // -> alias + // 2b. The module with resolved module name has not yet been registered + // -> register & alias + + // If necessary, register an alias. + if module_url_specified != module_url_found { + let mut state = state_rc.borrow_mut(); + state + .modules + .alias(&module_url_specified, &module_url_found); + } + + let maybe_mod_id = { + let state = state_rc.borrow(); + state.modules.get_id(&module_url_found) + }; + + let module_id = match maybe_mod_id { + Some(id) => { + // Module has already been registered. + debug!( + "Already-registered module fetched again: {}", + module_url_found + ); + id + } + // Module not registered yet, do it now. + None => self.mod_new(is_main, &module_url_found, &code)?, + }; + + // Now we must iterate over all imports of the module and load them. + let imports = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.modules.get_children(module_id).unwrap().clone() + }; + + for module_specifier in imports { + let is_registered = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.modules.is_registered(&module_specifier) + }; + if !is_registered { + load + .add_import(module_specifier.to_owned(), referrer_specifier.clone()); + } + } + + // If we just finished loading the root module, store the root module id. + if load.state == LoadState::LoadingRoot { + load.root_module_id = Some(module_id); + load.state = LoadState::LoadingImports; + } + + if load.pending.is_empty() { + load.state = LoadState::Done; + } + + Ok(()) + } + + /// Asynchronously load specified module and all of it's dependencies + /// + /// User must call `Isolate::mod_evaluate` with returned `ModuleId` + /// manually after load is finished. + pub async fn load_module( + &mut self, + specifier: &ModuleSpecifier, + code: Option, + ) -> Result { + self.shared_init(); + let loader = { + let state_rc = Self::state(self); + let state = state_rc.borrow(); + state.loader.clone() + }; + + let load = RecursiveModuleLoad::main(&specifier.to_string(), code, loader); + let (_load_id, prepare_result) = load.prepare().await; + + let mut load = prepare_result?; + + while let Some(info_result) = load.next().await { + let info = info_result?; + self.register_during_load(info, &mut load)?; + } + + let root_id = load.root_module_id.expect("Root module id empty"); + self.mod_instantiate(root_id).map(|_| root_id) + } +} + #[cfg(test)] pub mod tests { use super::*; + use crate::modules::ModuleSourceFuture; + use crate::ops::*; use crate::BasicState; use crate::BufVec; use futures::future::lazy; use futures::FutureExt; + use std::io; use std::ops::FnOnce; use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -1365,4 +1922,350 @@ pub mod tests { assert_eq!(0, callback_invoke_count_first.load(Ordering::SeqCst)); assert!(callback_invoke_count_second.load(Ordering::SeqCst) > 0); } + + #[test] + fn test_mods() { + #[derive(Default)] + struct ModsLoader { + pub count: Arc, + } + + impl ModuleLoader for ModsLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result { + self.count.fetch_add(1, Ordering::Relaxed); + assert_eq!(specifier, "./b.js"); + assert_eq!(referrer, "file:///a.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + unreachable!() + } + } + + let loader = Rc::new(ModsLoader::default()); + let state = BasicState::new(); + + let resolve_count = loader.count.clone(); + let dispatch_count = Arc::new(AtomicUsize::new(0)); + let dispatch_count_ = dispatch_count.clone(); + + let dispatcher = move |_state: Rc, bufs: BufVec| -> Op { + dispatch_count_.fetch_add(1, Ordering::Relaxed); + assert_eq!(bufs.len(), 1); + assert_eq!(bufs[0].len(), 1); + assert_eq!(bufs[0][0], 42); + let buf = [43u8, 0, 0, 0][..].into(); + Op::Async(futures::future::ready(buf).boxed()) + }; + state.register_op("test", dispatcher); + + let mut isolate = + CoreIsolate::new_with_loader(loader, state, StartupData::None, false); + + js_check(isolate.execute( + "setup.js", + r#" + function assert(cond) { + if (!cond) { + throw Error("assert"); + } + } + "#, + )); + + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + + let specifier_a = "file:///a.js".to_string(); + let mod_a = isolate + .mod_new( + true, + &specifier_a, + r#" + import { b } from './b.js' + if (b() != 'b') throw Error(); + let control = new Uint8Array([42]); + Deno.core.send(1, control); + "#, + ) + .unwrap(); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + + let state_rc = CoreIsolate::state(&isolate); + { + let state = state_rc.borrow(); + let imports = state.modules.get_children(mod_a); + assert_eq!( + imports, + Some(&vec![ModuleSpecifier::resolve_url("file:///b.js").unwrap()]) + ); + } + let mod_b = isolate + .mod_new(false, "file:///b.js", "export function b() { return 'b' }") + .unwrap(); + { + let state = state_rc.borrow(); + let imports = state.modules.get_children(mod_b).unwrap(); + assert_eq!(imports.len(), 0); + } + + js_check(isolate.mod_instantiate(mod_b)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + assert_eq!(resolve_count.load(Ordering::SeqCst), 1); + + js_check(isolate.mod_instantiate(mod_a)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + + js_check(isolate.mod_evaluate(mod_a)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); + } + + #[test] + fn dyn_import_err() { + #[derive(Clone, Default)] + struct DynImportErrLoader { + pub count: Arc, + } + + impl ModuleLoader for DynImportErrLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result { + self.count.fetch_add(1, Ordering::Relaxed); + assert_eq!(specifier, "/foo.js"); + assert_eq!(referrer, "file:///dyn_import2.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + async { Err(io::Error::from(io::ErrorKind::NotFound).into()) }.boxed() + } + } + + // Test an erroneous dynamic import where the specified module isn't found. + run_in_task(|cx| { + let loader = Rc::new(DynImportErrLoader::default()); + let count = loader.count.clone(); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); + + js_check(isolate.execute( + "file:///dyn_import2.js", + r#" + (async () => { + await import("/foo.js"); + })(); + "#, + )); + + assert_eq!(count.load(Ordering::Relaxed), 0); + // We should get an error here. + let result = isolate.poll_unpin(cx); + if let Poll::Ready(Ok(_)) = result { + unreachable!(); + } + assert_eq!(count.load(Ordering::Relaxed), 2); + }) + } + + #[derive(Clone, Default)] + struct DynImportOkLoader { + pub prepare_load_count: Arc, + pub resolve_count: Arc, + pub load_count: Arc, + } + + impl ModuleLoader for DynImportOkLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result { + let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); + assert!(c < 4); + assert_eq!(specifier, "./b.js"); + assert_eq!(referrer, "file:///dyn_import3.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + self.load_count.fetch_add(1, Ordering::Relaxed); + let info = ModuleSource { + module_url_specified: specifier.to_string(), + module_url_found: specifier.to_string(), + code: "export function b() { return 'b' }".to_owned(), + }; + async move { Ok(info) }.boxed() + } + + fn prepare_load( + &self, + _load_id: ModuleLoadId, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin>>> { + self.prepare_load_count.fetch_add(1, Ordering::Relaxed); + async { Ok(()) }.boxed_local() + } + } + + #[test] + fn dyn_import_ok() { + run_in_task(|cx| { + let loader = Rc::new(DynImportOkLoader::default()); + let prepare_load_count = loader.prepare_load_count.clone(); + let resolve_count = loader.resolve_count.clone(); + let load_count = loader.load_count.clone(); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); + + // Dynamically import mod_b + js_check(isolate.execute( + "file:///dyn_import3.js", + r#" + (async () => { + let mod = await import("./b.js"); + if (mod.b() !== 'b') { + throw Error("bad1"); + } + // And again! + mod = await import("./b.js"); + if (mod.b() !== 'b') { + throw Error("bad2"); + } + })(); + "#, + )); + + // First poll runs `prepare_load` hook. + assert!(matches!(isolate.poll_unpin(cx), Poll::Pending)); + assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1); + + // Second poll actually loads modules into the isolate. + assert!(matches!(isolate.poll_unpin(cx), Poll::Ready(Ok(_)))); + assert_eq!(resolve_count.load(Ordering::Relaxed), 4); + assert_eq!(load_count.load(Ordering::Relaxed), 2); + assert!(matches!(isolate.poll_unpin(cx), Poll::Ready(Ok(_)))); + assert_eq!(resolve_count.load(Ordering::Relaxed), 4); + assert_eq!(load_count.load(Ordering::Relaxed), 2); + }) + } + + #[test] + fn dyn_import_borrow_mut_error() { + // https://github.com/denoland/deno/issues/6054 + run_in_task(|cx| { + let loader = Rc::new(DynImportOkLoader::default()); + let prepare_load_count = loader.prepare_load_count.clone(); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); + js_check(isolate.execute( + "file:///dyn_import3.js", + r#" + (async () => { + let mod = await import("./b.js"); + if (mod.b() !== 'b') { + throw Error("bad"); + } + // Now do any op + Deno.core.ops(); + })(); + "#, + )); + // First poll runs `prepare_load` hook. + let _ = isolate.poll_unpin(cx); + assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1); + // Second poll triggers error + let _ = isolate.poll_unpin(cx); + }) + } + + #[test] + fn es_snapshot() { + #[derive(Default)] + struct ModsLoader; + + impl ModuleLoader for ModsLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result { + assert_eq!(specifier, "file:///main.js"); + assert_eq!(referrer, "."); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + unreachable!() + } + } + + let loader = std::rc::Rc::new(ModsLoader::default()); + let mut runtime_isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + true, + ); + + let specifier = ModuleSpecifier::resolve_url("file:///main.js").unwrap(); + let source_code = "Deno.core.print('hello\\n')".to_string(); + + let module_id = futures::executor::block_on( + runtime_isolate.load_module(&specifier, Some(source_code)), + ) + .unwrap(); + + js_check(runtime_isolate.mod_evaluate(module_id)); + + let _snapshot = runtime_isolate.snapshot(); + } } diff --git a/core/es_isolate.rs b/core/es_isolate.rs deleted file mode 100644 index 194f1adfa6..0000000000 --- a/core/es_isolate.rs +++ /dev/null @@ -1,983 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// This module provides higher level implementation of CoreIsolate that -// supports asynchronous loading and executution of ES Modules. -// The isolate.rs should never depend on this module. - -use rusty_v8 as v8; - -use crate::bindings; -use crate::errors::ErrBox; -use crate::errors::ErrWithV8Handle; -use crate::futures::FutureExt; -use crate::OpRouter; -use futures::ready; -use futures::stream::FuturesUnordered; -use futures::stream::StreamExt; -use futures::stream::StreamFuture; -use futures::task::AtomicWaker; -use futures::Future; -use std::cell::RefCell; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::ops::{Deref, DerefMut}; -use std::option::Option; -use std::pin::Pin; -use std::rc::Rc; -use std::task::Context; -use std::task::Poll; - -use crate::core_isolate::exception_to_err_result; -use crate::errors::attach_handle_to_error; -use crate::module_specifier::ModuleSpecifier; -use crate::modules::LoadState; -use crate::modules::ModuleId; -use crate::modules::ModuleLoadId; -use crate::modules::ModuleLoader; -use crate::modules::ModuleSource; -use crate::modules::Modules; -use crate::modules::PrepareLoadFuture; -use crate::modules::RecursiveModuleLoad; -use crate::CoreIsolate; -use crate::StartupData; - -/// More specialized version of `CoreIsolate` that provides loading -/// and execution of ES Modules. -/// -/// Creating `EsIsolate` requires to pass `loader` argument -/// that implements `ModuleLoader` trait - that way actual resolution and -/// loading of modules can be customized by the implementor. -pub struct EsIsolate(CoreIsolate); - -pub struct EsIsolateState { - loader: Rc, - pub modules: Modules, - pub(crate) dyn_import_map: - HashMap>, - - preparing_dyn_imports: FuturesUnordered>>, - pending_dyn_imports: FuturesUnordered>, - waker: AtomicWaker, -} - -impl Deref for EsIsolate { - type Target = CoreIsolate; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for EsIsolate { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl EsIsolate { - pub fn new( - loader: Rc, - op_router: Rc, - startup_data: StartupData, - will_snapshot: bool, - ) -> Self { - let mut core_isolate = - CoreIsolate::new(op_router, startup_data, will_snapshot); - { - core_isolate.set_host_initialize_import_meta_object_callback( - bindings::host_initialize_import_meta_object_callback, - ); - core_isolate.set_host_import_module_dynamically_callback( - bindings::host_import_module_dynamically_callback, - ); - } - - core_isolate.set_slot(Rc::new(RefCell::new(EsIsolateState { - modules: Modules::new(), - loader, - dyn_import_map: HashMap::new(), - preparing_dyn_imports: FuturesUnordered::new(), - pending_dyn_imports: FuturesUnordered::new(), - waker: AtomicWaker::new(), - }))); - - EsIsolate(core_isolate) - } - - /// Low-level module creation. - /// - /// Called during module loading or dynamic import loading. - fn mod_new( - &mut self, - main: bool, - name: &str, - source: &str, - ) -> Result { - let state_rc = Self::state(self); - let core_state_rc = CoreIsolate::state(self); - let scope = &mut v8::HandleScope::with_context( - &mut *self.0, - core_state_rc.borrow().global_context.as_ref().unwrap(), - ); - - let name_str = v8::String::new(scope, name).unwrap(); - let source_str = v8::String::new(scope, source).unwrap(); - - let origin = bindings::module_origin(scope, name_str); - let source = v8::script_compiler::Source::new(source_str, &origin); - - let tc_scope = &mut v8::TryCatch::new(scope); - - let maybe_module = v8::script_compiler::compile_module(tc_scope, source); - - if tc_scope.has_caught() { - assert!(maybe_module.is_none()); - let e = tc_scope.exception().unwrap(); - return exception_to_err_result(tc_scope, e); - } - - let module = maybe_module.unwrap(); - let id = module.get_identity_hash(); - - let mut import_specifiers: Vec = vec![]; - for i in 0..module.get_module_requests_length() { - let import_specifier = - module.get_module_request(i).to_rust_string_lossy(tc_scope); - let state = state_rc.borrow(); - let module_specifier = - state.loader.resolve(&import_specifier, name, false)?; - import_specifiers.push(module_specifier); - } - - state_rc.borrow_mut().modules.register( - id, - name, - main, - v8::Global::::new(tc_scope, module), - import_specifiers, - ); - - Ok(id) - } - - /// Instantiates a ES module - /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JSError, however it may be a - /// different type if CoreIsolate::set_js_error_create_fn() has been used. - fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> { - let core_state_rc = CoreIsolate::state(self); - let core_state = core_state_rc.borrow(); - let scope = &mut v8::HandleScope::with_context( - &mut *self.0, - core_state.global_context.as_ref().unwrap(), - ); - let tc_scope = &mut v8::TryCatch::new(scope); - - let module = match Self::state(tc_scope).borrow().modules.get_info(id) { - Some(info) => v8::Local::new(tc_scope, &info.handle), - None if id == 0 => return Ok(()), - _ => panic!("module id {} not found in module table", id), - }; - - if module.get_status() == v8::ModuleStatus::Errored { - exception_to_err_result(tc_scope, module.get_exception())? - } - - let result = - module.instantiate_module(tc_scope, bindings::module_resolve_callback); - match result { - Some(_) => Ok(()), - None => { - let exception = tc_scope.exception().unwrap(); - exception_to_err_result(tc_scope, exception) - } - } - } - - /// Evaluates an already instantiated ES module. - /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JSError, however it may be a - /// different type if CoreIsolate::set_js_error_create_fn() has been used. - pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { - self.shared_init(); - - let core_state_rc = CoreIsolate::state(self); - - let scope = &mut v8::HandleScope::with_context( - &mut *self.0, - core_state_rc.borrow().global_context.as_ref().unwrap(), - ); - - let module = Self::state(scope) - .borrow() - .modules - .get_info(id) - .map(|info| v8::Local::new(scope, &info.handle)) - .expect("ModuleInfo not found"); - let mut status = module.get_status(); - - if status == v8::ModuleStatus::Instantiated { - // IMPORTANT: Top-level-await is enabled, which means that return value - // of module evaluation is a promise. - // - // Because that promise is created internally by V8, when error occurs during - // module evaluation the promise is rejected, and since the promise has no rejection - // handler it will result in call to `bindings::promise_reject_callback` adding - // the promise to pending promise rejection table - meaning Isolate will return - // error on next poll(). - // - // This situation is not desirable as we want to manually return error at the - // end of this function to handle it further. It means we need to manually - // remove this promise from pending promise rejection table. - // - // For more details see: - // https://github.com/denoland/deno/issues/4908 - // https://v8.dev/features/top-level-await#module-execution-order - let maybe_value = module.evaluate(scope); - - // Update status after evaluating. - status = module.get_status(); - - if let Some(value) = maybe_value { - assert!( - status == v8::ModuleStatus::Evaluated - || status == v8::ModuleStatus::Errored - ); - let promise = v8::Local::::try_from(value) - .expect("Expected to get promise as module evaluation result"); - let promise_id = promise.get_identity_hash(); - let mut core_state = core_state_rc.borrow_mut(); - core_state.pending_promise_exceptions.remove(&promise_id); - } else { - assert!(status == v8::ModuleStatus::Errored); - } - } - - match status { - v8::ModuleStatus::Evaluated => Ok(()), - v8::ModuleStatus::Errored => { - let exception = module.get_exception(); - exception_to_err_result(scope, exception) - .map_err(|err| attach_handle_to_error(scope, err, exception)) - } - other => panic!("Unexpected module status {:?}", other), - } - } - - fn dyn_import_error( - &mut self, - id: ModuleLoadId, - err: ErrBox, - ) -> Result<(), ErrBox> { - let state_rc = Self::state(self); - let core_state_rc = CoreIsolate::state(self); - - let scope = &mut v8::HandleScope::with_context( - &mut *self.0, - core_state_rc.borrow().global_context.as_ref().unwrap(), - ); - - let resolver_handle = state_rc - .borrow_mut() - .dyn_import_map - .remove(&id) - .expect("Invalid dyn import id"); - let resolver = resolver_handle.get(scope); - - let exception = err - .downcast_ref::() - .map(|err| err.get_handle(scope)) - .unwrap_or_else(|| { - let message = err.to_string(); - let message = v8::String::new(scope, &message).unwrap(); - v8::Exception::type_error(scope, message) - }); - - resolver.reject(scope, exception).unwrap(); - scope.perform_microtask_checkpoint(); - Ok(()) - } - - fn dyn_import_done( - &mut self, - id: ModuleLoadId, - mod_id: ModuleId, - ) -> Result<(), ErrBox> { - let state_rc = Self::state(self); - - let core_state_rc = CoreIsolate::state(self); - - debug!("dyn_import_done {} {:?}", id, mod_id); - assert!(mod_id != 0); - let scope = &mut v8::HandleScope::with_context( - &mut *self.0, - core_state_rc.borrow().global_context.as_ref().unwrap(), - ); - - let resolver_handle = state_rc - .borrow_mut() - .dyn_import_map - .remove(&id) - .expect("Invalid dyn import id"); - let resolver = resolver_handle.get(scope); - - let module = { - let state = state_rc.borrow(); - state - .modules - .get_info(mod_id) - .map(|info| v8::Local::new(scope, &info.handle)) - .expect("Dyn import module info not found") - }; - // Resolution success - assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated); - - let module_namespace = module.get_module_namespace(); - resolver.resolve(scope, module_namespace).unwrap(); - scope.perform_microtask_checkpoint(); - Ok(()) - } - - fn prepare_dyn_imports( - &mut self, - cx: &mut Context, - ) -> Poll> { - let state_rc = Self::state(self); - - loop { - let r = { - let mut state = state_rc.borrow_mut(); - state.preparing_dyn_imports.poll_next_unpin(cx) - }; - match r { - Poll::Pending | Poll::Ready(None) => { - // There are no active dynamic import loaders, or none are ready. - return Poll::Ready(Ok(())); - } - Poll::Ready(Some(prepare_poll)) => { - let dyn_import_id = prepare_poll.0; - let prepare_result = prepare_poll.1; - - match prepare_result { - Ok(load) => { - let state = state_rc.borrow_mut(); - state.pending_dyn_imports.push(load.into_future()); - } - Err(err) => { - self.dyn_import_error(dyn_import_id, err)?; - } - } - } - } - } - } - - fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll> { - let state_rc = Self::state(self); - loop { - let poll_result = { - let mut state = state_rc.borrow_mut(); - state.pending_dyn_imports.poll_next_unpin(cx) - }; - - match poll_result { - Poll::Pending | Poll::Ready(None) => { - // There are no active dynamic import loaders, or none are ready. - return Poll::Ready(Ok(())); - } - Poll::Ready(Some(load_stream_poll)) => { - let maybe_result = load_stream_poll.0; - let mut load = load_stream_poll.1; - let dyn_import_id = load.id; - - if let Some(load_stream_result) = maybe_result { - match load_stream_result { - Ok(info) => { - // A module (not necessarily the one dynamically imported) has been - // fetched. Create and register it, and if successful, poll for the - // next recursive-load event related to this dynamic import. - match self.register_during_load(info, &mut load) { - Ok(()) => { - // Keep importing until it's fully drained - let state = state_rc.borrow_mut(); - state.pending_dyn_imports.push(load.into_future()); - } - Err(err) => self.dyn_import_error(dyn_import_id, err)?, - } - } - Err(err) => { - // A non-javascript error occurred; this could be due to a an invalid - // module specifier, or a problem with the source map, or a failure - // to fetch the module source code. - self.dyn_import_error(dyn_import_id, err)? - } - } - } else { - // The top-level module from a dynamic import has been instantiated. - // Load is done. - let module_id = load.root_module_id.unwrap(); - self.mod_instantiate(module_id)?; - match self.mod_evaluate(module_id) { - Ok(()) => self.dyn_import_done(dyn_import_id, module_id)?, - Err(err) => self.dyn_import_error(dyn_import_id, err)?, - }; - } - } - } - } - } - - fn register_during_load( - &mut self, - info: ModuleSource, - load: &mut RecursiveModuleLoad, - ) -> Result<(), ErrBox> { - let ModuleSource { - code, - module_url_specified, - module_url_found, - } = info; - - let is_main = - load.state == LoadState::LoadingRoot && !load.is_dynamic_import(); - let referrer_specifier = - ModuleSpecifier::resolve_url(&module_url_found).unwrap(); - - let state_rc = Self::state(self); - // #A There are 3 cases to handle at this moment: - // 1. Source code resolved result have the same module name as requested - // and is not yet registered - // -> register - // 2. Source code resolved result have a different name as requested: - // 2a. The module with resolved module name has been registered - // -> alias - // 2b. The module with resolved module name has not yet been registered - // -> register & alias - - // If necessary, register an alias. - if module_url_specified != module_url_found { - let mut state = state_rc.borrow_mut(); - state - .modules - .alias(&module_url_specified, &module_url_found); - } - - let maybe_mod_id = { - let state = state_rc.borrow(); - state.modules.get_id(&module_url_found) - }; - - let module_id = match maybe_mod_id { - Some(id) => { - // Module has already been registered. - debug!( - "Already-registered module fetched again: {}", - module_url_found - ); - id - } - // Module not registered yet, do it now. - None => self.mod_new(is_main, &module_url_found, &code)?, - }; - - // Now we must iterate over all imports of the module and load them. - let imports = { - let state_rc = Self::state(self); - let state = state_rc.borrow(); - state.modules.get_children(module_id).unwrap().clone() - }; - - for module_specifier in imports { - let is_registered = { - let state_rc = Self::state(self); - let state = state_rc.borrow(); - state.modules.is_registered(&module_specifier) - }; - if !is_registered { - load - .add_import(module_specifier.to_owned(), referrer_specifier.clone()); - } - } - - // If we just finished loading the root module, store the root module id. - if load.state == LoadState::LoadingRoot { - load.root_module_id = Some(module_id); - load.state = LoadState::LoadingImports; - } - - if load.pending.is_empty() { - load.state = LoadState::Done; - } - - Ok(()) - } - - /// Asynchronously load specified module and all of it's dependencies - /// - /// User must call `Isolate::mod_evaluate` with returned `ModuleId` - /// manually after load is finished. - pub async fn load_module( - &mut self, - specifier: &ModuleSpecifier, - code: Option, - ) -> Result { - self.shared_init(); - let loader = { - let state_rc = Self::state(self); - let state = state_rc.borrow(); - state.loader.clone() - }; - - let load = RecursiveModuleLoad::main(&specifier.to_string(), code, loader); - let (_load_id, prepare_result) = load.prepare().await; - - let mut load = prepare_result?; - - while let Some(info_result) = load.next().await { - let info = info_result?; - self.register_during_load(info, &mut load)?; - } - - let root_id = load.root_module_id.expect("Root module id empty"); - self.mod_instantiate(root_id).map(|_| root_id) - } - - pub fn snapshot(&mut self) -> v8::StartupData { - let state_rc = Self::state(self); - std::mem::take(&mut state_rc.borrow_mut().modules); - CoreIsolate::snapshot(self) - } - - pub fn state(isolate: &v8::Isolate) -> Rc> { - let s = isolate.get_slot::>>().unwrap(); - s.clone() - } -} - -impl Future for EsIsolate { - type Output = Result<(), ErrBox>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let es_isolate = self.get_mut(); - - let state_rc = Self::state(es_isolate); - - { - let state = state_rc.borrow(); - state.waker.register(cx.waker()); - } - - let has_preparing = { - let state = state_rc.borrow(); - !state.preparing_dyn_imports.is_empty() - }; - if has_preparing { - let poll_imports = es_isolate.prepare_dyn_imports(cx)?; - assert!(poll_imports.is_ready()); - } - - let has_pending = { - let state = state_rc.borrow(); - !state.pending_dyn_imports.is_empty() - }; - if has_pending { - let poll_imports = es_isolate.poll_dyn_imports(cx)?; - assert!(poll_imports.is_ready()); - } - - match ready!(es_isolate.0.poll_unpin(cx)) { - Ok(()) => { - let state = state_rc.borrow(); - if state.pending_dyn_imports.is_empty() - && state.preparing_dyn_imports.is_empty() - { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - } -} - -impl EsIsolateState { - // Called by V8 during `Isolate::mod_instantiate`. - pub fn module_resolve_cb( - &mut self, - specifier: &str, - referrer_id: ModuleId, - ) -> ModuleId { - let referrer = self.modules.get_name(referrer_id).unwrap(); - let specifier = self - .loader - .resolve(specifier, referrer, false) - .expect("Module should have been already resolved"); - self.modules.get_id(specifier.as_str()).unwrap_or(0) - } - - // Called by V8 during `Isolate::mod_instantiate`. - pub fn dyn_import_cb( - &mut self, - resolver_handle: v8::Global, - specifier: &str, - referrer: &str, - ) { - debug!("dyn_import specifier {} referrer {} ", specifier, referrer); - - let load = RecursiveModuleLoad::dynamic_import( - specifier, - referrer, - self.loader.clone(), - ); - self.dyn_import_map.insert(load.id, resolver_handle); - self.waker.wake(); - let fut = load.prepare().boxed_local(); - self.preparing_dyn_imports.push(fut); - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::core_isolate::tests::run_in_task; - use crate::js_check; - use crate::modules::ModuleSourceFuture; - use crate::ops::*; - use crate::BasicState; - use crate::BufVec; - use std::io; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Arc; - - #[test] - fn test_mods() { - #[derive(Default)] - struct ModsLoader { - pub count: Arc, - } - - impl ModuleLoader for ModsLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - ) -> Result { - self.count.fetch_add(1, Ordering::Relaxed); - assert_eq!(specifier, "./b.js"); - assert_eq!(referrer, "file:///a.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } - - fn load( - &self, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin> { - unreachable!() - } - } - - let loader = Rc::new(ModsLoader::default()); - let state = BasicState::new(); - - let resolve_count = loader.count.clone(); - let dispatch_count = Arc::new(AtomicUsize::new(0)); - let dispatch_count_ = dispatch_count.clone(); - - let dispatcher = move |_state: Rc, bufs: BufVec| -> Op { - dispatch_count_.fetch_add(1, Ordering::Relaxed); - assert_eq!(bufs.len(), 1); - assert_eq!(bufs[0].len(), 1); - assert_eq!(bufs[0][0], 42); - let buf = [43u8, 0, 0, 0][..].into(); - Op::Async(futures::future::ready(buf).boxed()) - }; - state.register_op("test", dispatcher); - - let mut isolate = EsIsolate::new(loader, state, StartupData::None, false); - - js_check(isolate.execute( - "setup.js", - r#" - function assert(cond) { - if (!cond) { - throw Error("assert"); - } - } - "#, - )); - - assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - - let specifier_a = "file:///a.js".to_string(); - let mod_a = isolate - .mod_new( - true, - &specifier_a, - r#" - import { b } from './b.js' - if (b() != 'b') throw Error(); - let control = new Uint8Array([42]); - Deno.core.send(1, control); - "#, - ) - .unwrap(); - assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - - let state_rc = EsIsolate::state(&isolate); - { - let state = state_rc.borrow(); - let imports = state.modules.get_children(mod_a); - assert_eq!( - imports, - Some(&vec![ModuleSpecifier::resolve_url("file:///b.js").unwrap()]) - ); - } - let mod_b = isolate - .mod_new(false, "file:///b.js", "export function b() { return 'b' }") - .unwrap(); - { - let state = state_rc.borrow(); - let imports = state.modules.get_children(mod_b).unwrap(); - assert_eq!(imports.len(), 0); - } - - js_check(isolate.mod_instantiate(mod_b)); - assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - assert_eq!(resolve_count.load(Ordering::SeqCst), 1); - - js_check(isolate.mod_instantiate(mod_a)); - assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - - js_check(isolate.mod_evaluate(mod_a)); - assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); - } - - #[test] - fn dyn_import_err() { - #[derive(Clone, Default)] - struct DynImportErrLoader { - pub count: Arc, - } - - impl ModuleLoader for DynImportErrLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - ) -> Result { - self.count.fetch_add(1, Ordering::Relaxed); - assert_eq!(specifier, "/foo.js"); - assert_eq!(referrer, "file:///dyn_import2.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } - - fn load( - &self, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin> { - async { Err(io::Error::from(io::ErrorKind::NotFound).into()) }.boxed() - } - } - - // Test an erroneous dynamic import where the specified module isn't found. - run_in_task(|cx| { - let loader = Rc::new(DynImportErrLoader::default()); - let count = loader.count.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); - - js_check(isolate.execute( - "file:///dyn_import2.js", - r#" - (async () => { - await import("/foo.js"); - })(); - "#, - )); - - assert_eq!(count.load(Ordering::Relaxed), 0); - // We should get an error here. - let result = isolate.poll_unpin(cx); - if let Poll::Ready(Ok(_)) = result { - unreachable!(); - } - assert_eq!(count.load(Ordering::Relaxed), 2); - }) - } - - #[derive(Clone, Default)] - struct DynImportOkLoader { - pub prepare_load_count: Arc, - pub resolve_count: Arc, - pub load_count: Arc, - } - - impl ModuleLoader for DynImportOkLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - ) -> Result { - let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); - assert!(c < 4); - assert_eq!(specifier, "./b.js"); - assert_eq!(referrer, "file:///dyn_import3.js"); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } - - fn load( - &self, - specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin> { - self.load_count.fetch_add(1, Ordering::Relaxed); - let info = ModuleSource { - module_url_specified: specifier.to_string(), - module_url_found: specifier.to_string(), - code: "export function b() { return 'b' }".to_owned(), - }; - async move { Ok(info) }.boxed() - } - - fn prepare_load( - &self, - _load_id: ModuleLoadId, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin>>> { - self.prepare_load_count.fetch_add(1, Ordering::Relaxed); - async { Ok(()) }.boxed_local() - } - } - - #[test] - fn dyn_import_ok() { - run_in_task(|cx| { - let loader = Rc::new(DynImportOkLoader::default()); - let prepare_load_count = loader.prepare_load_count.clone(); - let resolve_count = loader.resolve_count.clone(); - let load_count = loader.load_count.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); - - // Dynamically import mod_b - js_check(isolate.execute( - "file:///dyn_import3.js", - r#" - (async () => { - let mod = await import("./b.js"); - if (mod.b() !== 'b') { - throw Error("bad1"); - } - // And again! - mod = await import("./b.js"); - if (mod.b() !== 'b') { - throw Error("bad2"); - } - })(); - "#, - )); - - // First poll runs `prepare_load` hook. - assert!(matches!(isolate.poll_unpin(cx), Poll::Pending)); - assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1); - - // Second poll actually loads modules into the isolate. - assert!(matches!(isolate.poll_unpin(cx), Poll::Ready(Ok(_)))); - assert_eq!(resolve_count.load(Ordering::Relaxed), 4); - assert_eq!(load_count.load(Ordering::Relaxed), 2); - assert!(matches!(isolate.poll_unpin(cx), Poll::Ready(Ok(_)))); - assert_eq!(resolve_count.load(Ordering::Relaxed), 4); - assert_eq!(load_count.load(Ordering::Relaxed), 2); - }) - } - - #[test] - fn dyn_import_borrow_mut_error() { - // https://github.com/denoland/deno/issues/6054 - run_in_task(|cx| { - let loader = Rc::new(DynImportOkLoader::default()); - let prepare_load_count = loader.prepare_load_count.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); - js_check(isolate.execute( - "file:///dyn_import3.js", - r#" - (async () => { - let mod = await import("./b.js"); - if (mod.b() !== 'b') { - throw Error("bad"); - } - // Now do any op - Deno.core.ops(); - })(); - "#, - )); - // First poll runs `prepare_load` hook. - let _ = isolate.poll_unpin(cx); - assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1); - // Second poll triggers error - let _ = isolate.poll_unpin(cx); - }) - } - - #[test] - fn es_snapshot() { - #[derive(Default)] - struct ModsLoader; - - impl ModuleLoader for ModsLoader { - fn resolve( - &self, - specifier: &str, - referrer: &str, - _is_main: bool, - ) -> Result { - assert_eq!(specifier, "file:///main.js"); - assert_eq!(referrer, "."); - let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); - Ok(s) - } - - fn load( - &self, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, - _is_dyn_import: bool, - ) -> Pin> { - unreachable!() - } - } - - let loader = std::rc::Rc::new(ModsLoader::default()); - let mut runtime_isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, true); - - let specifier = ModuleSpecifier::resolve_url("file:///main.js").unwrap(); - let source_code = "Deno.core.print('hello\\n')".to_string(); - - let module_id = futures::executor::block_on( - runtime_isolate.load_module(&specifier, Some(source_code)), - ) - .unwrap(); - - js_check(runtime_isolate.mod_evaluate(module_id)); - - let _snapshot = runtime_isolate.snapshot(); - } -} diff --git a/core/lib.rs b/core/lib.rs index 647c91aa4e..f09278b2f6 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -12,7 +12,6 @@ mod basic_state; mod bindings; mod core_isolate; mod errors; -mod es_isolate; mod flags; mod module_specifier; mod modules; @@ -37,8 +36,6 @@ pub use crate::core_isolate::StartupData; pub use crate::errors::AnyError; pub use crate::errors::ErrBox; pub use crate::errors::JSError; -pub use crate::es_isolate::EsIsolate; -pub use crate::es_isolate::EsIsolateState; pub use crate::flags::v8_set_flags; pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; diff --git a/core/modules.rs b/core/modules.rs index 817e1f25e8..7b1e0c5a3c 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -65,7 +65,7 @@ pub trait ModuleLoader { &self, specifier: &str, referrer: &str, - is_main: bool, + _is_main: bool, ) -> Result; /// Given ModuleSpecifier, load its source code. @@ -98,6 +98,31 @@ pub trait ModuleLoader { } } +/// Placeholder structure used when creating +/// isolate that doesn't support module loading. +pub(crate) struct NoopModuleLoader; + +impl ModuleLoader for NoopModuleLoader { + fn resolve( + &self, + _specifier: &str, + _referrer: &str, + _is_main: bool, + ) -> Result { + Err(ErrBox::error("Module loading is not supported")) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + async { Err(ErrBox::error("Module loading is not supported")) } + .boxed_local() + } +} + #[derive(Debug, Eq, PartialEq)] enum Kind { Main, @@ -520,9 +545,9 @@ impl fmt::Display for Deps { #[cfg(test)] mod tests { use super::*; - use crate::es_isolate::EsIsolate; use crate::js_check; use crate::BasicState; + use crate::CoreIsolate; use crate::StartupData; use futures::future::FutureExt; use std::error::Error; @@ -700,8 +725,12 @@ mod tests { fn test_recursive_load() { let loader = MockLoader::new(); let loads = loader.loads.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); let spec = ModuleSpecifier::resolve_url("file:///a.js").unwrap(); let a_id_fut = isolate.load_module(&spec, None); let a_id = futures::executor::block_on(a_id_fut).expect("Failed to load"); @@ -718,7 +747,7 @@ mod tests { ] ); - let state_rc = EsIsolate::state(&isolate); + let state_rc = CoreIsolate::state(&isolate); let state = state_rc.borrow(); let modules = &state.modules; assert_eq!(modules.get_id("file:///a.js"), Some(a_id)); @@ -763,8 +792,12 @@ mod tests { fn test_circular_load() { let loader = MockLoader::new(); let loads = loader.loads.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); let fut = async move { let spec = ModuleSpecifier::resolve_url("file:///circular1.js").unwrap(); @@ -783,7 +816,7 @@ mod tests { ] ); - let state_rc = EsIsolate::state(&isolate); + let state_rc = CoreIsolate::state(&isolate); let state = state_rc.borrow(); let modules = &state.modules; @@ -837,8 +870,12 @@ mod tests { fn test_redirect_load() { let loader = MockLoader::new(); let loads = loader.loads.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); let fut = async move { let spec = ModuleSpecifier::resolve_url("file:///redirect1.js").unwrap(); @@ -857,7 +894,7 @@ mod tests { ] ); - let state_rc = EsIsolate::state(&isolate); + let state_rc = CoreIsolate::state(&isolate); let state = state_rc.borrow(); let modules = &state.modules; @@ -902,8 +939,12 @@ mod tests { run_in_task(|mut cx| { let loader = MockLoader::new(); let loads = loader.loads.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); let spec = ModuleSpecifier::resolve_url("file:///main.js").unwrap(); let mut recursive_load = isolate.load_module(&spec, None).boxed_local(); @@ -948,8 +989,12 @@ mod tests { fn loader_disappears_after_error() { run_in_task(|mut cx| { let loader = MockLoader::new(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); let spec = ModuleSpecifier::resolve_url("file:///bad_import.js").unwrap(); let mut load_fut = isolate.load_module(&spec, None).boxed_local(); let result = load_fut.poll_unpin(&mut cx); @@ -977,8 +1022,12 @@ mod tests { fn recursive_load_main_with_code() { let loader = MockLoader::new(); let loads = loader.loads.clone(); - let mut isolate = - EsIsolate::new(loader, BasicState::new(), StartupData::None, false); + let mut isolate = CoreIsolate::new_with_loader( + loader, + BasicState::new(), + StartupData::None, + false, + ); // In default resolution code should be empty. // Instead we explicitly pass in our own code. // The behavior should be very similar to /a.js. @@ -998,7 +1047,7 @@ mod tests { vec!["file:///b.js", "file:///c.js", "file:///d.js"] ); - let state_rc = EsIsolate::state(&isolate); + let state_rc = CoreIsolate::state(&isolate); let state = state_rc.borrow(); let modules = &state.modules;