diff --git a/src/binding.cc b/src/binding.cc index 2179f054..eb3926d7 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -153,6 +153,11 @@ uint32_t v8__Isolate__GetNumberOfDataSlots(v8::Isolate* isolate) { return SLOT_NUM_EXTERNAL(isolate); } +const v8::Data* v8__Isolate__GetDataFromSnapshotOnce(v8::Isolate* isolate, + size_t index) { + return maybe_local_to_ptr(isolate->GetDataFromSnapshotOnce(index)); +} + v8::MicrotasksPolicy v8__Isolate__GetMicrotasksPolicy( const v8::Isolate* isolate) { return isolate->GetMicrotasksPolicy(); @@ -296,11 +301,7 @@ const v8::Module* v8__ScriptCompiler__CompileModule( v8::ScriptCompiler::NoCacheReason no_cache_reason) { v8::MaybeLocal maybe_local = v8::ScriptCompiler::CompileModule( isolate, source, options, no_cache_reason); - if (maybe_local.IsEmpty()) { - return nullptr; - } else { - return local_to_ptr(maybe_local.ToLocalChecked()); - } + return maybe_local_to_ptr(maybe_local); } bool v8__Data__EQ(const v8::Data& self, const v8::Data& other) { @@ -1012,6 +1013,12 @@ const v8::Object* v8__Context__Global(const v8::Context& self) { return local_to_ptr(ptr_to_local(&self)->Global()); } +const v8::Data* v8__Context__GetDataFromSnapshotOnce(v8::Context& self, + size_t index) { + return maybe_local_to_ptr( + ptr_to_local(&self)->GetDataFromSnapshotOnce(index)); +} + const v8::String* v8__Message__Get(const v8::Message& self) { return local_to_ptr(self.Get()); } @@ -1519,6 +1526,17 @@ void v8__SnapshotCreator__SetDefaultContext(v8::SnapshotCreator* self, self->SetDefaultContext(ptr_to_local(&context), SerializeInternalFields); } +size_t v8__SnapshotCreator__AddData_to_isolate(v8::SnapshotCreator* self, + const v8::Data& data) { + return self->AddData(ptr_to_local(&data)); +} + +size_t v8__SnapshotCreator__AddData_to_context(v8::SnapshotCreator* self, + const v8::Context& context, + const v8::Data& data) { + return self->AddData(ptr_to_local(&context), ptr_to_local(&data)); +} + v8::StartupData v8__SnapshotCreator__CreateBlob( v8::SnapshotCreator* self, v8::SnapshotCreator::FunctionCodeHandling function_code_handling) { diff --git a/src/data.rs b/src/data.rs index 18ebe354..556eacc1 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,5 +1,6 @@ // Copyright 2019-2020 the Deno authors. All rights reserved. MIT license. +use std::any::type_name; use std::convert::From; use std::convert::TryFrom; use std::error::Error; @@ -89,11 +90,11 @@ macro_rules! impl_from { macro_rules! impl_try_from { { $source:ident for $target:ident if $value:pat => $check:expr } => { impl<'s> TryFrom> for Local<'s, $target> { - type Error = TryFromTypeError; + type Error = DataError; fn try_from(l: Local<'s, $source>) -> Result { match l { $value if $check => Ok(unsafe { transmute(l) }), - _ => Err(TryFromTypeError::new(stringify!($target))) + _ => Err(DataError::bad_type::<$target, $source>()) } } } @@ -152,23 +153,45 @@ macro_rules! impl_partial_eq { } #[derive(Clone, Copy, Debug)] -pub struct TryFromTypeError { - expected_type: &'static str, +pub enum DataError { + BadType { + actual: &'static str, + expected: &'static str, + }, + NoData { + expected: &'static str, + }, } -impl TryFromTypeError { - fn new(expected_type: &'static str) -> Self { - Self { expected_type } +impl DataError { + pub(crate) fn bad_type() -> Self { + Self::BadType { + expected: type_name::(), + actual: type_name::(), + } + } + + pub(crate) fn no_data() -> Self { + Self::NoData { + expected: type_name::(), + } } } -impl Display for TryFromTypeError { +impl Display for DataError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{} expected", self.expected_type) + match self { + Self::BadType { expected, actual } => { + write!(f, "expected type `{}`, got `{}`", expected, actual) + } + Self::NoData { expected } => { + write!(f, "expected `Some({})`, found `None`", expected) + } + } } } -impl Error for TryFromTypeError {} +impl Error for DataError {} /// The superclass of objects that can reside on V8's heap. #[repr(C)] @@ -587,6 +610,11 @@ impl_partial_eq! { UnboundScript for UnboundScript use identity } pub struct Value(Opaque); impl_deref! { Data for Value } +// TODO: Also implement `TryFrom` for all subtypes of `Value`, +// so a `Local` can be directly cast to any `Local` with a JavaScript +// value type in it. We need this to make certain APIs work, such as +// `scope.get_context_data_from_snapshot_once::()` and +// `scope.get_isolate_data_from_snapshot_once::()`. impl_try_from! { Data for Value if d => d.is_value() } impl_from! { External for Value } impl_from! { Object for Value } diff --git a/src/function.rs b/src/function.rs index 61d4066b..b334fbf5 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,12 +1,12 @@ use std::convert::TryFrom; use std::marker::PhantomData; +use crate::scope::CallbackScope; use crate::support::MapFnFrom; use crate::support::MapFnTo; use crate::support::ToCFn; use crate::support::UnitType; use crate::support::{int, Opaque}; -use crate::CallbackScope; use crate::Context; use crate::Function; use crate::HandleScope; diff --git a/src/scope.rs b/src/scope.rs index cb09bc61..53ffc303 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -81,6 +81,8 @@ use std::alloc::alloc; use std::alloc::Layout; use std::any::type_name; use std::cell::Cell; +use std::convert::TryInto; + use std::marker::PhantomData; use std::mem::MaybeUninit; use std::num::NonZeroUsize; @@ -93,6 +95,7 @@ use crate::function::FunctionCallbackInfo; use crate::function::PropertyCallbackInfo; use crate::Context; use crate::Data; +use crate::DataError; use crate::Handle; use crate::Isolate; use crate::Local; @@ -224,6 +227,61 @@ impl<'s> HandleScope<'s, ()> { } } +impl<'s> HandleScope<'s> { + /// Return data that was previously attached to the isolate snapshot via + /// SnapshotCreator, and removes the reference to it. If called again with + /// same `index` argument, this function returns `DataError::NoData`. + /// + /// The value that was stored in the snapshot must either match or be + /// convertible to type parameter `T`, otherwise `DataError::BadType` is + /// returned. + pub fn get_isolate_data_from_snapshot_once( + &mut self, + index: usize, + ) -> Result, DataError> + where + T: 'static, + for<'l> Local<'l, Data>: TryInto, Error = DataError>, + { + unsafe { + self + .cast_local(|sd| { + raw::v8__Isolate__GetDataFromSnapshotOnce(sd.get_isolate_ptr(), index) + }) + .ok_or_else(DataError::no_data::) + .and_then(|data| data.try_into()) + } + } + + /// Return data that was previously attached to the context snapshot via + /// SnapshotCreator, and removes the reference to it. If called again with + /// same `index` argument, this function returns `DataError::NoData`. + /// + /// The value that was stored in the snapshot must either match or be + /// convertible to type parameter `T`, otherwise `DataError::BadType` is + /// returned. + pub fn get_context_data_from_snapshot_once( + &mut self, + index: usize, + ) -> Result, DataError> + where + T: 'static, + for<'l> Local<'l, Data>: TryInto, Error = DataError>, + { + unsafe { + self + .cast_local(|sd| { + raw::v8__Context__GetDataFromSnapshotOnce( + sd.get_current_context(), + index, + ) + }) + .ok_or_else(DataError::no_data::) + .and_then(|data| data.try_into()) + } + } +} + /// A HandleScope which first allocates a handle in the current scope /// which will be later filled with the escape value. // TODO(piscisaureus): type parameter `C` is not very useful in practice; being @@ -608,8 +666,8 @@ impl ScopeCast for T { /// /// For example: a `ContextScope` created inside `HandleScope<'a, ()>` does not /// produce a `ContextScope`, but rather a `HandleScope<'a, Context>`, which -/// describes a scope that is both a `HandleScope` _and_ a `ContextScope`. -/// +/// describes a scope that is both a `HandleScope` _and_ a `ContextScope`. +/// /// The Traits in the (private) `param` module define which types can be passed /// as a parameter to the `«Some»Scope::new()` constructor, and what the /// actual, merged scope type will be that `new()` returns for a specific @@ -1560,6 +1618,10 @@ mod raw { isolate: *mut Isolate, exception: *const Value, ) -> *const Value; + pub(super) fn v8__Isolate__GetDataFromSnapshotOnce( + this: *mut Isolate, + index: usize, + ) -> *const Data; pub(super) fn v8__Context__EQ( this: *const Context, @@ -1569,6 +1631,10 @@ mod raw { pub(super) fn v8__Context__Exit(this: *const Context); pub(super) fn v8__Context__GetIsolate(this: *const Context) -> *mut Isolate; + pub(super) fn v8__Context__GetDataFromSnapshotOnce( + this: *const Context, + index: usize, + ) -> *const Data; pub(super) fn v8__HandleScope__CONSTRUCT( buf: *mut MaybeUninit, diff --git a/src/snapshot.rs b/src/snapshot.rs index 8aeb48c8..d2c4c50e 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -4,6 +4,7 @@ use crate::support::char; use crate::support::int; use crate::support::intptr_t; use crate::Context; +use crate::Data; use crate::Isolate; use crate::Local; use crate::OwnedIsolate; @@ -30,6 +31,15 @@ extern "C" { this: *mut SnapshotCreator, context: *const Context, ); + fn v8__SnapshotCreator__AddData_to_isolate( + this: *mut SnapshotCreator, + data: *const Data, + ) -> usize; + fn v8__SnapshotCreator__AddData_to_context( + this: *mut SnapshotCreator, + context: *const Context, + data: *const Data, + ) -> usize; fn v8__StartupData__DESTRUCT(this: *mut StartupData); } @@ -112,6 +122,34 @@ impl SnapshotCreator { unsafe { v8__SnapshotCreator__SetDefaultContext(self, &*context) }; } + /// Attach arbitrary `v8::Data` to the isolate snapshot, which can be + /// retrieved via `HandleScope::get_context_data_from_snapshot_once()` after + /// deserialization. This data does not survive when a new snapshot is created + /// from an existing snapshot. + pub fn add_isolate_data(&mut self, data: Local) -> usize + where + for<'l> Local<'l, T>: Into>, + { + unsafe { v8__SnapshotCreator__AddData_to_isolate(self, &*data.into()) } + } + + /// Attach arbitrary `v8::Data` to the context snapshot, which can be + /// retrieved via `HandleScope::get_context_data_from_snapshot_once()` after + /// deserialization. This data does not survive when a new snapshot is + /// created from an existing snapshot. + pub fn add_context_data( + &mut self, + context: Local, + data: Local, + ) -> usize + where + for<'l> Local<'l, T>: Into>, + { + unsafe { + v8__SnapshotCreator__AddData_to_context(self, &*context, &*data.into()) + } + } + /// Creates a snapshot data blob. /// This must not be called from within a handle scope. pub fn create_blob( diff --git a/tests/test_api.rs b/tests/test_api.rs index d9089173..23db2fe3 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate lazy_static; +use std::any::type_name; use std::convert::{Into, TryFrom, TryInto}; use std::ffi::c_void; use std::ptr::NonNull; @@ -2101,6 +2102,9 @@ fn snapshot_creator() { let _setup_guard = setup(); // First we create the snapshot, there is a single global variable 'a' set to // the value 3. + let isolate_data_index; + let context_data_index; + let context_data_index_2; let startup_data = { let mut snapshot_creator = v8::SnapshotCreator::new(None); // TODO(ry) this shouldn't be necessary. workaround unfinished business in @@ -2119,6 +2123,13 @@ fn snapshot_creator() { script.run(scope).unwrap(); snapshot_creator.set_default_context(context); + + isolate_data_index = + snapshot_creator.add_isolate_data(v8::Number::new(scope, 1.0)); + context_data_index = + snapshot_creator.add_context_data(context, v8::Number::new(scope, 2.0)); + context_data_index_2 = + snapshot_creator.add_context_data(context, v8::Number::new(scope, 3.0)); } std::mem::forget(isolate); // TODO(ry) this shouldn't be necessary. snapshot_creator @@ -2140,6 +2151,26 @@ fn snapshot_creator() { let result = script.run(scope).unwrap(); let true_val = v8::Boolean::new(scope, true).into(); assert!(result.same_value(true_val)); + + let isolate_data = scope + .get_isolate_data_from_snapshot_once::(isolate_data_index); + assert!(isolate_data.unwrap() == v8::Number::new(scope, 1.0)); + let no_data_err = scope + .get_isolate_data_from_snapshot_once::(isolate_data_index); + assert!(matches!(no_data_err, Err(v8::DataError::NoData { .. }))); + + let context_data = scope + .get_context_data_from_snapshot_once::(context_data_index); + assert!(context_data.unwrap() == v8::Number::new(scope, 2.0)); + let no_data_err = scope + .get_context_data_from_snapshot_once::(context_data_index); + assert!(matches!(no_data_err, Err(v8::DataError::NoData { .. }))); + + let bad_type_err = scope + .get_context_data_from_snapshot_once::( + context_data_index_2, + ); + assert!(matches!(bad_type_err, Err(v8::DataError::BadType { .. }))); } } } @@ -2782,20 +2813,16 @@ fn try_from_value() { { let value: v8::Local = v8::undefined(scope).into(); let _primitive = v8::Local::::try_from(value).unwrap(); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "Object expected" - ); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "Int32 expected" - ); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); } { @@ -2803,20 +2830,16 @@ fn try_from_value() { let primitive = v8::Local::::try_from(value).unwrap(); let _boolean = v8::Local::::try_from(value).unwrap(); let _boolean = v8::Local::::try_from(primitive).unwrap(); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "String expected" - ); - assert_eq!( - v8::Local::::try_from(primitive) - .err() - .unwrap() - .to_string(), - "Number expected" - ); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(primitive), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); } { @@ -2831,27 +2854,21 @@ fn try_from_value() { let _int32 = v8::Local::::try_from(primitive).unwrap(); let _int32 = v8::Local::::try_from(integer).unwrap(); let _int32 = v8::Local::::try_from(number).unwrap(); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "String expected" - ); - assert_eq!( - v8::Local::::try_from(primitive) - .err() - .unwrap() - .to_string(), - "Boolean expected" - ); - assert_eq!( - v8::Local::::try_from(integer) - .err() - .unwrap() - .to_string(), - "Uint32 expected" - ); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(primitive), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(integer), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); } { @@ -2859,48 +2876,36 @@ fn try_from_value() { let object = v8::Local::::try_from(value).unwrap(); let _function = v8::Local::::try_from(value).unwrap(); let _function = v8::Local::::try_from(object).unwrap(); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "Primitive expected" - ); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "BigInt expected" - ); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "NumberObject expected" - ); - assert_eq!( - v8::Local::::try_from(object) - .err() - .unwrap() - .to_string(), - "NumberObject expected" - ); - assert_eq!( - v8::Local::::try_from(value) - .err() - .unwrap() - .to_string(), - "Set expected" - ); - assert_eq!( - v8::Local::::try_from(object) - .err() - .unwrap() - .to_string(), - "Set expected" - ); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(object), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(value), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); + assert!(matches!( + v8::Local::::try_from(object), + Err(v8::DataError::BadType { expected, .. }) + if expected == type_name::() + )); } } }