diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-01-07 12:45:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-07 12:45:44 +0100 |
commit | ad9fd589d4131e847721323a730ba91161f1b95b (patch) | |
tree | 629af315e9b91c62ff6bdba86e775f0b17fa69ea | |
parent | 8bf383710fc32efdbf2996abf5130bbd9aecacd1 (diff) |
core: factor out EsIsolate from Isolate (#3613)
-rw-r--r-- | cli/js.rs | 4 | ||||
-rw-r--r-- | cli/worker.rs | 4 | ||||
-rw-r--r-- | core/bindings.rs | 196 | ||||
-rw-r--r-- | core/es_isolate.rs | 888 | ||||
-rw-r--r-- | core/isolate.rs | 1013 | ||||
-rw-r--r-- | core/lib.rs | 2 | ||||
-rw-r--r-- | core/modules.rs | 22 | ||||
-rw-r--r-- | deno_typescript/lib.rs | 12 |
8 files changed, 1139 insertions, 1002 deletions
@@ -28,7 +28,7 @@ pub fn get_asset(name: &str) -> Option<&'static str> { #[test] fn cli_snapshot() { - let mut isolate = deno_core::Isolate::new( + let mut isolate = deno_core::EsIsolate::new( deno_core::StartupData::Snapshot(CLI_SNAPSHOT), false, ); @@ -45,7 +45,7 @@ fn cli_snapshot() { #[test] fn compiler_snapshot() { - let mut isolate = deno_core::Isolate::new( + let mut isolate = deno_core::EsIsolate::new( deno_core::StartupData::Snapshot(COMPILER_SNAPSHOT), false, ); diff --git a/cli/worker.rs b/cli/worker.rs index e25aa8018..cd8d6c8ae 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -35,7 +35,7 @@ pub struct WorkerChannels { #[derive(Clone)] pub struct Worker { pub name: String, - isolate: Arc<Mutex<Box<deno_core::Isolate>>>, + isolate: Arc<Mutex<Box<deno_core::EsIsolate>>>, pub state: ThreadSafeState, external_channels: Arc<Mutex<WorkerChannels>>, } @@ -48,7 +48,7 @@ impl Worker { external_channels: WorkerChannels, ) -> Self { let isolate = - Arc::new(Mutex::new(deno_core::Isolate::new(startup_data, false))); + Arc::new(Mutex::new(deno_core::EsIsolate::new(startup_data, false))); { let mut i = isolate.lock().unwrap(); let op_registry = i.op_registry.clone(); diff --git a/core/bindings.rs b/core/bindings.rs index 78777089b..ee78cf01d 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -1,9 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::es_isolate::EsIsolate; +use crate::es_isolate::ResolveContext; use crate::isolate::DenoBuf; use crate::isolate::Isolate; use crate::isolate::PinnedBuf; -use crate::isolate::ResolveContext; use rusty_v8 as v8; use v8::InIsolate; @@ -230,8 +231,8 @@ pub extern "C" fn host_import_module_dynamically_callback( let mut hs = v8::EscapableHandleScope::new(cbs.enter()); let scope = hs.enter(); let isolate = scope.isolate(); - let deno_isolate: &mut Isolate = - unsafe { &mut *(isolate.get_data(0) as *mut Isolate) }; + let deno_isolate: &mut EsIsolate = + unsafe { &mut *(isolate.get_data(1) as *mut EsIsolate) }; // NOTE(bartlomieju): will crash for non-UTF-8 specifier let specifier_str = specifier @@ -276,8 +277,8 @@ pub extern "C" fn host_initialize_import_meta_object_callback( let mut hs = v8::HandleScope::new(cbs.enter()); let scope = hs.enter(); let isolate = scope.isolate(); - let deno_isolate: &mut Isolate = - unsafe { &mut *(isolate.get_data(0) as *mut Isolate) }; + let deno_isolate: &mut EsIsolate = + unsafe { &mut *(isolate.get_data(1) as *mut EsIsolate) }; let id = module.get_identity_hash(); assert_ne!(id, 0); @@ -704,8 +705,8 @@ pub fn module_resolve_callback( let mut cbs = v8::CallbackScope::new(context); let cb_scope = cbs.enter(); let isolate = cb_scope.isolate(); - let deno_isolate: &mut Isolate = - unsafe { &mut *(isolate.get_data(0) as *mut Isolate) }; + let deno_isolate: &mut EsIsolate = + unsafe { &mut *(isolate.get_data(1) as *mut EsIsolate) }; let mut locker = v8::Locker::new(isolate); let mut hs = v8::EscapableHandleScope::new(&mut locker); @@ -747,3 +748,184 @@ pub fn module_resolve_callback( std::ptr::null_mut() } + +pub fn encode_message_as_object<'a>( + s: &mut impl v8::ToLocal<'a>, + context: v8::Local<v8::Context>, + message: v8::Local<v8::Message>, +) -> v8::Local<'a, v8::Object> { + let json_obj = v8::Object::new(s); + + let exception_str = message.get(s); + json_obj.set( + context, + v8::String::new(s, "message").unwrap().into(), + exception_str.into(), + ); + + let script_resource_name = message + .get_script_resource_name(s) + .expect("Missing ScriptResourceName"); + json_obj.set( + context, + v8::String::new(s, "scriptResourceName").unwrap().into(), + script_resource_name, + ); + + let source_line = message + .get_source_line(s, context) + .expect("Missing SourceLine"); + json_obj.set( + context, + v8::String::new(s, "sourceLine").unwrap().into(), + source_line.into(), + ); + + let line_number = message + .get_line_number(context) + .expect("Missing LineNumber"); + json_obj.set( + context, + v8::String::new(s, "lineNumber").unwrap().into(), + v8::Integer::new(s, line_number as i32).into(), + ); + + json_obj.set( + context, + v8::String::new(s, "startPosition").unwrap().into(), + v8::Integer::new(s, message.get_start_position() as i32).into(), + ); + + json_obj.set( + context, + v8::String::new(s, "endPosition").unwrap().into(), + v8::Integer::new(s, message.get_end_position() as i32).into(), + ); + + json_obj.set( + context, + v8::String::new(s, "errorLevel").unwrap().into(), + v8::Integer::new(s, message.error_level() as i32).into(), + ); + + json_obj.set( + context, + v8::String::new(s, "startColumn").unwrap().into(), + v8::Integer::new(s, message.get_start_column() as i32).into(), + ); + + json_obj.set( + context, + v8::String::new(s, "endColumn").unwrap().into(), + v8::Integer::new(s, message.get_end_column() as i32).into(), + ); + + let is_shared_cross_origin = + v8::Boolean::new(s, message.is_shared_cross_origin()); + + json_obj.set( + context, + v8::String::new(s, "isSharedCrossOrigin").unwrap().into(), + is_shared_cross_origin.into(), + ); + + let is_opaque = v8::Boolean::new(s, message.is_opaque()); + + json_obj.set( + context, + v8::String::new(s, "isOpaque").unwrap().into(), + is_opaque.into(), + ); + + let frames = if let Some(stack_trace) = message.get_stack_trace(s) { + let count = stack_trace.get_frame_count() as i32; + let frames = v8::Array::new(s, count); + + for i in 0..count { + let frame = stack_trace + .get_frame(s, i as usize) + .expect("No frame found"); + let frame_obj = v8::Object::new(s); + frames.set(context, v8::Integer::new(s, i).into(), frame_obj.into()); + frame_obj.set( + context, + v8::String::new(s, "line").unwrap().into(), + v8::Integer::new(s, frame.get_line_number() as i32).into(), + ); + frame_obj.set( + context, + v8::String::new(s, "column").unwrap().into(), + v8::Integer::new(s, frame.get_column() as i32).into(), + ); + + if let Some(function_name) = frame.get_function_name(s) { + frame_obj.set( + context, + v8::String::new(s, "functionName").unwrap().into(), + function_name.into(), + ); + } + + let script_name = match frame.get_script_name_or_source_url(s) { + Some(name) => name, + None => v8::String::new(s, "<unknown>").unwrap(), + }; + frame_obj.set( + context, + v8::String::new(s, "scriptName").unwrap().into(), + script_name.into(), + ); + + frame_obj.set( + context, + v8::String::new(s, "isEval").unwrap().into(), + v8::Boolean::new(s, frame.is_eval()).into(), + ); + + frame_obj.set( + context, + v8::String::new(s, "isConstructor").unwrap().into(), + v8::Boolean::new(s, frame.is_constructor()).into(), + ); + + frame_obj.set( + context, + v8::String::new(s, "isWasm").unwrap().into(), + v8::Boolean::new(s, frame.is_wasm()).into(), + ); + } + + frames + } else { + // No stack trace. We only have one stack frame of info.. + let frames = v8::Array::new(s, 1); + let frame_obj = v8::Object::new(s); + frames.set(context, v8::Integer::new(s, 0).into(), frame_obj.into()); + + frame_obj.set( + context, + v8::String::new(s, "scriptResourceName").unwrap().into(), + script_resource_name, + ); + frame_obj.set( + context, + v8::String::new(s, "line").unwrap().into(), + v8::Integer::new(s, line_number as i32).into(), + ); + frame_obj.set( + context, + v8::String::new(s, "column").unwrap().into(), + v8::Integer::new(s, message.get_start_column() as i32).into(), + ); + + frames + }; + + json_obj.set( + context, + v8::String::new(s, "frames").unwrap().into(), + frames.into(), + ); + + json_obj +} 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); + }) + } +} diff --git a/core/isolate.rs b/core/isolate.rs index 47ef2c301..2ab639e00 100644 --- a/core/isolate.rs +++ b/core/isolate.rs @@ -17,17 +17,11 @@ use crate::shared_queue::RECOMMENDED_SIZE; use futures::future::FutureExt; use futures::future::TryFutureExt; 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::convert::From; -use std::fmt; use std::future::Future; use std::ops::{Deref, DerefMut}; use std::option::Option; @@ -159,10 +153,6 @@ impl AsMut<[u8]> for PinnedBuf { } } -// TODO(bartlomieju): move to core/modules.rs -pub type ModuleId = i32; -pub type DynImportId = i32; - pub enum SnapshotConfig { Borrowed(v8::StartupData<'static>), Owned(v8::OwnedStartupData), @@ -196,87 +186,6 @@ pub struct Script<'a> { pub filename: &'a str, } -/// 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 Isolate, - ) -> 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(); - match self_inner.inner.try_poll_next_unpin(cx) { - Poll::Ready(Some(Ok(event))) => { - Poll::Ready(Some(Ok((self_inner.id, event)))) - } - Poll::Ready(None) => unreachable!(), - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err((self_inner.id, e)))), - Poll::Pending => Poll::Pending, - } - } -} - -impl ImportStream for DynImport { - fn register( - &mut self, - source_code_info: SourceCodeInfo, - isolate: &mut Isolate, - ) -> Result<(), ErrBox> { - self.inner.register(source_code_info, isolate) - } -} - // TODO(ry) It's ugly that we have both Script and OwnedScript. Ideally we // wouldn't expose such twiddly complexity. struct OwnedScript { @@ -306,13 +215,6 @@ pub enum StartupData<'a> { type JSErrorCreateFn = dyn Fn(V8Exception) -> ErrBox; type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>; -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 @@ -323,7 +225,7 @@ pub struct ModuleInfo { /// as arguments. An async Op corresponds exactly to a Promise in JavaScript. #[allow(unused)] pub struct Isolate { - v8_isolate: Option<v8::OwnedIsolate>, + pub(crate) v8_isolate: Option<v8::OwnedIsolate>, snapshot_creator: Option<v8::SnapshotCreator>, has_snapshotted: bool, snapshot: Option<SnapshotConfig>, @@ -336,24 +238,14 @@ pub struct Isolate { pub(crate) current_send_cb_info: *const v8::FunctionCallbackInfo, pub(crate) pending_promise_map: HashMap<i32, v8::Global<v8::Value>>, - // TODO(bartlomieju): move into `core/modules.rs` - 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, - // TODO: end - // TODO: These two fields were not yet ported from libdeno // void* global_import_buf_ptr_; // v8::Persistent<v8::ArrayBuffer> global_import_buf_; shared_isolate_handle: Arc<Mutex<Option<*mut v8::Isolate>>>, - dyn_import: Option<Arc<DynImportFn>>, js_error_create: Arc<JSErrorCreateFn>, needs_init: bool, shared: SharedQueue, pending_ops: FuturesUnordered<PendingOpFuture>, - pending_dyn_imports: FuturesUnordered<StreamFuture<IntoStream<DynImport>>>, have_unpolled_ops: bool, startup_script: Option<OwnedScript>, pub op_registry: Arc<OpRegistry>, @@ -371,6 +263,7 @@ impl Drop for Isolate { // TODO Too much boiler plate. // <Boilerplate> let isolate = self.v8_isolate.take().unwrap(); + // Clear persistent handles we own. { let mut locker = v8::Locker::new(&isolate); let mut hs = v8::HandleScope::new(&mut locker); @@ -380,12 +273,6 @@ impl Drop for Isolate { self.shared_ab.reset(scope); self.last_exception_handle.reset(scope); self.js_recv_cb.reset(scope); - for (_key, module) in self.mods_.iter_mut() { - module.handle.reset(scope); - } - for (_key, handle) in self.dyn_import_map.iter_mut() { - handle.reset(scope); - } for (_key, handle) in self.pending_promise_map.iter_mut() { handle.reset(scope); } @@ -508,7 +395,6 @@ impl Isolate { last_exception: None, last_exception_handle: v8::Global::<v8::Value>::new(), global_context, - mods_: HashMap::new(), pending_promise_map: HashMap::new(), shared_buf: shared.as_deno_buf(), shared_ab: v8::Global::<v8::SharedArrayBuffer>::new(), @@ -517,17 +403,12 @@ impl Isolate { snapshot_creator: maybe_snapshot_creator, snapshot: load_snapshot, has_snapshotted: false, - next_dyn_import_id: 0, - dyn_import_map: HashMap::new(), - resolve_context: std::ptr::null_mut(), shared_isolate_handle: Arc::new(Mutex::new(None)), - dyn_import: None, js_error_create: Arc::new(CoreJSError::from_v8_exception), shared, needs_init, pending_ops: FuturesUnordered::new(), have_unpolled_ops: false, - pending_dyn_imports: FuturesUnordered::new(), startup_script, op_registry: Arc::new(OpRegistry::new()), waker: AtomicWaker::new(), @@ -548,27 +429,13 @@ impl Isolate { boxed_isolate } - // Methods ported from libdeno, to be refactored pub 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.add_message_listener(bindings::message_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 } - pub fn get_module_info(&self, id: ModuleId) -> Option<&ModuleInfo> { - if id == 0 { - return None; - } - self.mods_.get(&id) - } - pub fn handle_exception<'a>( &mut self, s: &mut impl v8::ToLocal<'a>, @@ -621,193 +488,11 @@ impl Isolate { context: v8::Local<v8::Context>, message: v8::Local<v8::Message>, ) -> String { - let json_obj = self.encode_message_as_object(s, context, message); + let json_obj = bindings::encode_message_as_object(s, context, message); let json_string = v8::json::stringify(context, json_obj.into()).unwrap(); json_string.to_rust_string_lossy(s) } - fn encode_message_as_object<'a>( - &mut self, - s: &mut impl v8::ToLocal<'a>, - context: v8::Local<v8::Context>, - message: v8::Local<v8::Message>, - ) -> v8::Local<'a, v8::Object> { - let json_obj = v8::Object::new(s); - - let exception_str = message.get(s); - json_obj.set( - context, - v8::String::new(s, "message").unwrap().into(), - exception_str.into(), - ); - - let script_resource_name = message - .get_script_resource_name(s) - .expect("Missing ScriptResourceName"); - json_obj.set( - context, - v8::String::new(s, "scriptResourceName").unwrap().into(), - script_resource_name, - ); - - let source_line = message - .get_source_line(s, context) - .expect("Missing SourceLine"); - json_obj.set( - context, - v8::String::new(s, "sourceLine").unwrap().into(), - source_line.into(), - ); - - let line_number = message - .get_line_number(context) - .expect("Missing LineNumber"); - json_obj.set( - context, - v8::String::new(s, "lineNumber").unwrap().into(), - v8::Integer::new(s, line_number as i32).into(), - ); - - json_obj.set( - context, - v8::String::new(s, "startPosition").unwrap().into(), - v8::Integer::new(s, message.get_start_position() as i32).into(), - ); - - json_obj.set( - context, - v8::String::new(s, "endPosition").unwrap().into(), - v8::Integer::new(s, message.get_end_position() as i32).into(), - ); - - json_obj.set( - context, - v8::String::new(s, "errorLevel").unwrap().into(), - v8::Integer::new(s, message.error_level() as i32).into(), - ); - - json_obj.set( - context, - v8::String::new(s, "startColumn").unwrap().into(), - v8::Integer::new(s, message.get_start_column() as i32).into(), - ); - - json_obj.set( - context, - v8::String::new(s, "endColumn").unwrap().into(), - v8::Integer::new(s, message.get_end_column() as i32).into(), - ); - - let is_shared_cross_origin = - v8::Boolean::new(s, message.is_shared_cross_origin()); - - json_obj.set( - context, - v8::String::new(s, "isSharedCrossOrigin").unwrap().into(), - is_shared_cross_origin.into(), - ); - - let is_opaque = v8::Boolean::new(s, message.is_opaque()); - - json_obj.set( - context, - v8::String::new(s, "isOpaque").unwrap().into(), - is_opaque.into(), - ); - - let frames = if let Some(stack_trace) = message.get_stack_trace(s) { - let count = stack_trace.get_frame_count() as i32; - let frames = v8::Array::new(s, count); - - for i in 0..count { - let frame = stack_trace - .get_frame(s, i as usize) - .expect("No frame found"); - let frame_obj = v8::Object::new(s); - frames.set(context, v8::Integer::new(s, i).into(), frame_obj.into()); - frame_obj.set( - context, - v8::String::new(s, "line").unwrap().into(), - v8::Integer::new(s, frame.get_line_number() as i32).into(), - ); - frame_obj.set( - context, - v8::String::new(s, "column").unwrap().into(), - v8::Integer::new(s, frame.get_column() as i32).into(), - ); - - if let Some(function_name) = frame.get_function_name(s) { - frame_obj.set( - context, - v8::String::new(s, "functionName").unwrap().into(), - function_name.into(), - ); - } - - let script_name = match frame.get_script_name_or_source_url(s) { - Some(name) => name, - None => v8::String::new(s, "<unknown>").unwrap(), - }; - frame_obj.set( - context, - v8::String::new(s, "scriptName").unwrap().into(), - script_name.into(), - ); - - frame_obj.set( - context, - v8::String::new(s, "isEval").unwrap().into(), - v8::Boolean::new(s, frame.is_eval()).into(), - ); - - frame_obj.set( - context, - v8::String::new(s, "isConstructor").unwrap().into(), - v8::Boolean::new(s, frame.is_constructor()).into(), - ); - - frame_obj.set( - context, - v8::String::new(s, "isWasm").unwrap().into(), - v8::Boolean::new(s, frame.is_wasm()).into(), - ); - } - - frames - } else { - // No stack trace. We only have one stack frame of info.. - let frames = v8::Array::new(s, 1); - let frame_obj = v8::Object::new(s); - frames.set(context, v8::Integer::new(s, 0).into(), frame_obj.into()); - - frame_obj.set( - context, - v8::String::new(s, "scriptResourceName").unwrap().into(), - script_resource_name, - ); - frame_obj.set( - context, - v8::String::new(s, "line").unwrap().into(), - v8::Integer::new(s, line_number as i32).into(), - ); - frame_obj.set( - context, - v8::String::new(s, "column").unwrap().into(), - v8::Integer::new(s, message.get_start_column() as i32).into(), - ); - - frames - }; - - json_obj.set( - context, - v8::String::new(s, "frames").unwrap().into(), - frames.into(), - ); - - json_obj - } - #[allow(dead_code)] pub fn run_microtasks(&mut self) { let isolate = self.v8_isolate.as_mut().unwrap(); @@ -816,7 +501,6 @@ impl Isolate { isolate.run_microtasks(); isolate.exit(); } - // End of methods from libdeno pub fn set_error_handler(&mut self, handler: Box<IsolateErrorHandleFn>) { self.error_handler = Some(handler); @@ -834,13 +518,6 @@ impl Isolate { self.op_registry.register(name, op) } - 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)); - } - /// Allows a callback to be set whenever a V8 exception is made. This allows /// the caller to wrap the V8Exception into an error. By default this callback /// is set to CoreJSError::from_v8_exception. @@ -859,7 +536,7 @@ impl Isolate { } /// Executes a bit of built-in JavaScript to provide Deno.sharedQueue. - fn shared_init(&mut self) { + pub(crate) fn shared_init(&mut self) { if self.needs_init { self.needs_init = false; js_check( @@ -872,26 +549,6 @@ impl Isolate { } } - 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") - } - } - pub fn pre_dispatch( &mut self, op_id: OpId, @@ -930,15 +587,24 @@ impl Isolate { } } - fn libdeno_execute<'a>( + /// Executes traditional JavaScript code (traditional = not ES modules) + /// + /// 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 execute( &mut self, - s: &mut impl v8::ToLocal<'a>, - context: v8::Local<'a, v8::Context>, js_filename: &str, js_source: &str, - ) -> bool { - let mut hs = v8::HandleScope::new(s); + ) -> Result<(), ErrBox> { + self.shared_init(); + let isolate = self.v8_isolate.as_ref().unwrap(); + let mut locker = v8::Locker::new(isolate); + assert!(!self.global_context.is_empty()); + let mut hs = v8::HandleScope::new(&mut locker); let s = hs.enter(); + let mut context = self.global_context.get(s).unwrap(); + context.enter(); let source = v8::String::new(s, js_source).unwrap(); let name = v8::String::new(s, js_filename).unwrap(); let mut try_catch = v8::TryCatch::new(s); @@ -951,54 +617,29 @@ impl Isolate { assert!(tc.has_caught()); let exception = tc.exception().unwrap(); self.handle_exception(s, context, exception); - false - } else { - true } - } - - /// Executes traditional JavaScript code (traditional = not ES modules) - /// - /// 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 execute( - &mut self, - js_filename: &str, - js_source: &str, - ) -> Result<(), ErrBox> { - self.shared_init(); - let isolate = self.v8_isolate.as_ref().unwrap(); - let mut locker = v8::Locker::new(isolate); - assert!(!self.global_context.is_empty()); - let mut hs = v8::HandleScope::new(&mut locker); - let scope = hs.enter(); - let mut context = self.global_context.get(scope).unwrap(); - context.enter(); - self.libdeno_execute(scope, context, js_filename, js_source); context.exit(); self.check_last_exception() } - fn check_last_exception(&mut self) -> Result<(), ErrBox> { - let maybe_err = self.last_exception.clone(); - match maybe_err { - None => Ok(()), - Some(json_str) => { - let js_error_create = &*self.js_error_create; - if self.error_handler.is_some() { - // We need to clear last exception to avoid double handling. - self.last_exception = None; - let v8_exception = V8Exception::from_json(&json_str).unwrap(); - let js_error = js_error_create(v8_exception); - let handler = self.error_handler.as_mut().unwrap(); - handler(js_error) - } else { - let v8_exception = V8Exception::from_json(&json_str).unwrap(); - let js_error = js_error_create(v8_exception); - Err(js_error) - } - } + pub(crate) fn check_last_exception(&mut self) -> Result<(), ErrBox> { + if self.last_exception.is_none() { + return Ok(()); + } + + let json_str = self.last_exception.clone().unwrap(); + let js_error_create = &*self.js_error_create; + if self.error_handler.is_some() { + // We need to clear last exception to avoid double handling. + self.last_exception = None; + let v8_exception = V8Exception::from_json(&json_str).unwrap(); + let js_error = js_error_create(v8_exception); + let handler = self.error_handler.as_mut().unwrap(); + handler(js_error) + } else { + let v8_exception = V8Exception::from_json(&json_str).unwrap(); + let js_error = js_error_create(v8_exception); + Err(js_error) } } @@ -1036,7 +677,7 @@ impl Isolate { isolate.throw_exception(msg.into()); } - fn libdeno_respond(&mut self, op_id: OpId, buf: DenoBuf) { + fn respond2(&mut self, op_id: OpId, buf: DenoBuf) { if !self.current_send_cb_info.is_null() { // Synchronous response. // Note op_id is not passed back in the case of synchronous response. @@ -1110,84 +751,10 @@ impl Isolate { None => (0, DenoBuf::empty()), Some((op_id, r)) => (op_id, DenoBuf::from(r)), }; - self.libdeno_respond(op_id, buf); + self.respond2(op_id, buf); self.check_last_exception() } - fn mod_new2(&mut self, main: bool, name: &str, source: &str) -> ModuleId { - let isolate = self.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.global_context.is_empty()); - let mut context = self.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.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.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 - } - /// Takes a snapshot. The isolate should have been created with will_snapshot /// set to true. /// @@ -1201,11 +768,6 @@ impl Isolate { let mut locker = v8::Locker::new(isolate); let mut hs = v8::HandleScope::new(&mut locker); let scope = hs.enter(); - - for (_key, module) in self.mods_.iter_mut() { - module.handle.reset(scope); - } - self.mods_.clear(); self.global_context.reset(scope); let snapshot_creator = self.snapshot_creator.as_mut().unwrap(); @@ -1218,240 +780,6 @@ impl Isolate { Err(err) => Err(err), } } - - 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.last_exception_handle.is_empty()) - ); - - let isolate = self.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.global_context.is_empty()); - let mut context = self.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.last_exception_handle.get(scope).unwrap(); - self.last_exception_handle.reset(scope); - self.last_exception.take(); - resolver.reject(context, e).unwrap(); - } - } - - isolate.run_microtasks(); - - context.exit(); - self.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!(), - } - } - } -} - -/// 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 Isolate { - fn libdeno_mod_instantiate( - &mut self, - mut ctx: ResolveContext<'_>, - id: ModuleId, - ) { - self.resolve_context = ctx.as_raw_ptr(); - let isolate = self.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.global_context.is_empty()); - let mut context = self.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.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.libdeno_mod_instantiate(ctx, id); - self.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.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.global_context.is_empty()); - let mut context = self.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.last_exception_handle.reset(scope); - self.last_exception.take(); - } - v8::ModuleStatus::Errored => { - self.handle_exception(scope, context, module.get_exception()); - } - other => panic!("Unexpected module status {:?}", other), - }; - - context.exit(); - - self.check_last_exception() - } } impl Future for Isolate { @@ -1467,12 +795,6 @@ impl Future for Isolate { let mut overflow_response: Option<(OpId, Buf)> = None; loop { - // 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()); - } - // Now handle actual ops. inner.have_unpolled_ops = false; #[allow(clippy::match_wild_err_arm)] @@ -1508,7 +830,7 @@ impl Future for Isolate { inner.check_last_exception()?; // We're idle if pending_ops is empty. - if inner.pending_ops.is_empty() && inner.pending_dyn_imports.is_empty() { + if inner.pending_ops.is_empty() { Poll::Ready(Ok(())) } else { if inner.have_unpolled_ops { @@ -1550,7 +872,6 @@ pub fn js_check<T>(r: Result<T, ErrBox>) -> T { pub mod tests { use super::*; use futures::future::lazy; - use std::io; use std::ops::FnOnce; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -1667,53 +988,6 @@ pub mod tests { } #[test] - fn test_mods() { - let (mut isolate, dispatch_count) = setup(Mode::Async); - 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); - } - - #[test] fn test_poll_async_delayed_ops() { run_in_task(|cx| { let (mut isolate, dispatch_count) = setup(Mode::Async); @@ -1766,215 +1040,6 @@ pub mod tests { }); } - 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 Isolate, - ) -> 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 = Isolate::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 = Isolate::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 = Isolate::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); - }) - } - #[test] fn terminate_execution() { let (tx, rx) = std::sync::mpsc::channel::<bool>(); diff --git a/core/lib.rs b/core/lib.rs index 8379b2b91..91f91a1c9 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -13,6 +13,7 @@ extern crate lazy_static; mod any_error; mod bindings; +mod es_isolate; mod flags; mod isolate; mod js_errors; @@ -26,6 +27,7 @@ mod shared_queue; use rusty_v8 as v8; pub use crate::any_error::*; +pub use crate::es_isolate::*; pub use crate::flags::v8_set_flags; pub use crate::isolate::*; pub use crate::js_errors::*; diff --git a/core/modules.rs b/core/modules.rs index 079bc5dd7..1579b3480 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -7,12 +7,12 @@ // synchronously. The isolate.rs module should never depend on this module. use crate::any_error::ErrBox; -use crate::isolate::DynImportId; -use crate::isolate::ImportStream; -use crate::isolate::Isolate; -use crate::isolate::ModuleId; -use crate::isolate::RecursiveLoadEvent as Event; -use crate::isolate::SourceCodeInfo; +use crate::es_isolate::DynImportId; +use crate::es_isolate::EsIsolate; +use crate::es_isolate::ImportStream; +use crate::es_isolate::ModuleId; +use crate::es_isolate::RecursiveLoadEvent as Event; +use crate::es_isolate::SourceCodeInfo; use crate::module_specifier::ModuleSpecifier; use futures::future::FutureExt; use futures::stream::FuturesUnordered; @@ -198,7 +198,7 @@ impl<L: Loader + Unpin> RecursiveLoad<L> { /// This future needs to take ownership of the isolate. pub fn get_future( self, - isolate: Arc<Mutex<Box<Isolate>>>, + isolate: Arc<Mutex<Box<EsIsolate>>>, ) -> impl Future<Output = Result<ModuleId, ErrBox>> { async move { let mut load = self; @@ -221,7 +221,7 @@ impl<L: Loader + Unpin> ImportStream for RecursiveLoad<L> { fn register( &mut self, source_code_info: SourceCodeInfo, - isolate: &mut Isolate, + isolate: &mut EsIsolate, ) -> Result<(), ErrBox> { // #A There are 3 cases to handle at this moment: // 1. Source code resolved result have the same module name as requested @@ -609,8 +609,8 @@ impl fmt::Display for Deps { #[cfg(test)] mod tests { use super::*; + use crate::es_isolate::tests::*; use crate::isolate::js_check; - use crate::isolate::tests::*; use futures::future::FutureExt; use futures::stream::StreamExt; use std::error::Error; @@ -619,14 +619,14 @@ mod tests { struct MockLoader { pub loads: Arc<Mutex<Vec<String>>>, - pub isolate: Arc<Mutex<Box<Isolate>>>, + pub isolate: Arc<Mutex<Box<EsIsolate>>>, pub modules: Arc<Mutex<Modules>>, } impl MockLoader { fn new() -> Self { let modules = Modules::new(); - let (isolate, _dispatch_count) = setup(Mode::Async); + let (isolate, _dispatch_count) = setup(); Self { loads: Arc::new(Mutex::new(Vec::new())), isolate: Arc::new(Mutex::new(isolate)), diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index cc4970d16..430493f33 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -10,7 +10,7 @@ use deno_core::js_check; pub use deno_core::v8_set_flags; use deno_core::CoreOp; use deno_core::ErrBox; -use deno_core::Isolate; +use deno_core::EsIsolate; use deno_core::ModuleSpecifier; use deno_core::PinnedBuf; use deno_core::StartupData; @@ -64,13 +64,13 @@ where } pub struct TSIsolate { - isolate: Box<Isolate>, + isolate: Box<EsIsolate>, state: Arc<Mutex<TSState>>, } impl TSIsolate { fn new(bundle: bool) -> TSIsolate { - let mut isolate = Isolate::new(StartupData::None, false); + let mut isolate = EsIsolate::new(StartupData::None, false); js_check(isolate.execute("assets/typescript.js", TYPESCRIPT_CODE)); js_check(isolate.execute("compiler_main.js", COMPILER_CODE)); @@ -180,7 +180,7 @@ pub fn mksnapshot_bundle( bundle: &Path, state: Arc<Mutex<TSState>>, ) -> Result<(), ErrBox> { - let mut runtime_isolate = Isolate::new(StartupData::None, true); + let runtime_isolate = &mut EsIsolate::new(StartupData::None, true); let source_code_vec = std::fs::read(bundle)?; let source_code = std::str::from_utf8(&source_code_vec)?; @@ -203,7 +203,7 @@ pub fn mksnapshot_bundle_ts( bundle: &Path, state: Arc<Mutex<TSState>>, ) -> Result<(), ErrBox> { - let mut runtime_isolate = Isolate::new(StartupData::None, true); + let runtime_isolate = &mut EsIsolate::new(StartupData::None, true); let source_code_vec = std::fs::read(bundle)?; let source_code = std::str::from_utf8(&source_code_vec)?; @@ -222,7 +222,7 @@ pub fn mksnapshot_bundle_ts( } fn write_snapshot( - mut runtime_isolate: Box<Isolate>, + runtime_isolate: &mut EsIsolate, bundle: &Path, ) -> Result<(), ErrBox> { println!("creating snapshot..."); |