diff options
Diffstat (limited to 'core/core_isolate.rs')
-rw-r--r-- | core/core_isolate.rs | 905 |
1 files changed, 904 insertions, 1 deletions
diff --git a/core/core_isolate.rs b/core/core_isolate.rs index 52e856174..b8922b982 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<PendingOpFuture>, pub(crate) have_unpolled_ops: Cell<bool>, pub(crate) op_router: Rc<dyn OpRouter>, + loader: Rc<dyn ModuleLoader>, + pub modules: Modules, + pub(crate) dyn_import_map: + HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>, + preparing_dyn_imports: FuturesUnordered<Pin<Box<PrepareLoadFuture>>>, + pending_dyn_imports: FuturesUnordered<StreamFuture<RecursiveModuleLoad>>, waker: AtomicWaker, } @@ -199,6 +221,7 @@ pub struct HeapLimits { } pub(crate) struct IsolateOptions { + loader: Rc<dyn ModuleLoader>, op_router: Rc<dyn OpRouter>, startup_script: Option<OwnedScript>, startup_snapshot: Option<Snapshot>, @@ -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<dyn ModuleLoader>, + op_router: Rc<dyn OpRouter>, + 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<v8::PromiseResolver>, + 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<ModuleId, 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 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<ModuleSpecifier> = 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::<v8::Module>::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::<v8::Promise>::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::<ErrWithV8Handle>() + .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<Result<(), ErrBox>> { + 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<Result<(), ErrBox>> { + 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<String>, + ) -> Result<ModuleId, ErrBox> { + 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<AtomicUsize>, + } + + impl ModuleLoader for ModsLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + 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<ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + 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<BasicState>, 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<AtomicUsize>, + } + + impl ModuleLoader for DynImportErrLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + 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<ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + 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<AtomicUsize>, + pub resolve_count: Arc<AtomicUsize>, + pub load_count: Arc<AtomicUsize>, + } + + impl ModuleLoader for DynImportOkLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + 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<ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + 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<String>, + _is_dyn_import: bool, + ) -> Pin<Box<dyn Future<Output = Result<(), ErrBox>>>> { + 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<ModuleSpecifier, ErrBox> { + 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<ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Pin<Box<ModuleSourceFuture>> { + 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(); + } } |