diff --git a/src/binding.cc b/src/binding.cc index 8e5c4a14..a5c9b2f6 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1,6 +1,7 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. #include #include +#include #include #include "support.h" @@ -2690,6 +2691,26 @@ const v8::UnboundModuleScript* v8__Module__GetUnboundModuleScript( return local_to_ptr(ptr_to_local(&self)->GetUnboundModuleScript()); } +struct StalledTopLevelAwaitMessage { + const v8::Module* module; + const v8::Message* message; +}; + + +size_t v8__Module__GetStalledTopLevelAwaitMessage( + const v8::Module& self, v8::Isolate* isolate, + StalledTopLevelAwaitMessage* out_vec, size_t out_len) { + auto messages = ptr_to_local(&self)->GetStalledTopLevelAwaitMessage(isolate); + auto len = std::min(messages.size(), out_len); + for (size_t i = 0; i < len; i += 1) { + StalledTopLevelAwaitMessage stalled_message; + stalled_message.module = local_to_ptr(std::get<0>(messages[i])); + stalled_message.message = local_to_ptr(std::get<1>(messages[i])); + out_vec[i] = stalled_message; + } + return len; +} + const v8::String* v8__ModuleRequest__GetSpecifier( const v8::ModuleRequest& self) { return local_to_ptr(self.GetSpecifier()); diff --git a/src/module.rs b/src/module.rs index 420d9b84..e8c8c4e3 100644 --- a/src/module.rs +++ b/src/module.rs @@ -13,6 +13,7 @@ use crate::FixedArray; use crate::HandleScope; use crate::Isolate; use crate::Local; +use crate::Message; use crate::Module; use crate::ModuleRequest; use crate::String; @@ -195,6 +196,18 @@ extern "C" { fn v8__ModuleRequest__GetImportAssertions( this: *const ModuleRequest, ) -> *const FixedArray; + fn v8__Module__GetStalledTopLevelAwaitMessage( + this: *const Module, + isolate: *const Isolate, + out_vec: *mut StalledTopLevelAwaitMessage, + vec_len: usize, + ) -> usize; +} + +#[repr(C)] +pub struct StalledTopLevelAwaitMessage { + pub module: *const Module, + pub message: *const Message, } /// A location in JavaScript source. @@ -413,6 +426,44 @@ impl Module { .unwrap() } } + + /// Search the modules requested directly or indirectly by the module for + /// any top-level await that has not yet resolved. If there is any, the + /// returned vector contains a tuple of the unresolved module and a message + /// with the pending top-level await. + /// An embedder may call this before exiting to improve error messages. + pub fn get_stalled_top_level_await_message( + &self, + scope: &mut HandleScope, + ) -> Vec<(Local, Local)> { + let mut out_vec: Vec = Vec::with_capacity(16); + for _i in 0..16 { + out_vec.push(StalledTopLevelAwaitMessage { + module: std::ptr::null(), + message: std::ptr::null(), + }); + } + + let returned_len = unsafe { + v8__Module__GetStalledTopLevelAwaitMessage( + &*self, + scope.get_isolate_ptr(), + out_vec.as_mut_ptr(), + out_vec.len(), + ) + }; + + let mut ret_vec = Vec::with_capacity(returned_len); + for item in out_vec.iter().take(returned_len) { + unsafe { + ret_vec.push(( + Local::from_raw(item.module).unwrap(), + Local::from_raw(item.message).unwrap(), + )); + } + } + ret_vec + } } impl ModuleRequest { diff --git a/tests/test_api.rs b/tests/test_api.rs index 755b3640..e199094a 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3506,6 +3506,66 @@ fn module_evaluation() { } } +#[test] +fn module_stalled_top_level_await() { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + { + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let source_text = + v8::String::new(scope, "await new Promise((_resolve, _reject) => {});") + .unwrap(); + let origin = mock_script_origin(scope, "foo.js"); + let source = v8::script_compiler::Source::new(source_text, Some(&origin)); + + let module = v8::script_compiler::compile_module(scope, source).unwrap(); + assert!(module.script_id().is_some()); + assert!(module.is_source_text_module()); + assert!(!module.is_synthetic_module()); + assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); + module.hash(&mut DefaultHasher::new()); // Should not crash. + + let result = module + .instantiate_module(scope, compile_specifier_as_module_resolve_callback); + assert!(result.unwrap()); + assert_eq!(v8::ModuleStatus::Instantiated, module.get_status()); + + let result = module.evaluate(scope); + assert!(result.is_some()); + assert_eq!(v8::ModuleStatus::Evaluated, module.get_status()); + + let promise: v8::Local = result.unwrap().try_into().unwrap(); + scope.perform_microtask_checkpoint(); + assert_eq!(promise.state(), v8::PromiseState::Pending); + let stalled = module.get_stalled_top_level_await_message(scope); + assert_eq!(stalled.len(), 1); + let (_module, message) = stalled[0]; + let message_str = message.get(scope); + assert_eq!( + message_str.to_rust_string_lossy(scope), + "Top-level await promise never resolved" + ); + assert_eq!(Some(1), message.get_line_number(scope)); + assert_eq!( + message + .get_script_resource_name(scope) + .unwrap() + .to_rust_string_lossy(scope), + "foo.js" + ); + assert_eq!( + message + .get_source_line(scope) + .unwrap() + .to_rust_string_lossy(scope), + "await new Promise((_resolve, _reject) => {});" + ); + } +} + #[test] fn import_assertions() { let _setup_guard = setup();