diff options
Diffstat (limited to 'core/es_isolate.rs')
-rw-r--r-- | core/es_isolate.rs | 888 |
1 files changed, 888 insertions, 0 deletions
diff --git a/core/es_isolate.rs b/core/es_isolate.rs new file mode 100644 index 000000000..0e9bb5b5b --- /dev/null +++ b/core/es_isolate.rs @@ -0,0 +1,888 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// Do not add any dependency to modules.rs! +// modules.rs is complex and should remain decoupled from isolate.rs to keep the +// Isolate struct from becoming too bloating for users who do not need +// asynchronous module loading. + +use rusty_v8 as v8; + +use crate::any_error::ErrBox; +use crate::bindings; +use futures::future::Future; +use futures::future::FutureExt; +use futures::ready; +use futures::stream::FuturesUnordered; +use futures::stream::IntoStream; +use futures::stream::Stream; +use futures::stream::StreamExt; +use futures::stream::StreamFuture; +use futures::stream::TryStream; +use futures::stream::TryStreamExt; +use futures::task::AtomicWaker; +use libc::c_void; +use std::collections::HashMap; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::option::Option; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Context; +use std::task::Poll; + +use crate::isolate::Isolate; +use crate::isolate::StartupData; + +pub type ModuleId = i32; +pub type DynImportId = i32; +/// Represent result of fetching the source code of a module. Found module URL +/// might be different from specified URL used for loading due to redirections +/// (like HTTP 303). E.G. Both https://example.com/a.ts and +/// https://example.com/b.ts may point to https://example.com/c.ts +/// By keeping track of specified and found URL we can alias modules and avoid +/// recompiling the same code 3 times. +#[derive(Debug, Eq, PartialEq)] +pub struct SourceCodeInfo { + pub code: String, + pub module_url_specified: String, + pub module_url_found: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum RecursiveLoadEvent { + Fetch(SourceCodeInfo), + Instantiate(ModuleId), +} + +pub trait ImportStream: TryStream { + fn register( + &mut self, + source_code_info: SourceCodeInfo, + isolate: &mut EsIsolate, + ) -> Result<(), ErrBox>; +} + +type DynImportStream = Box< + dyn ImportStream< + Ok = RecursiveLoadEvent, + Error = ErrBox, + Item = Result<RecursiveLoadEvent, ErrBox>, + > + Send + + Unpin, +>; + +type DynImportFn = dyn Fn(DynImportId, &str, &str) -> DynImportStream; + +/// Wraps DynImportStream to include the DynImportId, so that it doesn't +/// need to be exposed. +#[derive(Debug)] +struct DynImport { + pub id: DynImportId, + pub inner: DynImportStream, +} + +impl fmt::Debug for DynImportStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DynImportStream(..)") + } +} + +impl Stream for DynImport { + type Item = Result<(DynImportId, RecursiveLoadEvent), (DynImportId, ErrBox)>; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll<Option<Self::Item>> { + let self_inner = self.get_mut(); + let result = ready!(self_inner.inner.try_poll_next_unpin(cx)).unwrap(); + match result { + Ok(event) => Poll::Ready(Some(Ok((self_inner.id, event)))), + Err(e) => Poll::Ready(Some(Err((self_inner.id, e)))), + } + } +} + +impl ImportStream for DynImport { + fn register( + &mut self, + source_code_info: SourceCodeInfo, + isolate: &mut EsIsolate, + ) -> Result<(), ErrBox> { + self.inner.register(source_code_info, isolate) + } +} + +pub struct ModuleInfo { + pub main: bool, + pub name: String, + pub handle: v8::Global<v8::Module>, + pub import_specifiers: Vec<String>, +} + +/// A single execution context of JavaScript. Corresponds roughly to the "Web +/// Worker" concept in the DOM. An Isolate is a Future that can be used with +/// Tokio. The Isolate future complete when there is an error or when all +/// pending ops have completed. +/// +/// Ops are created in JavaScript by calling Deno.core.dispatch(), and in Rust +/// by implementing dispatcher function that takes control buffer and optional zero copy buffer +/// as arguments. An async Op corresponds exactly to a Promise in JavaScript. +pub struct EsIsolate { + core_isolate: Box<Isolate>, + + mods_: HashMap<ModuleId, ModuleInfo>, + pub(crate) next_dyn_import_id: DynImportId, + pub(crate) dyn_import_map: + HashMap<DynImportId, v8::Global<v8::PromiseResolver>>, + pub(crate) resolve_context: *mut c_void, + + pending_dyn_imports: FuturesUnordered<StreamFuture<IntoStream<DynImport>>>, + dyn_import: Option<Arc<DynImportFn>>, + waker: AtomicWaker, +} + +impl Deref for EsIsolate { + type Target = Isolate; + + fn deref(&self) -> &Self::Target { + &self.core_isolate + } +} + +impl DerefMut for EsIsolate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.core_isolate + } +} + +unsafe impl Send for EsIsolate {} + +impl Drop for EsIsolate { + fn drop(&mut self) { + let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); + // Clear persistent handles we own. + { + let mut locker = v8::Locker::new(&isolate); + let mut hs = v8::HandleScope::new(&mut locker); + let scope = hs.enter(); + for module in self.mods_.values_mut() { + module.handle.reset(scope); + } + for handle in self.dyn_import_map.values_mut() { + handle.reset(scope); + } + } + } +} + +/// Called during mod_instantiate() to resolve imports. +type ResolveFn<'a> = dyn FnMut(&str, ModuleId) -> ModuleId + 'a; + +/// Used internally by Isolate::mod_instantiate to wrap ResolveFn and +/// encapsulate pointer casts. +pub struct ResolveContext<'a> { + pub resolve_fn: &'a mut ResolveFn<'a>, +} + +impl<'a> ResolveContext<'a> { + #[inline] + fn as_raw_ptr(&mut self) -> *mut c_void { + self as *mut _ as *mut c_void + } + + #[allow(clippy::missing_safety_doc)] + #[inline] + pub(crate) unsafe fn from_raw_ptr(ptr: *mut c_void) -> &'a mut Self { + &mut *(ptr as *mut _) + } +} + +impl EsIsolate { + pub fn new(startup_data: StartupData, will_snapshot: bool) -> Box<Self> { + let mut core_isolate = Isolate::new(startup_data, will_snapshot); + { + let isolate = core_isolate.v8_isolate.as_mut().unwrap(); + 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, + ); + } + + let es_isolate = Self { + core_isolate, + mods_: HashMap::new(), + next_dyn_import_id: 0, + dyn_import_map: HashMap::new(), + resolve_context: std::ptr::null_mut(), + dyn_import: None, + pending_dyn_imports: FuturesUnordered::new(), + waker: AtomicWaker::new(), + }; + + let mut boxed_es_isolate = Box::new(es_isolate); + { + let es_isolate_ptr: *mut Self = Box::into_raw(boxed_es_isolate); + boxed_es_isolate = unsafe { Box::from_raw(es_isolate_ptr) }; + unsafe { + let v8_isolate = boxed_es_isolate.v8_isolate.as_mut().unwrap(); + v8_isolate.set_data(1, es_isolate_ptr as *mut c_void); + }; + } + boxed_es_isolate + } + + fn mod_new2(&mut self, main: bool, name: &str, source: &str) -> ModuleId { + let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); + let mut locker = v8::Locker::new(&isolate); + + let mut hs = v8::HandleScope::new(&mut locker); + let scope = hs.enter(); + assert!(!self.core_isolate.global_context.is_empty()); + let mut context = self.core_isolate.global_context.get(scope).unwrap(); + context.enter(); + + 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 mut try_catch = v8::TryCatch::new(scope); + let tc = try_catch.enter(); + + let maybe_module = v8::script_compiler::compile_module(&isolate, source); + + if tc.has_caught() { + assert!(maybe_module.is_none()); + self.core_isolate.handle_exception( + scope, + context, + tc.exception().unwrap(), + ); + context.exit(); + return 0; + } + let module = maybe_module.unwrap(); + let id = module.get_identity_hash(); + + let mut import_specifiers: Vec<String> = vec![]; + for i in 0..module.get_module_requests_length() { + let specifier = module.get_module_request(i); + import_specifiers.push(specifier.to_rust_string_lossy(scope)); + } + + let mut handle = v8::Global::<v8::Module>::new(); + handle.set(scope, module); + self.mods_.insert( + id, + ModuleInfo { + main, + name: name.to_string(), + import_specifiers, + handle, + }, + ); + context.exit(); + id + } + + /// Low-level module creation. + pub fn mod_new( + &mut self, + main: bool, + name: &str, + source: &str, + ) -> Result<ModuleId, ErrBox> { + let id = self.mod_new2(main, name, source); + self.core_isolate.check_last_exception().map(|_| id) + } + + pub fn mod_get_imports(&self, id: ModuleId) -> Vec<String> { + let info = self.get_module_info(id).unwrap(); + let len = info.import_specifiers.len(); + let mut out = Vec::new(); + for i in 0..len { + let info = self.get_module_info(id).unwrap(); + let specifier = info.import_specifiers.get(i).unwrap().to_string(); + out.push(specifier); + } + out + } + + fn mod_instantiate2(&mut self, mut ctx: ResolveContext<'_>, id: ModuleId) { + self.resolve_context = ctx.as_raw_ptr(); + let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); + let mut locker = v8::Locker::new(isolate); + let mut hs = v8::HandleScope::new(&mut locker); + let scope = hs.enter(); + assert!(!self.core_isolate.global_context.is_empty()); + let mut context = self.core_isolate.global_context.get(scope).unwrap(); + context.enter(); + let mut try_catch = v8::TryCatch::new(scope); + let tc = try_catch.enter(); + + let maybe_info = self.get_module_info(id); + + if maybe_info.is_none() { + return; + } + + let module_handle = &maybe_info.unwrap().handle; + let mut module = module_handle.get(scope).unwrap(); + + if module.get_status() == v8::ModuleStatus::Errored { + return; + } + + let maybe_ok = + module.instantiate_module(context, bindings::module_resolve_callback); + assert!(maybe_ok.is_some() || tc.has_caught()); + + if tc.has_caught() { + self.core_isolate.handle_exception( + scope, + context, + tc.exception().unwrap(), + ); + } + + context.exit(); + self.resolve_context = std::ptr::null_mut(); + } + /// Instanciates a ES module + /// + /// ErrBox can be downcast to a type that exposes additional information about + /// the V8 exception. By default this type is CoreJSError, however it may be a + /// different type if Isolate::set_js_error_create() has been used. + pub fn mod_instantiate( + &mut self, + id: ModuleId, + resolve_fn: &mut ResolveFn, + ) -> Result<(), ErrBox> { + let ctx = ResolveContext { resolve_fn }; + self.mod_instantiate2(ctx, id); + self.core_isolate.check_last_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 CoreJSError, however it may be a + /// different type if Isolate::set_js_error_create() has been used. + pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + self.shared_init(); + let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); + let mut locker = v8::Locker::new(isolate); + let mut hs = v8::HandleScope::new(&mut locker); + let scope = hs.enter(); + assert!(!self.core_isolate.global_context.is_empty()); + let mut context = self.core_isolate.global_context.get(scope).unwrap(); + context.enter(); + + let info = self.get_module_info(id).expect("ModuleInfo not found"); + let mut module = info.handle.get(scope).expect("Empty module handle"); + let mut status = module.get_status(); + + if status == v8::ModuleStatus::Instantiated { + let ok = module.evaluate(scope, context).is_some(); + // Update status after evaluating. + status = module.get_status(); + if ok { + assert!( + status == v8::ModuleStatus::Evaluated + || status == v8::ModuleStatus::Errored + ); + } else { + assert!(status == v8::ModuleStatus::Errored); + } + } + + match status { + v8::ModuleStatus::Evaluated => { + self.core_isolate.last_exception_handle.reset(scope); + self.core_isolate.last_exception.take(); + } + v8::ModuleStatus::Errored => { + self.core_isolate.handle_exception( + scope, + context, + module.get_exception(), + ); + } + other => panic!("Unexpected module status {:?}", other), + }; + + context.exit(); + + self.core_isolate.check_last_exception() + } + + pub fn get_module_info(&self, id: ModuleId) -> Option<&ModuleInfo> { + if id == 0 { + return None; + } + self.mods_.get(&id) + } + + pub fn set_dyn_import<F>(&mut self, f: F) + where + F: Fn(DynImportId, &str, &str) -> DynImportStream + Send + Sync + 'static, + { + self.dyn_import = Some(Arc::new(f)); + } + + pub fn dyn_import_cb( + &mut self, + specifier: &str, + referrer: &str, + id: DynImportId, + ) { + debug!("dyn_import specifier {} referrer {} ", specifier, referrer); + + if let Some(ref f) = self.dyn_import { + let inner = f(id, specifier, referrer); + let stream = DynImport { inner, id }; + self.waker.wake(); + self + .pending_dyn_imports + .push(stream.into_stream().into_future()); + } else { + panic!("dyn_import callback not set") + } + } + + fn dyn_import_done( + &mut self, + id: DynImportId, + result: Result<ModuleId, Option<String>>, + ) -> Result<(), ErrBox> { + debug!("dyn_import_done {} {:?}", id, result); + let (mod_id, maybe_err_str) = match result { + Ok(mod_id) => (mod_id, None), + Err(None) => (0, None), + Err(Some(err_str)) => (0, Some(err_str)), + }; + + assert!( + (mod_id == 0 && maybe_err_str.is_some()) + || (mod_id != 0 && maybe_err_str.is_none()) + || (mod_id == 0 && !self.core_isolate.last_exception_handle.is_empty()) + ); + + let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); + let mut locker = v8::Locker::new(isolate); + let mut hs = v8::HandleScope::new(&mut locker); + let scope = hs.enter(); + assert!(!self.core_isolate.global_context.is_empty()); + let mut context = self.core_isolate.global_context.get(scope).unwrap(); + context.enter(); + + // TODO(ry) error on bad import_id. + let mut resolver_handle = self.dyn_import_map.remove(&id).unwrap(); + // Resolve. + let mut resolver = resolver_handle.get(scope).unwrap(); + resolver_handle.reset(scope); + + let maybe_info = self.get_module_info(mod_id); + + if let Some(info) = maybe_info { + // Resolution success + let mut module = info.handle.get(scope).unwrap(); + assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated); + let module_namespace = module.get_module_namespace(); + resolver.resolve(context, module_namespace).unwrap(); + } else { + // Resolution error. + if let Some(error_str) = maybe_err_str { + let msg = v8::String::new(scope, &error_str).unwrap(); + let isolate = context.get_isolate(); + isolate.enter(); + let e = v8::type_error(scope, msg); + isolate.exit(); + resolver.reject(context, e).unwrap(); + } else { + let e = self.core_isolate.last_exception_handle.get(scope).unwrap(); + self.core_isolate.last_exception_handle.reset(scope); + self.core_isolate.last_exception.take(); + resolver.reject(context, e).unwrap(); + } + } + + isolate.run_microtasks(); + + context.exit(); + self.core_isolate.check_last_exception() + } + + fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll<Result<(), ErrBox>> { + use RecursiveLoadEvent::*; + loop { + match self.pending_dyn_imports.poll_next_unpin(cx) { + Poll::Pending | Poll::Ready(None) => { + // There are no active dynamic import loaders, or none are ready. + return Poll::Ready(Ok(())); + } + Poll::Ready(Some(( + Some(Ok((dyn_import_id, Fetch(source_code_info)))), + mut stream, + ))) => { + // 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 stream.get_mut().register(source_code_info, self) { + Ok(()) => self.pending_dyn_imports.push(stream.into_future()), + Err(err) => { + self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))? + } + } + } + Poll::Ready(Some(( + Some(Ok((dyn_import_id, Instantiate(module_id)))), + _, + ))) => { + // The top-level module from a dynamic import has been instantiated. + match self.mod_evaluate(module_id) { + Ok(()) => self.dyn_import_done(dyn_import_id, Ok(module_id))?, + Err(..) => self.dyn_import_done(dyn_import_id, Err(None))?, + } + } + Poll::Ready(Some((Some(Err((dyn_import_id, 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_done(dyn_import_id, Err(Some(err.to_string())))? + } + Poll::Ready(Some((None, _))) => unreachable!(), + } + } + } +} + +impl Future for EsIsolate { + type Output = Result<(), ErrBox>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { + let inner = self.get_mut(); + + inner.waker.register(cx.waker()); + + // If there are any pending dyn_import futures, do those first. + if !inner.pending_dyn_imports.is_empty() { + let poll_imports = inner.poll_dyn_imports(cx)?; + assert!(poll_imports.is_ready()); + } + + match ready!(inner.core_isolate.poll_unpin(cx)) { + Ok(()) => { + if inner.pending_dyn_imports.is_empty() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::isolate::js_check; + use crate::isolate::tests::run_in_task; + use crate::isolate::PinnedBuf; + use crate::ops::*; + use std::io; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Mutex; + + pub fn setup() -> (Box<EsIsolate>, Arc<AtomicUsize>) { + let dispatch_count = Arc::new(AtomicUsize::new(0)); + let dispatch_count_ = dispatch_count.clone(); + + let mut isolate = EsIsolate::new(StartupData::None, false); + + let dispatcher = + move |control: &[u8], _zero_copy: Option<PinnedBuf>| -> CoreOp { + dispatch_count_.fetch_add(1, Ordering::Relaxed); + assert_eq!(control.len(), 1); + assert_eq!(control[0], 42); + let buf = vec![43u8, 0, 0, 0].into_boxed_slice(); + Op::Async(futures::future::ok(buf).boxed()) + }; + + isolate.register_op("test", dispatcher); + + js_check(isolate.execute( + "setup.js", + r#" + function assert(cond) { + if (!cond) { + throw Error("assert"); + } + } + "#, + )); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + (isolate, dispatch_count) + } + + #[test] + fn test_mods() { + let (mut isolate, dispatch_count) = setup(); + let mod_a = isolate + .mod_new( + true, + "a.js", + 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 imports = isolate.mod_get_imports(mod_a); + assert_eq!(imports, vec!["b.js".to_string()]); + let mod_b = isolate + .mod_new(false, "b.js", "export function b() { return 'b' }") + .unwrap(); + let imports = isolate.mod_get_imports(mod_b); + assert_eq!(imports.len(), 0); + + let resolve_count = Arc::new(AtomicUsize::new(0)); + let resolve_count_ = resolve_count.clone(); + + let mut resolve = move |specifier: &str, _referrer: ModuleId| -> ModuleId { + resolve_count_.fetch_add(1, Ordering::SeqCst); + assert_eq!(specifier, "b.js"); + mod_b + }; + + js_check(isolate.mod_instantiate(mod_b, &mut resolve)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + assert_eq!(resolve_count.load(Ordering::SeqCst), 0); + + js_check(isolate.mod_instantiate(mod_a, &mut resolve)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); + assert_eq!(resolve_count.load(Ordering::SeqCst), 1); + + js_check(isolate.mod_evaluate(mod_a)); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); + assert_eq!(resolve_count.load(Ordering::SeqCst), 1); + } + + struct MockImportStream(Vec<Result<RecursiveLoadEvent, ErrBox>>); + + impl Stream for MockImportStream { + type Item = Result<RecursiveLoadEvent, ErrBox>; + + fn poll_next( + self: Pin<&mut Self>, + _cx: &mut Context, + ) -> Poll<Option<Self::Item>> { + let inner = self.get_mut(); + let event = if inner.0.is_empty() { + None + } else { + Some(inner.0.remove(0)) + }; + Poll::Ready(event) + } + } + + impl ImportStream for MockImportStream { + fn register( + &mut self, + module_data: SourceCodeInfo, + isolate: &mut EsIsolate, + ) -> Result<(), ErrBox> { + let id = isolate.mod_new( + false, + &module_data.module_url_found, + &module_data.code, + )?; + println!( + "MockImportStream register {} {}", + id, module_data.module_url_found + ); + Ok(()) + } + } + + #[test] + fn dyn_import_err() { + // Test an erroneous dynamic import where the specified module isn't found. + run_in_task(|cx| { + let count = Arc::new(AtomicUsize::new(0)); + let count_ = count.clone(); + let mut isolate = EsIsolate::new(StartupData::None, false); + isolate.set_dyn_import(move |_, specifier, referrer| { + count_.fetch_add(1, Ordering::Relaxed); + assert_eq!(specifier, "foo.js"); + assert_eq!(referrer, "dyn_import2.js"); + let err = io::Error::from(io::ErrorKind::NotFound); + let stream = MockImportStream(vec![Err(err.into())]); + Box::new(stream) + }); + js_check(isolate.execute( + "dyn_import2.js", + r#" + (async () => { + await import("foo.js"); + })(); + "#, + )); + assert_eq!(count.load(Ordering::Relaxed), 1); + + // We should get an error here. + let result = isolate.poll_unpin(cx); + if let Poll::Ready(Ok(_)) = result { + unreachable!(); + } + }) + } + + #[test] + fn dyn_import_err2() { + use std::convert::TryInto; + // Import multiple modules to demonstrate that after failed dynamic import + // another dynamic import can still be run + run_in_task(|cx| { + let count = Arc::new(AtomicUsize::new(0)); + let count_ = count.clone(); + let mut isolate = EsIsolate::new(StartupData::None, false); + isolate.set_dyn_import(move |_, specifier, referrer| { + let c = count_.fetch_add(1, Ordering::Relaxed); + match c { + 0 => assert_eq!(specifier, "foo1.js"), + 1 => assert_eq!(specifier, "foo2.js"), + 2 => assert_eq!(specifier, "foo3.js"), + _ => unreachable!(), + } + assert_eq!(referrer, "dyn_import_error.js"); + + let source_code_info = SourceCodeInfo { + module_url_specified: specifier.to_owned(), + module_url_found: specifier.to_owned(), + code: "# not valid JS".to_owned(), + }; + let stream = MockImportStream(vec![ + Ok(RecursiveLoadEvent::Fetch(source_code_info)), + Ok(RecursiveLoadEvent::Instantiate(c.try_into().unwrap())), + ]); + Box::new(stream) + }); + + js_check(isolate.execute( + "dyn_import_error.js", + r#" + (async () => { + await import("foo1.js"); + })(); + (async () => { + await import("foo2.js"); + })(); + (async () => { + await import("foo3.js"); + })(); + "#, + )); + + assert_eq!(count.load(Ordering::Relaxed), 3); + // Now each poll should return error + assert!(match isolate.poll_unpin(cx) { + Poll::Ready(Err(_)) => true, + _ => false, + }); + assert!(match isolate.poll_unpin(cx) { + Poll::Ready(Err(_)) => true, + _ => false, + }); + assert!(match isolate.poll_unpin(cx) { + Poll::Ready(Err(_)) => true, + _ => false, + }); + }) + } + + #[test] + fn dyn_import_ok() { + run_in_task(|cx| { + let count = Arc::new(AtomicUsize::new(0)); + let count_ = count.clone(); + + // Sometimes Rust is really annoying. + let mod_b = Arc::new(Mutex::new(0)); + let mod_b2 = mod_b.clone(); + + let mut isolate = EsIsolate::new(StartupData::None, false); + isolate.set_dyn_import(move |_id, specifier, referrer| { + let c = count_.fetch_add(1, Ordering::Relaxed); + match c { + 0 => assert_eq!(specifier, "foo1.js"), + 1 => assert_eq!(specifier, "foo2.js"), + _ => unreachable!(), + } + assert_eq!(referrer, "dyn_import3.js"); + let mod_id = *mod_b2.lock().unwrap(); + let source_code_info = SourceCodeInfo { + module_url_specified: "foo.js".to_owned(), + module_url_found: "foo.js".to_owned(), + code: "".to_owned(), + }; + let stream = MockImportStream(vec![ + Ok(RecursiveLoadEvent::Fetch(source_code_info)), + Ok(RecursiveLoadEvent::Instantiate(mod_id)), + ]); + Box::new(stream) + }); + + // Instantiate mod_b + { + let mut mod_id = mod_b.lock().unwrap(); + *mod_id = isolate + .mod_new(false, "b.js", "export function b() { return 'b' }") + .unwrap(); + let mut resolve = move |_specifier: &str, + _referrer: ModuleId| + -> ModuleId { unreachable!() }; + js_check(isolate.mod_instantiate(*mod_id, &mut resolve)); + } + // Dynamically import mod_b + js_check(isolate.execute( + "dyn_import3.js", + r#" + (async () => { + let mod = await import("foo1.js"); + if (mod.b() !== 'b') { + throw Error("bad1"); + } + // And again! + mod = await import("foo2.js"); + if (mod.b() !== 'b') { + throw Error("bad2"); + } + })(); + "#, + )); + + assert_eq!(count.load(Ordering::Relaxed), 1); + assert!(match isolate.poll_unpin(cx) { + Poll::Ready(Ok(_)) => true, + _ => false, + }); + assert_eq!(count.load(Ordering::Relaxed), 2); + assert!(match isolate.poll_unpin(cx) { + Poll::Ready(Ok(_)) => true, + _ => false, + }); + assert_eq!(count.load(Ordering::Relaxed), 2); + }) + } +} |