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 /core/isolate.rs | |
parent | 8bf383710fc32efdbf2996abf5130bbd9aecacd1 (diff) |
core: factor out EsIsolate from Isolate (#3613)
Diffstat (limited to 'core/isolate.rs')
-rw-r--r-- | core/isolate.rs | 1013 |
1 files changed, 39 insertions, 974 deletions
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>(); |