diff --git a/src/binding.cc b/src/binding.cc index 6e8dbdb3..a1c005bb 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -73,19 +73,24 @@ enum InternalSlots { (isolate->GetNumberOfDataSlots() - 1 - slot) // This is an extern C calling convention compatible version of -// v8::HostImportModuleDynamicallyCallback -typedef v8::Promise* (*v8__HostImportModuleDynamicallyCallback)( +// v8::HostImportModuleDynamicallyWithImportAssertionsCallback +typedef v8::Promise* ( + *v8__HostImportModuleDynamicallyWithImportAssertionsCallback)( v8::Local context, v8::Local referrer, - v8::Local specifier); + v8::Local specifier, + v8::Local import_assertions); -v8::MaybeLocal HostImportModuleDynamicallyCallback( +v8::MaybeLocal +HostImportModuleDynamicallyWithImportAssertionsCallback( v8::Local context, v8::Local referrer, - v8::Local specifier) { + v8::Local specifier, + v8::Local import_assertions) { auto* isolate = context->GetIsolate(); void* d = isolate->GetData(SLOT_INTERNAL(isolate, kSlotDynamicImport)); - auto* callback = reinterpret_cast(d); + auto* callback = reinterpret_cast< + v8__HostImportModuleDynamicallyWithImportAssertionsCallback>(d); assert(callback != nullptr); - auto* promise_ptr = callback(context, referrer, specifier); + auto* promise_ptr = callback(context, referrer, specifier, import_assertions); if (promise_ptr == nullptr) { return v8::MaybeLocal(); } else { @@ -221,11 +226,12 @@ void v8__Isolate__SetHostInitializeImportMetaObjectCallback( } void v8__Isolate__SetHostImportModuleDynamicallyCallback( - v8::Isolate* isolate, v8__HostImportModuleDynamicallyCallback callback) { + v8::Isolate* isolate, + v8__HostImportModuleDynamicallyWithImportAssertionsCallback callback) { isolate->SetData(SLOT_INTERNAL(isolate, kSlotDynamicImport), reinterpret_cast(callback)); isolate->SetHostImportModuleDynamicallyCallback( - HostImportModuleDynamicallyCallback); + HostImportModuleDynamicallyWithImportAssertionsCallback); } bool v8__Isolate__AddMessageListener(v8::Isolate* isolate, @@ -614,6 +620,13 @@ const v8::Boolean* v8__Boolean__New(v8::Isolate* isolate, bool value) { return local_to_ptr(v8::Boolean::New(isolate, value)); } +int v8__FixedArray__Length(const v8::FixedArray& self) { return self.Length(); } + +const v8::Data* v8__FixedArray__Get(const v8::FixedArray& self, + const v8::Context& context, int index) { + return local_to_ptr(ptr_to_local(&self)->Get(ptr_to_local(&context), index)); +} + const v8::PrimitiveArray* v8__PrimitiveArray__New(v8::Isolate* isolate, int length) { return local_to_ptr(v8::PrimitiveArray::New(isolate, length)); @@ -1560,18 +1573,15 @@ const v8::Value* v8__Script__Run(const v8::Script& script, } void v8__ScriptOrigin__CONSTRUCT( - v8::Isolate* isolate, - uninit_t* buf, const v8::Value& resource_name, - int resource_line_offset, int resource_column_offset, - bool resource_is_shared_cross_origin, int script_id, - const v8::Value& source_map_url, - bool resource_is_opaque, bool is_wasm, bool is_module) { + v8::Isolate* isolate, uninit_t* buf, + const v8::Value& resource_name, int resource_line_offset, + int resource_column_offset, bool resource_is_shared_cross_origin, + int script_id, const v8::Value& source_map_url, bool resource_is_opaque, + bool is_wasm, bool is_module) { construct_in_place( - buf, isolate, ptr_to_local(&resource_name), - resource_line_offset, resource_column_offset, - resource_is_shared_cross_origin, script_id, - ptr_to_local(&source_map_url), - resource_is_opaque, is_wasm, is_module); + buf, isolate, ptr_to_local(&resource_name), resource_line_offset, + resource_column_offset, resource_is_shared_cross_origin, script_id, + ptr_to_local(&source_map_url), resource_is_opaque, is_wasm, is_module); } const v8::Value* v8__ScriptOrModule__GetResourceName( @@ -2055,7 +2065,7 @@ int v8__Module__ScriptId(const v8::Module& self) { MaybeBool v8__Module__InstantiateModule(const v8::Module& self, const v8::Context& context, - v8::Module::ResolveCallback cb) { + v8::Module::ResolveModuleCallback cb) { return maybe_to_maybe_bool( ptr_to_local(&self)->InstantiateModule(ptr_to_local(&context), cb)); } diff --git a/src/data.rs b/src/data.rs index 6a83ec95..b9e48e42 100644 --- a/src/data.rs +++ b/src/data.rs @@ -356,6 +356,17 @@ impl_eq! { for Module } impl_partial_eq! { Data for Module use identity } impl_partial_eq! { Module for Module use identity } +/// A fixed-sized array with elements of type Data. +#[repr(C)] +#[derive(Debug)] +pub struct FixedArray(Opaque); + +impl_deref! { Data for FixedArray } +impl_eq! { for FixedArray } +impl_hash! { for FixedArray } +impl_partial_eq! { Data for FixedArray use identity } +impl_partial_eq! { FixedArray for FixedArray use identity } + /// An array to hold Primitive values. This is used by the embedder to /// pass host defined options to the ScriptOptions during compilation. /// diff --git a/src/fixed_array.rs b/src/fixed_array.rs new file mode 100644 index 00000000..6dedde90 --- /dev/null +++ b/src/fixed_array.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2020 the Deno authors. All rights reserved. MIT license. +use crate::support::int; +use crate::Context; +use crate::Data; +use crate::FixedArray; +use crate::HandleScope; +use crate::Local; + +extern "C" { + fn v8__FixedArray__Length(this: *const FixedArray) -> int; + + fn v8__FixedArray__Get( + this: *const FixedArray, + context: *const Context, + index: int, + ) -> *const Data; +} + +impl FixedArray { + pub fn length(&self) -> usize { + unsafe { v8__FixedArray__Length(self) as usize } + } + + pub fn get<'s>( + &self, + scope: &mut HandleScope<'s>, + index: usize, + ) -> Option> { + if index >= self.length() { + return None; + } + + unsafe { + scope.cast_local(|sd| { + v8__FixedArray__Get(self, &*sd.get_current_context(), index as int) + }) + } + } +} diff --git a/src/isolate.rs b/src/isolate.rs index 03914f98..0a36b5ff 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -11,6 +11,7 @@ use crate::support::UnitType; use crate::wasm::trampoline; use crate::wasm::WasmStreaming; use crate::Context; +use crate::FixedArray; use crate::Function; use crate::Local; use crate::Message; @@ -90,7 +91,7 @@ pub type PromiseRejectCallback = extern "C" fn(PromiseRejectMessage); pub type HostInitializeImportMetaObjectCallback = extern "C" fn(Local, Local, Local); -/// HostImportModuleDynamicallyCallback is called when we require the +/// HostImportModuleDynamicallyWithImportAssertionsCallback is called when we require the /// embedder to load a module. This is used as part of the dynamic /// import syntax. /// @@ -108,11 +109,13 @@ pub type HostInitializeImportMetaObjectCallback = /// this promise with the exception. If the promise creation itself /// fails (e.g. due to stack overflow), the embedder must propagate /// that exception by returning an empty MaybeLocal. -pub type HostImportModuleDynamicallyCallback = extern "C" fn( - Local, - Local, - Local, -) -> *mut Promise; +pub type HostImportModuleDynamicallyWithImportAssertionsCallback = + extern "C" fn( + Local, + Local, + Local, + Local, + ) -> *mut Promise; pub type InterruptCallback = extern "C" fn(isolate: &mut Isolate, data: *mut c_void); @@ -180,7 +183,7 @@ extern "C" { ); fn v8__Isolate__SetHostImportModuleDynamicallyCallback( isolate: *mut Isolate, - callback: HostImportModuleDynamicallyCallback, + callback: HostImportModuleDynamicallyWithImportAssertionsCallback, ); fn v8__Isolate__RequestInterrupt( isolate: *const Isolate, @@ -507,7 +510,7 @@ impl Isolate { /// import() language feature to load modules. pub fn set_host_import_module_dynamically_callback( &mut self, - callback: HostImportModuleDynamicallyCallback, + callback: HostImportModuleDynamicallyWithImportAssertionsCallback, ) { unsafe { v8__Isolate__SetHostImportModuleDynamicallyCallback(self, callback) diff --git a/src/isolate_create_params.rs b/src/isolate_create_params.rs index 019c398f..e9fcbbfe 100644 --- a/src/isolate_create_params.rs +++ b/src/isolate_create_params.rs @@ -192,6 +192,7 @@ pub(crate) mod raw { pub embedder_wrapper_type_index: int, pub embedder_wrapper_object_index: int, pub cpp_heap_params: SharedPtr, + // NOTE(bartlomieju): this field is deprecated in V8 API. // This is an std::vector. It's usually no bigger // than three or four words but let's take a generous upper bound. pub supported_import_assertions: [usize; 8], diff --git a/src/lib.rs b/src/lib.rs index cde42290..51bca49e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ mod date; mod exception; mod external; mod external_references; +mod fixed_array; mod function; mod handle; mod isolate; @@ -92,7 +93,7 @@ pub use handle::Global; pub use handle::Handle; pub use handle::Local; pub use isolate::HeapStatistics; -pub use isolate::HostImportModuleDynamicallyCallback; +pub use isolate::HostImportModuleDynamicallyWithImportAssertionsCallback; pub use isolate::HostInitializeImportMetaObjectCallback; pub use isolate::Isolate; pub use isolate::IsolateHandle; diff --git a/src/module.rs b/src/module.rs index 6f080f0c..c8ff1d7a 100644 --- a/src/module.rs +++ b/src/module.rs @@ -11,6 +11,7 @@ use crate::support::MaybeBool; use crate::support::ToCFn; use crate::support::UnitType; use crate::Context; +use crate::FixedArray; use crate::HandleScope; use crate::Isolate; use crate::Local; @@ -19,7 +20,7 @@ use crate::String; use crate::Value; /// Called during Module::instantiate_module. Provided with arguments: -/// (context, specifier, referrer). Return None on error. +/// (context, specifier, import_assertions, referrer). Return None on error. /// /// Note: this callback has an unusual signature due to ABI incompatibilities /// between Rust and C++. However end users can implement the callback as @@ -29,6 +30,7 @@ use crate::Value; /// fn my_resolve_callback<'a>( /// context: v8::Local<'a, v8::Context>, /// specifier: v8::Local<'a, v8::String>, +/// import_assertions: v8::Local<'a, v8::FixedArray>, /// referrer: v8::Local<'a, v8::Module>, /// ) -> Option> { /// // ... @@ -38,34 +40,37 @@ use crate::Value; // System V AMD64 ABI: Local returned in a register. #[cfg(not(target_os = "windows"))] -pub type ResolveCallback<'a> = extern "C" fn( +pub type ResolveModuleCallback<'a> = extern "C" fn( Local<'a, Context>, Local<'a, String>, + Local<'a, FixedArray>, Local<'a, Module>, ) -> *const Module; // Windows x64 ABI: Local returned on the stack. #[cfg(target_os = "windows")] -pub type ResolveCallback<'a> = extern "C" fn( +pub type ResolveModuleCallback<'a> = extern "C" fn( *mut *const Module, Local<'a, Context>, Local<'a, String>, + Local<'a, FixedArray>, Local<'a, Module>, ) -> *mut *const Module; -impl<'a, F> MapFnFrom for ResolveCallback<'a> +impl<'a, F> MapFnFrom for ResolveModuleCallback<'a> where F: UnitType + Fn( Local<'a, Context>, Local<'a, String>, + Local<'a, FixedArray>, Local<'a, Module>, ) -> Option>, { #[cfg(not(target_os = "windows"))] fn mapping() -> Self { - let f = |context, specifier, referrer| { - (F::get())(context, specifier, referrer) + let f = |context, specifier, import_assertions, referrer| { + (F::get())(context, specifier, import_assertions, referrer) .map(|r| -> *const Module { &*r }) .unwrap_or(null()) }; @@ -74,8 +79,8 @@ where #[cfg(target_os = "windows")] fn mapping() -> Self { - let f = |ret_ptr, context, specifier, referrer| { - let r = (F::get())(context, specifier, referrer) + let f = |ret_ptr, context, specifier, import_assertions, referrer| { + let r = (F::get())(context, specifier, import_assertions, referrer) .map(|r| -> *const Module { &*r }) .unwrap_or(null()); unsafe { std::ptr::write(ret_ptr, r) }; // Write result to stack. @@ -144,7 +149,7 @@ extern "C" { fn v8__Module__InstantiateModule( this: *const Module, context: *const Context, - cb: ResolveCallback, + cb: ResolveModuleCallback, ) -> MaybeBool; fn v8__Module__Evaluate( this: *const Module, @@ -283,7 +288,7 @@ impl Module { pub fn instantiate_module<'a>( &self, scope: &mut HandleScope, - callback: impl MapFnTo>, + callback: impl MapFnTo>, ) -> Option { unsafe { v8__Module__InstantiateModule( diff --git a/tests/test_api.rs b/tests/test_api.rs index 3092081b..9d18ac66 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -34,7 +34,7 @@ fn setup() -> SetupGuard { let mut g = INIT_LOCK.lock().unwrap(); *g += 1; if *g == 1 { - v8::V8::set_flags_from_string("--expose_gc"); + v8::V8::set_flags_from_string("--expose_gc --harmony-import-assertions"); v8::V8::initialize_platform(v8::new_default_platform().unwrap()); v8::V8::initialize(); } @@ -913,6 +913,7 @@ fn add_message_listener() { fn unexpected_module_resolve_callback<'a>( _context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, + _import_assertions: v8::Local<'a, v8::FixedArray>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { unreachable!() @@ -1980,6 +1981,7 @@ fn module_instantiation_failures1() { fn resolve_callback<'a>( context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, + _import_assertions: v8::Local<'a, v8::FixedArray>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { let scope = &mut unsafe { v8::CallbackScope::new(context) }; @@ -2003,6 +2005,7 @@ fn module_instantiation_failures1() { fn compile_specifier_as_module_resolve_callback<'a>( context: v8::Local<'a, v8::Context>, specifier: v8::Local<'a, v8::String>, + _import_assertions: v8::Local<'a, v8::FixedArray>, _referrer: v8::Local<'a, v8::Module>, ) -> Option> { let scope = &mut unsafe { v8::CallbackScope::new(context) }; @@ -2053,6 +2056,85 @@ fn module_evaluation() { } } +#[test] +fn import_assertions() { + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + + fn module_resolve_callback<'a>( + context: v8::Local<'a, v8::Context>, + _specifier: v8::Local<'a, v8::String>, + import_assertions: v8::Local<'a, v8::FixedArray>, + _referrer: v8::Local<'a, v8::Module>, + ) -> Option> { + let scope = &mut unsafe { v8::CallbackScope::new(context) }; + + // "type" keyword, value and source offset of assertion + assert_eq!(import_assertions.length(), 3); + let assert1 = import_assertions.get(scope, 0).unwrap(); + let assert1_val = v8::Local::::try_from(assert1).unwrap(); + assert_eq!(assert1_val.to_rust_string_lossy(scope), "type"); + let assert2 = import_assertions.get(scope, 1).unwrap(); + let assert2_val = v8::Local::::try_from(assert2).unwrap(); + assert_eq!(assert2_val.to_rust_string_lossy(scope), "json"); + let assert3 = import_assertions.get(scope, 2).unwrap(); + let assert3_val = v8::Local::::try_from(assert3).unwrap(); + assert_eq!(assert3_val.to_rust_string_lossy(scope), "27"); + + let origin = mock_script_origin(scope, "module.js"); + let src = v8::String::new(scope, "export const a = 'a';").unwrap(); + let source = v8::script_compiler::Source::new(src, &origin); + let module = v8::script_compiler::compile_module(scope, source).unwrap(); + Some(module) + } + + extern "C" fn dynamic_import_cb( + context: v8::Local, + _referrer: v8::Local, + _specifier: v8::Local, + import_assertions: v8::Local, + ) -> *mut v8::Promise { + let scope = &mut unsafe { v8::CallbackScope::new(context) }; + let scope = &mut v8::HandleScope::new(scope); + // "type" keyword, value + assert_eq!(import_assertions.length(), 2); + let assert1 = import_assertions.get(scope, 0).unwrap(); + let assert1_val = v8::Local::::try_from(assert1).unwrap(); + assert_eq!(assert1_val.to_rust_string_lossy(scope), "type"); + let assert2 = import_assertions.get(scope, 1).unwrap(); + let assert2_val = v8::Local::::try_from(assert2).unwrap(); + assert_eq!(assert2_val.to_rust_string_lossy(scope), "json"); + std::ptr::null_mut() + } + isolate.set_host_import_module_dynamically_callback(dynamic_import_cb); + + { + 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, + "import 'foo.json' assert { type: \"json\" };\n\ + import('foo.json', { assert: { type: 'json' } });", + ) + .unwrap(); + let origin = mock_script_origin(scope, "foo.js"); + let source = v8::script_compiler::Source::new(source_text, &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, module_resolve_callback); + assert!(result.unwrap()); + assert_eq!(v8::ModuleStatus::Instantiated, module.get_status()); + } +} + #[test] fn primitive_array() { let _setup_guard = setup(); @@ -2550,6 +2632,7 @@ fn dynamic_import() { context: v8::Local, _referrer: v8::Local, specifier: v8::Local, + _import_assertions: v8::Local, ) -> *mut v8::Promise { let scope = &mut unsafe { v8::CallbackScope::new(context) }; let scope = &mut v8::HandleScope::new(scope);