From de2cca4c7f55e3aecd9ef31c08d058ee555e413b Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 26 Jun 2020 01:40:53 +0200 Subject: [PATCH] Integrate 'TryCatch' in the scope system (#406) --- src/lib.rs | 3 +- src/scope.rs | 420 +++++++++++++++++- src/try_catch.rs | 247 ---------- .../try_catch_exception_lifetime.rs | 6 +- .../try_catch_exception_lifetime.stderr | 12 +- .../try_catch_message_lifetime.rs | 6 +- .../try_catch_message_lifetime.stderr | 12 +- tests/test_api.rs | 66 ++- 8 files changed, 457 insertions(+), 315 deletions(-) delete mode 100644 src/try_catch.rs diff --git a/src/lib.rs b/src/lib.rs index 172b0c3a..f405df20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,6 @@ mod snapshot; mod string; mod support; mod template; -mod try_catch; mod uint8_array; mod value; @@ -105,6 +104,7 @@ pub use scope::CallbackScope; pub use scope::ContextScope; pub use scope::EscapableHandleScope; pub use scope::HandleScope; +pub use scope::TryCatch; pub use script::ScriptOrigin; pub use snapshot::FunctionCodeHandling; pub use snapshot::SnapshotCreator; @@ -115,7 +115,6 @@ pub use support::SharedRef; pub use support::UniquePtr; pub use support::UniqueRef; pub use template::*; -pub use try_catch::{TryCatch, TryCatchScope}; // TODO(piscisaureus): Ideally this trait would not be exported. pub use support::MapFnTo; diff --git a/src/scope.rs b/src/scope.rs index a2e426ad..753f81b7 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -48,6 +48,16 @@ //! - A `Context` is available; any type of value can be created. //! - Derefs to `HandleScope<'s>`. //! +//! - `TryCatch<'s, P>` +//! - 's = lifetime of the TryCatch scope. +//! - `P` is either a `HandleScope` or an `EscapableHandleScope`. This type +//! also determines for how long the values returned by `TryCatch` methods +//! `exception()`, `message()`, and `stack_trace()` are valid. +//! - Derefs to `P`. +//! - Creating a new scope inside the `TryCatch` block makes its methods +//! inaccessible until the inner scope is dropped. However, the `TryCatch` +//! object will nonetheless catch all exception thrown during its lifetime. +//! //! - `CallbackScope<'s>` //! - 's = lifetime of local handles created in this scope, and the value //! returned from the callback, and of the scope itself. @@ -226,6 +236,161 @@ impl<'s, 'e: 's, C> EscapableHandleScope<'s, 'e, C> { } } +/// An external exception handler. +pub struct TryCatch<'s, P> { + data: NonNull, + _phantom: PhantomData<&'s mut P>, +} + +impl<'s, P: param::NewTryCatch<'s>> TryCatch<'s, P> { + #[allow(clippy::new_ret_no_self)] + pub fn new(param: &'s mut P) -> P::NewScope { + param.get_scope_data_mut().new_try_catch_data().as_scope() + } +} + +impl<'s, P> TryCatch<'s, P> { + /// Returns true if an exception has been caught by this try/catch block. + pub fn has_caught(&self) -> bool { + unsafe { raw::v8__TryCatch__HasCaught(self.get_raw()) } + } + + /// For certain types of exceptions, it makes no sense to continue execution. + /// + /// If CanContinue returns false, the correct action is to perform any C++ + /// cleanup needed and then return. If CanContinue returns false and + /// HasTerminated returns true, it is possible to call + /// CancelTerminateExecution in order to continue calling into the engine. + pub fn can_continue(&self) -> bool { + unsafe { raw::v8__TryCatch__CanContinue(self.get_raw()) } + } + + /// Returns true if an exception has been caught due to script execution + /// being terminated. + /// + /// There is no JavaScript representation of an execution termination + /// exception. Such exceptions are thrown when the TerminateExecution + /// methods are called to terminate a long-running script. + /// + /// If such an exception has been thrown, HasTerminated will return true, + /// indicating that it is possible to call CancelTerminateExecution in order + /// to continue calling into the engine. + pub fn has_terminated(&self) -> bool { + unsafe { raw::v8__TryCatch__HasTerminated(self.get_raw()) } + } + + /// Returns true if verbosity is enabled. + pub fn is_verbose(&self) -> bool { + unsafe { raw::v8__TryCatch__IsVerbose(self.get_raw()) } + } + + /// Set verbosity of the external exception handler. + /// + /// By default, exceptions that are caught by an external exception + /// handler are not reported. Call SetVerbose with true on an + /// external exception handler to have exceptions caught by the + /// handler reported as if they were not caught. + pub fn set_verbose(&mut self, value: bool) { + unsafe { raw::v8__TryCatch__SetVerbose(self.get_raw_mut(), value) }; + } + + /// Set whether or not this TryCatch should capture a Message object + /// which holds source information about where the exception + /// occurred. True by default. + pub fn set_capture_message(&mut self, value: bool) { + unsafe { raw::v8__TryCatch__SetCaptureMessage(self.get_raw_mut(), value) }; + } + + /// Clears any exceptions that may have been caught by this try/catch block. + /// After this method has been called, HasCaught() will return false. Cancels + /// the scheduled exception if it is caught and ReThrow() is not called + /// before. + /// + /// It is not necessary to clear a try/catch block before using it again; if + /// another exception is thrown the previously caught exception will just be + /// overwritten. However, it is often a good idea since it makes it easier + /// to determine which operation threw a given exception. + pub fn reset(&mut self) { + unsafe { raw::v8__TryCatch__Reset(self.get_raw_mut()) }; + } + + fn get_raw(&self) -> &raw::TryCatch { + data::ScopeData::get(self).get_try_catch() + } + + fn get_raw_mut(&mut self) -> &mut raw::TryCatch { + data::ScopeData::get_mut(self).get_try_catch_mut() + } +} + +impl<'s, 'p: 's, P> TryCatch<'s, P> +where + Self: AsMut>, +{ + /// Returns the exception caught by this try/catch block. If no exception has + /// been caught an empty handle is returned. + /// + /// Note: v8.h states that "the returned handle is valid until this TryCatch + /// block has been destroyed". This is incorrect; the return value lives + /// no longer and no shorter than the active HandleScope at the time this + /// method is called. An issue has been opened about this in the V8 bug + /// tracker: https://bugs.chromium.org/p/v8/issues/detail?id=10537. + pub fn exception(&mut self) -> Option> { + unsafe { + self + .as_mut() + .cast_local(|sd| raw::v8__TryCatch__Exception(sd.get_try_catch())) + } + } + + /// Returns the message associated with this exception. If there is + /// no message associated an empty handle is returned. + /// + /// Note: the remark about the lifetime for the `exception()` return value + /// applies here too. + pub fn message(&mut self) -> Option> { + unsafe { + self + .as_mut() + .cast_local(|sd| raw::v8__TryCatch__Message(sd.get_try_catch())) + } + } + + /// Throws the exception caught by this TryCatch in a way that avoids + /// it being caught again by this same TryCatch. As with ThrowException + /// it is illegal to execute any JavaScript operations after calling + /// ReThrow; the caller must return immediately to where the exception + /// is caught. + /// + /// This function returns the `undefined` value when successful, or `None` if + /// no exception was caught and therefore there was nothing to rethrow. + pub fn rethrow(&mut self) -> Option> { + unsafe { + self + .as_mut() + .cast_local(|sd| raw::v8__TryCatch__ReThrow(sd.get_try_catch_mut())) + } + } +} + +impl<'s, 'p: 's, P> TryCatch<'s, P> +where + Self: AsMut>, +{ + /// Returns the .stack property of the thrown object. If no .stack + /// property is present an empty handle is returned. + pub fn stack_trace(&mut self) -> Option> { + unsafe { + self.as_mut().cast_local(|sd| { + raw::v8__TryCatch__StackTrace( + sd.get_try_catch(), + sd.get_current_context(), + ) + }) + } + } +} + /// A `CallbackScope` can be used to bootstrap a `HandleScope` and /// `ContextScope` inside a callback function that gets called by V8. /// Bootstrapping a scope inside a callback is the only valid use case of this @@ -295,25 +460,40 @@ macro_rules! impl_as { impl_as!(<'s, 'p, P> ContextScope<'s, P> as Isolate); impl_as!(<'s, C> HandleScope<'s, C> as Isolate); impl_as!(<'s, 'e, C> EscapableHandleScope<'s, 'e, C> as Isolate); +impl_as!(<'s, P> TryCatch<'s, P> as Isolate); impl_as!(<'s> CallbackScope<'s> as Isolate); impl_as!(<'s, 'p> ContextScope<'s, HandleScope<'p>> as HandleScope<'p, ()>); impl_as!(<'s, 'p, 'e> ContextScope<'s, EscapableHandleScope<'p, 'e>> as HandleScope<'p, ()>); impl_as!(<'s, C> HandleScope<'s, C> as HandleScope<'s, ()>); impl_as!(<'s, 'e, C> EscapableHandleScope<'s, 'e, C> as HandleScope<'s, ()>); +impl_as!(<'s, 'p, C> TryCatch<'s, HandleScope<'p, C>> as HandleScope<'p, ()>); +impl_as!(<'s, 'p, 'e, C> TryCatch<'s, EscapableHandleScope<'p, 'e, C>> as HandleScope<'p, ()>); impl_as!(<'s> CallbackScope<'s> as HandleScope<'s, ()>); impl_as!(<'s, 'p> ContextScope<'s, HandleScope<'p>> as HandleScope<'p>); impl_as!(<'s, 'p, 'e> ContextScope<'s, EscapableHandleScope<'p, 'e>> as HandleScope<'p>); impl_as!(<'s> HandleScope<'s> as HandleScope<'s>); impl_as!(<'s, 'e> EscapableHandleScope<'s, 'e> as HandleScope<'s>); +impl_as!(<'s, 'p> TryCatch<'s, HandleScope<'p>> as HandleScope<'p>); +impl_as!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e>> as HandleScope<'p>); impl_as!(<'s> CallbackScope<'s> as HandleScope<'s>); impl_as!(<'s, 'p, 'e> ContextScope<'s, EscapableHandleScope<'p, 'e>> as EscapableHandleScope<'p, 'e, ()>); impl_as!(<'s, 'e, C> EscapableHandleScope<'s, 'e, C> as EscapableHandleScope<'s, 'e, ()>); +impl_as!(<'s, 'p, 'e, C> TryCatch<'s, EscapableHandleScope<'p, 'e, C>> as EscapableHandleScope<'p, 'e, ()>); impl_as!(<'s, 'p, 'e> ContextScope<'s, EscapableHandleScope<'p, 'e>> as EscapableHandleScope<'p, 'e>); impl_as!(<'s, 'e> EscapableHandleScope<'s, 'e> as EscapableHandleScope<'s, 'e>); +impl_as!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e>> as EscapableHandleScope<'p, 'e>); + +impl_as!(<'s, 'p, C> TryCatch<'s, HandleScope<'p, C>> as TryCatch<'s, HandleScope<'p, ()>>); +impl_as!(<'s, 'p, 'e, C> TryCatch<'s, EscapableHandleScope<'p, 'e, C>> as TryCatch<'s, HandleScope<'p, ()>>); +impl_as!(<'s, 'p, 'e, C> TryCatch<'s, EscapableHandleScope<'p, 'e, C>> as TryCatch<'s, EscapableHandleScope<'p, 'e, ()>>); + +impl_as!(<'s, 'p> TryCatch<'s, HandleScope<'p>> as TryCatch<'s, HandleScope<'p>>); +impl_as!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e>> as TryCatch<'s, HandleScope<'p>>); +impl_as!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e>> as TryCatch<'s, EscapableHandleScope<'p, 'e>>); macro_rules! impl_deref { (<$($params:tt),+> $src_type:ty as $tgt_type:ty) => { @@ -341,6 +521,11 @@ impl_deref!(<'s> HandleScope<'s> as HandleScope<'s, ()>); impl_deref!(<'s, 'e> EscapableHandleScope<'s, 'e, ()> as HandleScope<'s, ()>); impl_deref!(<'s, 'e> EscapableHandleScope<'s, 'e> as HandleScope<'s>); +impl_deref!(<'s, 'p> TryCatch<'s, HandleScope<'p, ()>> as HandleScope<'p, ()>); +impl_deref!(<'s, 'p> TryCatch<'s, HandleScope<'p>> as HandleScope<'p>); +impl_deref!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e, ()>> as EscapableHandleScope<'p, 'e, ()>); +impl_deref!(<'s, 'p, 'e> TryCatch<'s, EscapableHandleScope<'p, 'e>> as EscapableHandleScope<'p, 'e>); + impl_deref!(<'s> CallbackScope<'s> as HandleScope<'s>); macro_rules! impl_scope_drop { @@ -358,6 +543,7 @@ macro_rules! impl_scope_drop { impl_scope_drop!(<'s, 'p, P> ContextScope<'s, P>); impl_scope_drop!(<'s, C> HandleScope<'s, C> ); impl_scope_drop!(<'s, 'e, C> EscapableHandleScope<'s, 'e, C> ); +impl_scope_drop!(<'s, P> TryCatch<'s, P> ); impl_scope_drop!(<'s> CallbackScope<'s> ); pub unsafe trait Scope: Sized {} @@ -413,6 +599,12 @@ mod param { type NewScope = ContextScope<'s, EscapableHandleScope<'p, 'e>>; } + impl<'s, 'p: 's, P: NewContextScope<'s>> NewContextScope<'s> + for TryCatch<'p, P> + { + type NewScope =

>::NewScope; + } + impl<'s, 'p: 's> NewContextScope<'s> for CallbackScope<'p> { type NewScope = ContextScope<'s, HandleScope<'p>>; } @@ -445,6 +637,10 @@ mod param { type NewScope = EscapableHandleScope<'s, 'e, C>; } + impl<'s, 'p: 's, P: NewHandleScope<'s>> NewHandleScope<'s> for TryCatch<'p, P> { + type NewScope =

>::NewScope; + } + impl<'s, 'p: 's> NewHandleScope<'s> for CallbackScope<'p> { type NewScope = HandleScope<'s>; } @@ -469,10 +665,42 @@ mod param { type NewScope = EscapableHandleScope<'s, 'p, C>; } + impl<'s, 'p: 's, 'e: 'p, P: NewEscapableHandleScope<'s, 'e>> + NewEscapableHandleScope<'s, 'e> for TryCatch<'p, P> + { + type NewScope =

>::NewScope; + } + impl<'s, 'p: 's> NewEscapableHandleScope<'s, 'p> for CallbackScope<'p> { type NewScope = EscapableHandleScope<'s, 'p>; } + pub trait NewTryCatch<'s>: data::GetScopeData { + type NewScope: Scope; + } + + impl<'s, 'p: 's, P: NewTryCatch<'s>> NewTryCatch<'s> for ContextScope<'p, P> { + type NewScope =

>::NewScope; + } + + impl<'s, 'p: 's, C> NewTryCatch<'s> for HandleScope<'p, C> { + type NewScope = TryCatch<'s, HandleScope<'p, C>>; + } + + impl<'s, 'p: 's, 'e: 'p, C> NewTryCatch<'s> + for EscapableHandleScope<'p, 'e, C> + { + type NewScope = TryCatch<'s, EscapableHandleScope<'p, 'e, C>>; + } + + impl<'s, 'p: 's, P> NewTryCatch<'s> for TryCatch<'p, P> { + type NewScope = TryCatch<'s, P>; + } + + impl<'s, 'p: 's> NewTryCatch<'s> for CallbackScope<'p> { + type NewScope = TryCatch<'s, HandleScope<'p>>; + } + pub trait NewCallbackScope<'s>: Copy + Sized { fn maybe_get_current_context(self) -> Option> { None @@ -548,6 +776,7 @@ pub(crate) mod data { // (eiter current or shadowed -- not free). context: Cell>>, escape_slot: Option>>, + try_catch: Option>, scope_type_specific_data: ScopeTypeSpecificData, } @@ -660,6 +889,24 @@ pub(crate) mod data { }) } + pub(super) fn new_try_catch_data(&mut self) -> &mut Self { + self.new_scope_data_with(|data| { + let isolate = data.isolate; + data.scope_type_specific_data.init_with(|| { + ScopeTypeSpecificData::TryCatch { + raw_try_catch: unsafe { raw::TryCatch::uninit() }, + } + }); + match &mut data.scope_type_specific_data { + ScopeTypeSpecificData::TryCatch { raw_try_catch } => { + unsafe { raw_try_catch.init(isolate) }; + data.try_catch.replace(raw_try_catch.into()); + } + _ => unreachable!(), + } + }) + } + pub(super) fn new_callback_scope_data<'s>( &'s mut self, maybe_current_context: Option>, @@ -893,6 +1140,22 @@ pub(crate) mod data { .map(|escape_slot_nn| unsafe { escape_slot_nn.as_mut() }) } + pub(super) fn get_try_catch(&self) -> &raw::TryCatch { + self + .try_catch + .as_ref() + .map(|try_catch_nn| unsafe { try_catch_nn.as_ref() }) + .unwrap() + } + + pub(super) fn get_try_catch_mut(&mut self) -> &mut raw::TryCatch { + self + .try_catch + .as_mut() + .map(|try_catch_nn| unsafe { try_catch_nn.as_mut() }) + .unwrap() + } + /// Returns a new `Box` with the `isolate` field set as specified /// by the first parameter, and the other fields initialized to their /// default values. This function exists solely because it turns out that @@ -911,6 +1174,7 @@ pub(crate) mod data { status: Default::default(), context: Default::default(), escape_slot: Default::default(), + try_catch: Default::default(), scope_type_specific_data: Default::default(), }, ); @@ -944,6 +1208,9 @@ pub(crate) mod data { raw_handle_scope: raw::HandleScope, raw_escape_slot: Option, }, + TryCatch { + raw_try_catch: raw::TryCatch, + }, } impl Default for ScopeTypeSpecificData { @@ -1081,6 +1348,34 @@ mod raw { } } + #[repr(C)] + pub(super) struct TryCatch([usize; 6]); + + impl TryCatch { + /// This function is marked unsafe because the caller must ensure that the + /// returned value isn't dropped before `init()` has been called. + pub unsafe fn uninit() -> Self { + // This is safe because there is no combination of bits that would produce + // an invalid `[usize; 6]`. + #[allow(clippy::uninit_assumed_init)] + Self(MaybeUninit::uninit().assume_init()) + } + + /// This function is marked unsafe because `init()` must be called exactly + /// once, no more and no less, after creating a `TryCatch` value with + /// `TryCatch::uninit()`. + pub unsafe fn init(&mut self, isolate: NonNull) { + let buf = NonNull::from(self).cast(); + v8__TryCatch__CONSTRUCT(buf.as_ptr(), isolate.as_ptr()); + } + } + + impl Drop for TryCatch { + fn drop(&mut self) { + unsafe { v8__TryCatch__DESTRUCT(self) }; + } + } + extern "C" { pub(super) fn v8__Isolate__GetCurrentContext( isolate: *mut Isolate, @@ -1114,6 +1409,33 @@ mod raw { ) -> *const Data; pub(super) fn v8__Undefined(isolate: *mut Isolate) -> *const Primitive; + pub(super) fn v8__TryCatch__CONSTRUCT( + buf: *mut MaybeUninit, + isolate: *mut Isolate, + ); + pub(super) fn v8__TryCatch__DESTRUCT(this: *mut TryCatch); + pub(super) fn v8__TryCatch__HasCaught(this: *const TryCatch) -> bool; + pub(super) fn v8__TryCatch__CanContinue(this: *const TryCatch) -> bool; + pub(super) fn v8__TryCatch__HasTerminated(this: *const TryCatch) -> bool; + pub(super) fn v8__TryCatch__IsVerbose(this: *const TryCatch) -> bool; + pub(super) fn v8__TryCatch__SetVerbose(this: *mut TryCatch, value: bool); + pub(super) fn v8__TryCatch__SetCaptureMessage( + this: *mut TryCatch, + value: bool, + ); + pub(super) fn v8__TryCatch__Reset(this: *mut TryCatch); + pub(super) fn v8__TryCatch__Exception( + this: *const TryCatch, + ) -> *const Value; + pub(super) fn v8__TryCatch__StackTrace( + this: *const TryCatch, + context: *const Context, + ) -> *const Value; + pub(super) fn v8__TryCatch__Message( + this: *const TryCatch, + ) -> *const Message; + pub(super) fn v8__TryCatch__ReThrow(this: *mut TryCatch) -> *const Value; + pub(super) fn v8__Message__GetIsolate(this: *const Message) -> *mut Isolate; pub(super) fn v8__Object__GetIsolate(this: *const Object) -> *mut Isolate; @@ -1179,24 +1501,60 @@ mod tests { AssertTypeOf(d).is::(); } { - let l3_ehs = &mut EscapableHandleScope::new(l2_cxs); - AssertTypeOf(l3_ehs).is::(); - let l4_cxs = &mut ContextScope::new(l3_ehs, context); - AssertTypeOf(l4_cxs).is::>(); - let d = l4_cxs.deref_mut(); - AssertTypeOf(d).is::(); - let d = d.deref_mut(); + let l3_tc = &mut TryCatch::new(l2_cxs); + AssertTypeOf(l3_tc).is::>(); + let d = l3_tc.deref_mut(); AssertTypeOf(d).is::(); let d = d.deref_mut(); AssertTypeOf(d).is::>(); let d = d.deref_mut(); AssertTypeOf(d).is::(); } + { + let l3_ehs = &mut EscapableHandleScope::new(l2_cxs); + AssertTypeOf(l3_ehs).is::(); + { + let l4_cxs = &mut ContextScope::new(l3_ehs, context); + AssertTypeOf(l4_cxs).is::>(); + let d = l4_cxs.deref_mut(); + AssertTypeOf(d).is::(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); + let d = d.deref_mut(); + AssertTypeOf(d).is::>(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); + } + { + let l4_tc = &mut TryCatch::new(l3_ehs); + AssertTypeOf(l4_tc).is::>(); + let d = l4_tc.deref_mut(); + AssertTypeOf(d).is::(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); + let d = d.deref_mut(); + AssertTypeOf(d).is::>(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); + } + } + } + { + let l2_tc = &mut TryCatch::new(l1_hs); + AssertTypeOf(l2_tc).is::>>(); + let d = l2_tc.deref_mut(); + AssertTypeOf(d).is::>(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); } { let l2_ehs = &mut EscapableHandleScope::new(l1_hs); AssertTypeOf(l2_ehs).is::>(); - let d = l2_ehs.deref_mut(); + let l3_tc = &mut TryCatch::new(l2_ehs); + AssertTypeOf(l3_tc).is::>>(); + let d = l3_tc.deref_mut(); + AssertTypeOf(d).is::>(); + let d = d.deref_mut(); AssertTypeOf(d).is::>(); let d = d.deref_mut(); AssertTypeOf(d).is::(); @@ -1235,6 +1593,7 @@ mod tests { AssertTypeOf(&HandleScope::new(l2_cxs)).is::(); AssertTypeOf(&EscapableHandleScope::new(l2_cxs)) .is::(); + AssertTypeOf(&TryCatch::new(l2_cxs)).is::>(); } { let l2_ehs = &mut EscapableHandleScope::new(l1_hs); @@ -1250,7 +1609,39 @@ mod tests { AssertTypeOf(&HandleScope::new(l3_cxs)).is::(); AssertTypeOf(&EscapableHandleScope::new(l3_cxs)) .is::(); + { + let l4_tc = &mut TryCatch::new(l3_cxs); + AssertTypeOf(l4_tc).is::>(); + AssertTypeOf(&ContextScope::new(l4_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l4_tc)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l4_tc)) + .is::(); + AssertTypeOf(&TryCatch::new(l4_tc)) + .is::>(); + } } + { + let l3_tc = &mut TryCatch::new(l2_ehs); + AssertTypeOf(l3_tc).is::>>(); + AssertTypeOf(&ContextScope::new(l3_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_tc)).is::>(); + AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + .is::>(); + AssertTypeOf(&TryCatch::new(l3_tc)) + .is::>>(); + } + } + { + let l2_tc = &mut TryCatch::new(l1_hs); + AssertTypeOf(l2_tc).is::>>(); + AssertTypeOf(&ContextScope::new(l2_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l2_tc)).is::>(); + AssertTypeOf(&EscapableHandleScope::new(l2_tc)) + .is::>(); + AssertTypeOf(&TryCatch::new(l2_tc)).is::>>(); } { let l2_cbs = &mut unsafe { CallbackScope::new(context) }; @@ -1265,6 +1656,7 @@ mod tests { AssertTypeOf(&HandleScope::new(l3_hs)).is::(); AssertTypeOf(&EscapableHandleScope::new(l3_hs)) .is::(); + AssertTypeOf(&TryCatch::new(l3_hs)).is::>(); } { let l3_ehs = &mut EscapableHandleScope::new(l2_cbs); @@ -1274,6 +1666,18 @@ mod tests { AssertTypeOf(&HandleScope::new(l3_ehs)).is::(); AssertTypeOf(&EscapableHandleScope::new(l3_ehs)) .is::(); + AssertTypeOf(&TryCatch::new(l3_ehs)) + .is::>(); + } + { + let l3_tc = &mut TryCatch::new(l2_cbs); + AssertTypeOf(l3_tc).is::>(); + AssertTypeOf(&ContextScope::new(l3_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_tc)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + .is::(); + AssertTypeOf(&TryCatch::new(l3_tc)).is::>(); } } } diff --git a/src/try_catch.rs b/src/try_catch.rs deleted file mode 100644 index 5aaec222..00000000 --- a/src/try_catch.rs +++ /dev/null @@ -1,247 +0,0 @@ -use std::marker::PhantomData; -use std::mem::size_of; -use std::mem::size_of_val; -use std::mem::take; -use std::mem::MaybeUninit; - -use crate::Context; -use crate::HandleScope; -use crate::Isolate; -use crate::Local; -use crate::Message; -use crate::Value; - -extern "C" { - // Note: the C++ CxxTryCatch object *must* live on the stack, and it must - // not move after it is constructed. - fn v8__TryCatch__CONSTRUCT( - buf: *mut MaybeUninit, - isolate: *mut Isolate, - ); - - fn v8__TryCatch__DESTRUCT(this: *mut CxxTryCatch); - - fn v8__TryCatch__HasCaught(this: *const CxxTryCatch) -> bool; - - fn v8__TryCatch__CanContinue(this: *const CxxTryCatch) -> bool; - - fn v8__TryCatch__HasTerminated(this: *const CxxTryCatch) -> bool; - - fn v8__TryCatch__Exception(this: *const CxxTryCatch) -> *const Value; - - fn v8__TryCatch__StackTrace( - this: *const CxxTryCatch, - context: *const Context, - ) -> *const Value; - - fn v8__TryCatch__Message(this: *const CxxTryCatch) -> *const Message; - - fn v8__TryCatch__Reset(this: *mut CxxTryCatch); - - fn v8__TryCatch__ReThrow(this: *mut CxxTryCatch) -> *const Value; - - fn v8__TryCatch__IsVerbose(this: *const CxxTryCatch) -> bool; - - fn v8__TryCatch__SetVerbose(this: *mut CxxTryCatch, value: bool); - - fn v8__TryCatch__SetCaptureMessage(this: *mut CxxTryCatch, value: bool); -} - -// Note: the 'tc lifetime is there to ensure that after entering a TryCatchScope -// once, the same TryCatch object can't be entered again. - -/// An external exception handler. -#[repr(transparent)] -pub struct TryCatch<'tc>(CxxTryCatch, PhantomData<&'tc ()>); - -#[repr(C)] -struct CxxTryCatch([usize; 6]); - -/// A scope object that will, when entered, active the embedded TryCatch block. -pub struct TryCatchScope<'tc>(TryCatchState<'tc>); - -enum TryCatchState<'tc> { - New { isolate: *mut Isolate }, - Uninit(MaybeUninit>), - Entered(TryCatch<'tc>), -} - -impl<'tc> TryCatch<'tc> { - /// Creates a new try/catch block. Note that all TryCatch blocks should be - /// stack allocated because the memory location itself is compared against - /// JavaScript try/catch blocks. - #[allow(clippy::new_ret_no_self)] - pub fn new(scope: &mut Isolate) -> TryCatchScope<'tc> { - TryCatchScope(TryCatchState::New { isolate: scope }) - } - - /// Returns true if an exception has been caught by this try/catch block. - pub fn has_caught(&self) -> bool { - unsafe { v8__TryCatch__HasCaught(&self.0) } - } - - /// For certain types of exceptions, it makes no sense to continue execution. - /// - /// If CanContinue returns false, the correct action is to perform any C++ - /// cleanup needed and then return. If CanContinue returns false and - /// HasTerminated returns true, it is possible to call - /// CancelTerminateExecution in order to continue calling into the engine. - pub fn can_continue(&self) -> bool { - unsafe { v8__TryCatch__CanContinue(&self.0) } - } - - /// Returns true if an exception has been caught due to script execution - /// being terminated. - /// - /// There is no JavaScript representation of an execution termination - /// exception. Such exceptions are thrown when the TerminateExecution - /// methods are called to terminate a long-running script. - /// - /// If such an exception has been thrown, HasTerminated will return true, - /// indicating that it is possible to call CancelTerminateExecution in order - /// to continue calling into the engine. - pub fn has_terminated(&self) -> bool { - unsafe { v8__TryCatch__HasTerminated(&self.0) } - } - - /// Returns the exception caught by this try/catch block. If no exception has - /// been caught an empty handle is returned. - /// - /// Note: v8.h states that "the returned handle is valid until this TryCatch - /// block has been destroyed". This is incorrect; the return value lives - /// no longer and no shorter than the active HandleScope at the time this - /// method is called. An issue has been opened about this in the V8 bug - /// tracker: https://bugs.chromium.org/p/v8/issues/detail?id=10537. - pub fn exception<'s>( - &self, - scope: &mut HandleScope<'s>, - ) -> Option> { - unsafe { scope.cast_local(|_| v8__TryCatch__Exception(&self.0)) } - } - - /// Returns the message associated with this exception. If there is - /// no message associated an empty handle is returned. - /// - /// Note: the remark about the lifetime for the `exception()` return value - /// applies here too. - pub fn message<'s>( - &self, - scope: &mut HandleScope<'s>, - ) -> Option> { - unsafe { scope.cast_local(|_| v8__TryCatch__Message(&self.0)) } - } - - /// Returns the .stack property of the thrown object. If no .stack - /// property is present an empty handle is returned. - pub fn stack_trace<'s>( - &self, - scope: &mut HandleScope<'s>, - context: Local, - ) -> Option> { - unsafe { - scope.cast_local(|_| v8__TryCatch__StackTrace(&self.0, &*context)) - } - } - - /// Clears any exceptions that may have been caught by this try/catch block. - /// After this method has been called, HasCaught() will return false. Cancels - /// the scheduled exception if it is caught and ReThrow() is not called - /// before. - /// - /// It is not necessary to clear a try/catch block before using it again; if - /// another exception is thrown the previously caught exception will just be - /// overwritten. However, it is often a good idea since it makes it easier - /// to determine which operation threw a given exception. - pub fn reset(&mut self) { - unsafe { v8__TryCatch__Reset(&mut self.0) }; - } - - /// Throws the exception caught by this TryCatch in a way that avoids - /// it being caught again by this same TryCatch. As with ThrowException - /// it is illegal to execute any JavaScript operations after calling - /// ReThrow; the caller must return immediately to where the exception - /// is caught. - /// - /// This function returns the `undefined` value when successful, or `None` if - /// no exception was caught and therefore there was nothing to rethrow. - pub fn rethrow(&mut self) -> Option> { - let result = unsafe { Local::from_raw(v8__TryCatch__ReThrow(&mut self.0)) }; - if let Some(value) = result { - debug_assert!(value.is_undefined()) - } - result - } - - /// Returns true if verbosity is enabled. - pub fn is_verbose(&self) -> bool { - unsafe { v8__TryCatch__IsVerbose(&self.0) } - } - - /// Set verbosity of the external exception handler. - /// - /// By default, exceptions that are caught by an external exception - /// handler are not reported. Call SetVerbose with true on an - /// external exception handler to have exceptions caught by the - /// handler reported as if they were not caught. - pub fn set_verbose(&mut self, value: bool) { - unsafe { v8__TryCatch__SetVerbose(&mut self.0, value) }; - } - - /// Set whether or not this TryCatch should capture a Message object - /// which holds source information about where the exception - /// occurred. True by default. - pub fn set_capture_message(&mut self, value: bool) { - unsafe { v8__TryCatch__SetCaptureMessage(&mut self.0, value) }; - } - - fn construct(buf: &mut MaybeUninit, isolate: *mut Isolate) { - unsafe { - assert_eq!(size_of_val(buf), size_of::()); - let buf = &mut *(buf as *mut _ as *mut MaybeUninit); - v8__TryCatch__CONSTRUCT(buf, isolate); - } - } -} - -impl Drop for CxxTryCatch { - fn drop(&mut self) { - unsafe { v8__TryCatch__DESTRUCT(self) } - } -} - -impl<'tc> TryCatchScope<'tc> { - /// Enters the TryCatch block. Exceptions are caught as long as the returned - /// TryCatch object remains in scope. - pub fn enter(&'tc mut self) -> &'tc mut TryCatch { - use TryCatchState::*; - let state = &mut self.0; - - let isolate = match take(state) { - New { isolate } => isolate, - _ => unreachable!(), - }; - - let buf = match state { - Uninit(b) => b, - _ => unreachable!(), - }; - - TryCatch::construct(buf, isolate); - - *state = match take(state) { - Uninit(b) => Entered(unsafe { b.assume_init() }), - _ => unreachable!(), - }; - - match state { - Entered(v) => v, - _ => unreachable!(), - } - } -} - -impl<'tc> Default for TryCatchState<'tc> { - fn default() -> Self { - Self::Uninit(MaybeUninit::uninit()) - } -} diff --git a/tests/compile_fail/try_catch_exception_lifetime.rs b/tests/compile_fail/try_catch_exception_lifetime.rs index bc5b8c49..50560757 100644 --- a/tests/compile_fail/try_catch_exception_lifetime.rs +++ b/tests/compile_fail/try_catch_exception_lifetime.rs @@ -7,13 +7,11 @@ pub fn main() { let context = v8::Context::new(&mut scope1); let mut scope2 = v8::ContextScope::new(&mut scope1, context); - let mut try_catch = v8::TryCatch::new(&mut scope2); - let try_catch = try_catch.enter(); - let _exception = { let mut scope3 = v8::HandleScope::new(&mut scope2); let mut scope4 = v8::HandleScope::new(&mut scope3); - try_catch.exception(&mut scope4).unwrap() + let mut try_catch = v8::TryCatch::new(&mut scope4); + try_catch.exception().unwrap() }; } diff --git a/tests/compile_fail/try_catch_exception_lifetime.stderr b/tests/compile_fail/try_catch_exception_lifetime.stderr index d583eeae..664f164e 100644 --- a/tests/compile_fail/try_catch_exception_lifetime.stderr +++ b/tests/compile_fail/try_catch_exception_lifetime.stderr @@ -1,11 +1,11 @@ error[E0597]: `scope3` does not live long enough - --> $DIR/try_catch_exception_lifetime.rs:15:43 + --> $DIR/try_catch_exception_lifetime.rs:12:43 | -13 | let _exception = { +10 | let _exception = { | ---------- borrow later stored here -14 | let mut scope3 = v8::HandleScope::new(&mut scope2); -15 | let mut scope4 = v8::HandleScope::new(&mut scope3); +11 | let mut scope3 = v8::HandleScope::new(&mut scope2); +12 | let mut scope4 = v8::HandleScope::new(&mut scope3); | ^^^^^^^^^^^ borrowed value does not live long enough -16 | try_catch.exception(&mut scope4).unwrap() -17 | }; +... +15 | }; | - `scope3` dropped here while still borrowed diff --git a/tests/compile_fail/try_catch_message_lifetime.rs b/tests/compile_fail/try_catch_message_lifetime.rs index a59227e1..98172495 100644 --- a/tests/compile_fail/try_catch_message_lifetime.rs +++ b/tests/compile_fail/try_catch_message_lifetime.rs @@ -7,13 +7,11 @@ pub fn main() { let context = v8::Context::new(&mut scope1); let mut scope2 = v8::ContextScope::new(&mut scope1, context); - let mut try_catch = v8::TryCatch::new(&mut scope2); - let try_catch = try_catch.enter(); - let _message = { let mut scope3 = v8::HandleScope::new(&mut scope2); let mut scope4 = v8::HandleScope::new(&mut scope3); - try_catch.message(&mut scope4).unwrap() + let mut try_catch = v8::TryCatch::new(&mut scope4); + try_catch.message().unwrap() }; } diff --git a/tests/compile_fail/try_catch_message_lifetime.stderr b/tests/compile_fail/try_catch_message_lifetime.stderr index 411142ad..e38d6932 100644 --- a/tests/compile_fail/try_catch_message_lifetime.stderr +++ b/tests/compile_fail/try_catch_message_lifetime.stderr @@ -1,11 +1,11 @@ error[E0597]: `scope3` does not live long enough - --> $DIR/try_catch_message_lifetime.rs:15:43 + --> $DIR/try_catch_message_lifetime.rs:12:43 | -13 | let _message = { +10 | let _message = { | -------- borrow later stored here -14 | let mut scope3 = v8::HandleScope::new(&mut scope2); -15 | let mut scope4 = v8::HandleScope::new(&mut scope3); +11 | let mut scope3 = v8::HandleScope::new(&mut scope2); +12 | let mut scope4 = v8::HandleScope::new(&mut scope3); | ^^^^^^^^^^^ borrowed value does not live long enough -16 | try_catch.message(&mut scope4).unwrap() -17 | }; +... +15 | }; | - `scope3` dropped here while still borrowed diff --git a/tests/test_api.rs b/tests/test_api.rs index b7b15c20..a3020877 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -549,42 +549,35 @@ fn try_catch() { let scope = &mut v8::ContextScope::new(scope, context); { // Error thrown - should be caught. - let mut try_catch = v8::TryCatch::new(scope); - let tc = try_catch.enter(); - let result = eval(scope, context, "throw new Error('foo')"); + let tc = &mut v8::TryCatch::new(scope); + let result = eval(tc, context, "throw new Error('foo')"); assert!(result.is_none()); assert!(tc.has_caught()); - assert!(tc.exception(scope).is_some()); - assert!(tc.stack_trace(scope, context).is_some()); - assert!(tc.message(scope).is_some()); + assert!(tc.exception().is_some()); + assert!(tc.stack_trace().is_some()); + assert!(tc.message().is_some()); assert_eq!( - tc.message(scope) - .unwrap() - .get(scope) - .to_rust_string_lossy(scope), + tc.message().unwrap().get(tc).to_rust_string_lossy(tc), "Uncaught Error: foo" ); }; { // No error thrown. - let mut try_catch = v8::TryCatch::new(scope); - let tc = try_catch.enter(); - let result = eval(scope, context, "1 + 1"); + let tc = &mut v8::TryCatch::new(scope); + let result = eval(tc, context, "1 + 1"); assert!(result.is_some()); assert!(!tc.has_caught()); - assert!(tc.exception(scope).is_none()); - assert!(tc.stack_trace(scope, context).is_none()); - assert!(tc.message(scope).is_none()); + assert!(tc.exception().is_none()); + assert!(tc.stack_trace().is_none()); + assert!(tc.message().is_none()); assert!(tc.rethrow().is_none()); }; { // Rethrow and reset. - let mut try_catch_1 = v8::TryCatch::new(scope); - let tc1 = try_catch_1.enter(); + let tc1 = &mut v8::TryCatch::new(scope); { - let mut try_catch_2 = v8::TryCatch::new(scope); - let tc2 = try_catch_2.enter(); - eval(scope, context, "throw 'bar'"); + let tc2 = &mut v8::TryCatch::new(tc1); + eval(tc2, context, "throw 'bar'"); assert!(tc2.has_caught()); assert!(tc2.rethrow().is_some()); tc2.reset(); @@ -603,15 +596,14 @@ fn try_catch_caught_lifetime() { let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); let (caught_exc, caught_msg) = { - let mut try_catch = v8::TryCatch::new(scope); - let try_catch = try_catch.enter(); + let tc = &mut v8::TryCatch::new(scope); // Throw exception. - let msg = v8::String::new(scope, "DANG!").unwrap(); - let exc = v8::Exception::type_error(scope, msg); - scope.throw_exception(exc); + let msg = v8::String::new(tc, "DANG!").unwrap(); + let exc = v8::Exception::type_error(tc, msg); + tc.throw_exception(exc); // Catch exception. - let caught_exc = try_catch.exception(scope).unwrap(); - let caught_msg = try_catch.message(scope).unwrap(); + let caught_exc = tc.exception().unwrap(); + let caught_msg = tc.message().unwrap(); // Move `caught_exc` and `caught_msg` out of the extent of the TryCatch, // but still within the extent of the enclosing HandleScope. (caught_exc, caught_msg) @@ -637,15 +629,14 @@ fn throw_exception() { let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); { - let mut try_catch = v8::TryCatch::new(scope); - let tc = try_catch.enter(); - let exception = v8::String::new(scope, "boom").unwrap(); - scope.throw_exception(exception.into()); + let tc = &mut v8::TryCatch::new(scope); + let exception = v8::String::new(tc, "boom").unwrap(); + tc.throw_exception(exception.into()); assert!(tc.has_caught()); assert!(tc - .exception(scope) + .exception() .unwrap() - .strict_equals(v8::String::new(scope, "boom").unwrap().into())); + .strict_equals(v8::String::new(tc, "boom").unwrap().into())); }; } } @@ -1593,8 +1584,7 @@ fn module_instantiation_failures1() { // Instantiation should fail. { - let mut try_catch = v8::TryCatch::new(scope); - let tc = try_catch.enter(); + let tc = &mut v8::TryCatch::new(scope); fn resolve_callback<'a>( context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, @@ -1610,9 +1600,9 @@ fn module_instantiation_failures1() { assert!(result.is_none()); assert!(tc.has_caught()); assert!(tc - .exception(scope) + .exception() .unwrap() - .strict_equals(v8::String::new(scope, "boom").unwrap().into())); + .strict_equals(v8::String::new(tc, "boom").unwrap().into())); assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); } }