diff --git a/src/exception.rs b/src/exception.rs index 3b996003cb..4054ceed47 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -299,10 +299,10 @@ impl Exception { message: Local, contructor: unsafe extern "C" fn(*const String) -> *const Value, ) -> Local<'s, Value> { - scope.get_isolate_mut().enter(); + scope.enter_isolate(); let error = unsafe { scope.cast_local(|_| (contructor)(&*message)) }.unwrap(); - scope.get_isolate_mut().exit(); + scope.exit_isolate(); error } diff --git a/src/isolate.rs b/src/isolate.rs index 969ed62fc4..c2d6041f3e 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -276,7 +276,7 @@ impl Isolate { /// Sets this isolate as the entered one for the current thread. /// Saves the previously entered one (if any), so that it can be /// restored when exiting. Re-entering an isolate is allowed. - pub(crate) fn enter(&mut self) { + pub(crate) fn enter_isolate(&mut self) { unsafe { v8__Isolate__Enter(self) } } @@ -285,7 +285,7 @@ impl Isolate { /// entered more than once. /// /// Requires: self == Isolate::GetCurrent(). - pub(crate) fn exit(&mut self) { + pub(crate) fn exit_isolate(&mut self) { unsafe { v8__Isolate__Exit(self) } } diff --git a/src/lib.rs b/src/lib.rs index 172b0c3afd..f405df207f 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 c8e348b021..80c5aef78c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -17,25 +17,19 @@ //! - `HandleScope<'s, ()>` //! - 's = lifetime of local handles created in this scope, and of the scope //! itself. -//! - This type is returned when a HandleScope is constructed from an isolate -//! (`&mut Isolate` or `&mut OwnedIsolate`) reference. +//! - This type is returned when a HandleScope is constructed from a direct +//! reference to an isolate (`&mut Isolate` or `&mut OwnedIsolate`). //! - A `Context` is _not_ available. Only certain types JavaScript values can //! be created: primitive values, templates, and instances of `Context`. //! - Derefs to `Isolate`. //! -//! - `ContextScope<'s>` -//! - 's = lifetime of the scope itself. -//! - This type is never constructed; it only exists in the deref chain. -//! - A `Context` is available; any type of value can be created. -//! - Derefs to `HandleScope<'s, ()>`. -//! //! - `HandleScope<'s>` //! - 's = lifetime of local handles created in this scope, and of the scope //! itself. //! - A `Context` is available; any type of value can be created. -//! - Derefs to `ContextScope<'s>` +//! - Derefs to `HandleScope<'s, ()>` //! -//! - ContextScope<'s, P: Scope> +//! - ContextScope<'s, P> //! - 's = lifetime of the scope itself. //! - A `Context` is available; any type of value can be created. //! - Derefs to `P`. @@ -54,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. @@ -68,8 +72,6 @@ use std::alloc::Layout; use std::any::type_name; use std::cell::Cell; use std::marker::PhantomData; -use std::mem::size_of; -use std::mem::take; use std::mem::MaybeUninit; use std::num::NonZeroUsize; use std::ops::Deref; @@ -90,18 +92,14 @@ use crate::Primitive; use crate::PromiseRejectMessage; use crate::Value; -pub unsafe trait Scope: Sized {} - /// Stack-allocated class which sets the execution context for all operations /// executed within a local scope. After entering a context, all code compiled /// and run is compiled and run in this context. -pub struct ContextScope<'s, P = ()> { +pub struct ContextScope<'s, P> { data: NonNull, _phantom: PhantomData<&'s mut P>, } -unsafe impl<'s, P> Scope for ContextScope<'s, P> {} - impl<'s, P: param::NewContextScope<'s>> ContextScope<'s, P> { #[allow(clippy::new_ret_no_self)] pub fn new(param: &'s mut P, context: Local) -> P::NewScope { @@ -119,61 +117,6 @@ impl<'s, P: param::NewContextScope<'s>> ContextScope<'s, P> { } } -impl<'s, P> ContextScope<'s, P> { - /// Returns the context of the currently running JavaScript, or the context - /// on the top of the stack if no JavaScript is running. - pub fn get_current_context(&self) -> Local<'s, Context> { - let context_ptr = data::ScopeData::get(self).get_current_context(); - unsafe { Local::from_raw(context_ptr) }.unwrap() - } - - /// Returns either the last context entered through V8's C++ API, or the - /// context of the currently running microtask while processing microtasks. - /// If a context is entered while executing a microtask, that context is - /// returned. - pub fn get_entered_or_microtask_context(&self) -> Local<'s, Context> { - let data = data::ScopeData::get(self); - let isolate_ptr = data.get_isolate_ptr(); - let context_ptr = - unsafe { raw::v8__Isolate__GetEnteredOrMicrotaskContext(isolate_ptr) }; - unsafe { Local::from_raw(context_ptr) }.unwrap() - } -} - -impl<'s> Deref for ContextScope<'s> { - type Target = HandleScope<'s, ()>; - - fn deref(&self) -> &Self::Target { - self.cast_ref() - } -} - -impl<'s> DerefMut for ContextScope<'s> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.cast_mut() - } -} - -impl<'s, P: Scope> Deref for ContextScope<'s, P> { - type Target = P; - - fn deref(&self) -> &Self::Target { - self.cast_ref() - } -} - -impl<'s, P: Scope> DerefMut for ContextScope<'s, P> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.cast_mut() - } -} - -impl<'s, P> Drop for ContextScope<'s, P> { - fn drop(&mut self) { - data::ScopeData::get_mut(self).notify_scope_dropped(); - } -} - /// A stack-allocated class that governs a number of local handles. /// After a handle scope has been created, all local handles will be /// allocated within that handle scope until either the handle scope is @@ -191,8 +134,6 @@ pub struct HandleScope<'s, C = Context> { _phantom: PhantomData<&'s mut C>, } -unsafe impl<'s, C> Scope for HandleScope<'s, C> {} - impl<'s> HandleScope<'s> { #[allow(clippy::new_ret_no_self)] pub fn new>(param: &'s mut P) -> P::NewScope { @@ -201,21 +142,28 @@ impl<'s> HandleScope<'s> { .new_handle_scope_data() .as_scope() } -} -impl<'s> HandleScope<'s, ()> { - pub fn get_isolate(&self) -> &Isolate { - data::ScopeData::get(self).get_isolate() - } - - pub fn get_isolate_mut(&mut self) -> &mut Isolate { - data::ScopeData::get_mut(self).get_isolate_mut() + /// Returns the context of the currently running JavaScript, or the context + /// on the top of the stack if no JavaScript is running. + pub fn get_current_context(&self) -> Local<'s, Context> { + let context_ptr = data::ScopeData::get(self).get_current_context(); + unsafe { Local::from_raw(context_ptr) }.unwrap() } - pub(crate) fn get_isolate_ptr(&self) -> *mut Isolate { - data::ScopeData::get(self).get_isolate_ptr() + /// Returns either the last context entered through V8's C++ API, or the + /// context of the currently running microtask while processing microtasks. + /// If a context is entered while executing a microtask, that context is + /// returned. + pub fn get_entered_or_microtask_context(&self) -> Local<'s, Context> { + let data = data::ScopeData::get(self); + let isolate_ptr = data.get_isolate_ptr(); + let context_ptr = + unsafe { raw::v8__Isolate__GetEnteredOrMicrotaskContext(isolate_ptr) }; + unsafe { Local::from_raw(context_ptr) }.unwrap() } +} +impl<'s> HandleScope<'s, ()> { /// Schedules an exception to be thrown when returning to JavaScript. When /// an exception has been scheduled it is illegal to invoke any /// JavaScript operation; the caller must return immediately and only @@ -241,51 +189,9 @@ impl<'s> HandleScope<'s, ()> { ) -> Option> { Local::from_raw(f(data::ScopeData::get_mut(self))) } -} - -impl<'s> AsRef for HandleScope<'s, ()> { - fn as_ref(&self) -> &Isolate { - self.get_isolate() - } -} -impl<'s> AsMut for HandleScope<'s, ()> { - fn as_mut(&mut self) -> &mut Isolate { - self.get_isolate_mut() - } -} - -impl<'s> Deref for HandleScope<'s, ()> { - type Target = Isolate; - - fn deref(&self) -> &Self::Target { - self.get_isolate() - } -} - -impl<'s> DerefMut for HandleScope<'s, ()> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.get_isolate_mut() - } -} - -impl<'s> Deref for HandleScope<'s> { - type Target = ContextScope<'s>; - - fn deref(&self) -> &Self::Target { - self.cast_ref() - } -} - -impl<'s> DerefMut for HandleScope<'s> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.cast_mut() - } -} - -impl<'s, C> Drop for HandleScope<'s, C> { - fn drop(&mut self) { - data::ScopeData::get_mut(self).notify_scope_dropped(); + pub(crate) fn get_isolate_ptr(&self) -> *mut Isolate { + data::ScopeData::get(self).get_isolate_ptr() } } @@ -302,8 +208,6 @@ pub struct EscapableHandleScope<'s, 'e: 's, C = Context> { PhantomData<(&'s mut raw::HandleScope, &'e mut raw::EscapeSlot, &'s C)>, } -unsafe impl<'s, 'e: 's, C> Scope for EscapableHandleScope<'s, 'e, C> {} - impl<'s, 'e: 's> EscapableHandleScope<'s, 'e> { #[allow(clippy::new_ret_no_self)] pub fn new>( @@ -332,23 +236,158 @@ impl<'s, 'e: 's, C> EscapableHandleScope<'s, 'e, C> { } } -impl<'s, 'e: 's, C> Deref for EscapableHandleScope<'s, 'e, C> { - type Target = HandleScope<'s, 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 deref(&self) -> &Self::Target { - self.cast_ref() + fn get_raw_mut(&mut self) -> &mut raw::TryCatch { + data::ScopeData::get_mut(self).get_try_catch_mut() } } -impl<'s, 'e: 's, C> DerefMut for EscapableHandleScope<'s, 'e, C> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.cast_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, 'e: 's, C> Drop for EscapableHandleScope<'s, 'e, C> { - fn drop(&mut self) { - data::ScopeData::get_mut(self).notify_scope_dropped(); +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(), + ) + }) + } } } @@ -377,8 +416,6 @@ pub struct CallbackScope<'s> { _phantom: PhantomData<&'s mut HandleScope<'s>>, } -unsafe impl<'s> Scope for CallbackScope<'s> {} - impl<'s> CallbackScope<'s> { pub unsafe fn new>(param: P) -> Self { data::ScopeData::get_current_mut(param.get_isolate_mut()) @@ -387,26 +424,130 @@ impl<'s> CallbackScope<'s> { } } -impl<'s> Deref for CallbackScope<'s> { - type Target = HandleScope<'s>; +macro_rules! impl_as { + // Implements `AsRef` and AsMut` on a scope type. + (<$($params:tt),+> $src_type:ty as Isolate) => { + impl<$($params),*> AsRef for $src_type { + fn as_ref(&self) -> &Isolate { + data::ScopeData::get(self).get_isolate() + } + } + + impl<$($params),*> AsMut for $src_type { + fn as_mut(&mut self) -> &mut Isolate { + data::ScopeData::get_mut(self).get_isolate_mut() + } + } + }; + + // Implements `AsRef` and `AsMut` traits for the purpose of converting a + // a scope reference to a scope reference with a different but compatible type. + (<$($params:tt),+> $src_type:ty as $tgt_type:ty) => { + impl<$($params),*> AsRef<$tgt_type> for $src_type { + fn as_ref(&self) -> &$tgt_type { + self.cast_ref() + } + } - fn deref(&self) -> &Self::Target { - self.cast_ref() - } + impl<$($params),*> AsMut< $tgt_type> for $src_type { + fn as_mut(&mut self) -> &mut $tgt_type { + self.cast_mut() + } + } + }; } -impl<'s> DerefMut for CallbackScope<'s> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.cast_mut() - } +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) => { + impl<$($params),*> Deref for $src_type { + type Target = $tgt_type; + fn deref(&self) -> &Self::Target { + self.as_ref() + } + } + + impl<$($params),*> DerefMut for $src_type { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } + } + }; } -impl<'s> Drop for CallbackScope<'s> { - fn drop(&mut self) { - data::ScopeData::get_mut(self).notify_scope_dropped(); - } +impl_deref!(<'s, 'p> ContextScope<'s, HandleScope<'p>> as HandleScope<'p>); +impl_deref!(<'s, 'p, 'e> ContextScope<'s, EscapableHandleScope<'p, 'e>> as EscapableHandleScope<'p, 'e>); + +impl_deref!(<'s> HandleScope<'s,()> as Isolate); +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 { + (<$($params:tt),+> $type:ty) => { + unsafe impl<$($params),*> Scope for $type {} + + impl<$($params),*> Drop for $type { + fn drop(&mut self) { + data::ScopeData::get_mut(self).notify_scope_dropped(); + } + } + }; } +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 {} + trait ScopeCast: Sized { fn cast_ref(&self) -> &S; fn cast_mut(&mut self) -> &mut S; @@ -458,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>>; } @@ -490,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>; } @@ -514,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 @@ -593,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, } @@ -705,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>, @@ -731,7 +933,14 @@ pub(crate) mod data { let escape_slot = self.escape_slot; // Initialize the `struct ScopeData` for the new scope. let new_scope_data = self.allocate_or_reuse_scope_data(); - new_scope_data.status = ScopeStatus::Current { zombie: true }.into(); + // In debug builds, `zombie` is initially set to `true`, and the flag is + // later cleared in the `as_scope()` method, to verify that we're + // always creating exactly one scope from any `ScopeData` object. + // For performance reasons this check is not performed in release builds. + new_scope_data.status = Cell::new(ScopeStatus::Current { + zombie: cfg!(debug_assertions), + }); + // Store fields inherited from the parent scope. new_scope_data.context = context; new_scope_data.escape_slot = escape_slot; (init_fn)(new_scope_data); @@ -743,12 +952,13 @@ pub(crate) mod data { new_scope_data } - /// Either returns a free `Box` that is available for reuse, + /// or allocates a new one on the heap. fn allocate_or_reuse_scope_data(&mut self) -> &mut Self { let self_nn = NonNull::new(self); match &mut self.next { Some(next_box) => { - // Recycle an unused `Box` allocation. + // Reuse a free `Box` allocation. debug_assert_eq!(next_box.isolate, self.isolate); debug_assert_eq!(next_box.previous, self_nn); debug_assert_eq!(next_box.status.get(), ScopeStatus::Free); @@ -766,13 +976,14 @@ pub(crate) mod data { } pub(super) fn as_scope(&mut self) -> S { - self.status.set(match self.status.get() { - ScopeStatus::Current { zombie: true } => { - ScopeStatus::Current { zombie: false } - } - _ => unreachable!(), - }); - assert_eq!(size_of::<&mut Self>(), size_of::()); + assert_eq!(Layout::new::<&mut Self>(), Layout::new::()); + // In debug builds, a new initialized `ScopeStatus` will have the `zombie` + // flag set, so we have to reset it. In release builds, new `ScopeStatus` + // objects come with the `zombie` flag cleared, so no update is necessary. + if cfg!(debug_assertions) { + assert_eq!(self.status.get(), ScopeStatus::Current { zombie: true }); + self.status.set(ScopeStatus::Current { zombie: false }); + } let self_nn = NonNull::from(self); unsafe { ptr::read(&self_nn as *const _ as *const S) } } @@ -826,7 +1037,7 @@ pub(crate) mod data { fn exit_scope(&mut self) -> &mut Self { // Clear out the scope type specific data field. None of the other fields // have a destructor, and there's no need to do any cleanup on them. - take(&mut self.scope_type_specific_data); + self.scope_type_specific_data = Default::default(); // Change the ScopeData's status field from 'Current' to 'Free', which // means that it is not associated with a scope and can be reused. self.status.set(ScopeStatus::Free); @@ -929,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 @@ -947,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(), }, ); @@ -968,7 +1196,6 @@ pub(crate) mod data { } } - #[allow(clippy::enum_variant_names)] enum ScopeTypeSpecificData { None, ContextScope { @@ -981,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 { @@ -1075,7 +1305,7 @@ mod raw { } /// This function is marked unsafe because `init()` must be called exactly - /// once, no more and no less, after constructing a HandleScope with + /// once, no more and no less, after creating a `HandleScope` value with /// `HandleScope::uninit()`. pub unsafe fn init(&mut self, isolate: NonNull) { let buf = NonNull::from(self).cast(); @@ -1118,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, @@ -1151,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; @@ -1169,21 +1454,20 @@ mod tests { use crate::new_default_platform; use crate::V8; use std::any::type_name; - use std::borrow::Borrow; use std::sync::Once; - /// `AssertTypeOf` facilitates comparing types. This is done partially at - /// compile-type (with the `Borrow` constraits) and partially at runtime - /// (by comparing type names). The main difference with assigning a value - /// to a variable with an explicitly stated type is that the latter allows - /// coercions and dereferencing to change the type, whereas `AssertTypeOf` - /// does not allow that to happen. + trait SameType {} + impl SameType for (A, A) {} + + /// `AssertTypeOf` facilitates comparing types. The important difference with + /// assigning a value to a variable with an explicitly stated type is that the + /// latter allows coercions and dereferencing to change the type, whereas + /// `AssertTypeOf` requires the compared types to match exactly. struct AssertTypeOf<'a, T>(pub &'a T); impl<'a, T> AssertTypeOf<'a, T> { pub fn is(self) where - A: Borrow, - T: Borrow, + (A, T): SameType, { assert_eq!(type_name::(), type_name::()); } @@ -1201,48 +1485,91 @@ mod tests { fn deref_types() { initialize_v8(); let isolate = &mut Isolate::new(Default::default()); - let hs = &mut HandleScope::new(isolate); - AssertTypeOf(hs).is::>(); - let context = Context::new(hs); + AssertTypeOf(isolate).is::(); + let l1_hs = &mut HandleScope::new(isolate); + AssertTypeOf(l1_hs).is::>(); + let context = Context::new(l1_hs); + { + let l2_cxs = &mut ContextScope::new(l1_hs, context); + AssertTypeOf(l2_cxs).is::>(); + { + let d = l2_cxs.deref_mut(); + AssertTypeOf(d).is::(); + let d = d.deref_mut(); + AssertTypeOf(d).is::>(); + let d = d.deref_mut(); + AssertTypeOf(d).is::(); + } + { + 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 ehs = &mut EscapableHandleScope::new(hs); - AssertTypeOf(ehs).is::>(); - let d = ehs.deref_mut(); + 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 cs1 = &mut ContextScope::new(hs, context); - AssertTypeOf(cs1).is::>(); - let d = cs1.deref_mut(); - AssertTypeOf(d).is::(); - let ehs = &mut EscapableHandleScope::new(cs1); - AssertTypeOf(ehs).is::(); - let cs2 = &mut ContextScope::new(ehs, context); - AssertTypeOf(cs2).is::>(); - let d = cs2.deref_mut(); - AssertTypeOf(d).is::(); - let d = d.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 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::(); } { - // CallbackScope is not used as intended here. Push a ContextScope onto - // the stack so that its assumptions aren't violated. - let _ = ContextScope::new(hs, context); - let cbs = &mut unsafe { CallbackScope::new(context) }; - AssertTypeOf(cbs).is::(); - let d = cbs.deref_mut(); + // `CallbackScope` is meant to be used inside V8 API callback functions + // only. It assumes that a `HandleScope` already exists on the stack, and + // that a context has been entered. Push a `ContextScope` onto the stack + // to also meet the second expectation. + let _ = ContextScope::new(l1_hs, context); + let l2_cbs = &mut unsafe { CallbackScope::new(context) }; + AssertTypeOf(l2_cbs).is::(); + let d = l2_cbs.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::(); @@ -1253,78 +1580,105 @@ mod tests { fn new_scope_types() { initialize_v8(); let isolate = &mut Isolate::new(Default::default()); - let l0_hs = &mut HandleScope::new(isolate); - AssertTypeOf(l0_hs).is::>(); - let context = Context::new(l0_hs); + AssertTypeOf(isolate).is::(); + let l1_hs = &mut HandleScope::new(isolate); + AssertTypeOf(l1_hs).is::>(); + let context = Context::new(l1_hs); + AssertTypeOf(&HandleScope::new(l1_hs)).is::>(); { - let l1_cs = &mut ContextScope::new(l0_hs, context); - AssertTypeOf(l1_cs).is::>(); - AssertTypeOf(&ContextScope::new(l1_cs, context)) + let l2_cxs = &mut ContextScope::new(l1_hs, context); + AssertTypeOf(l2_cxs).is::>(); + AssertTypeOf(&ContextScope::new(l2_cxs, context)) .is::>(); + 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); + AssertTypeOf(l2_ehs).is::>(); + AssertTypeOf(&HandleScope::new(l2_ehs)).is::>(); + AssertTypeOf(&EscapableHandleScope::new(l2_ehs)) + .is::>(); { - let l2_hs = &mut HandleScope::new(l1_cs); - AssertTypeOf(l2_hs).is::(); - AssertTypeOf(&EscapableHandleScope::new(l2_hs)) + let l3_cxs = &mut ContextScope::new(l2_ehs, context); + AssertTypeOf(l3_cxs).is::>(); + AssertTypeOf(&ContextScope::new(l3_cxs, context)) + .is::>(); + 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 l2_ehs = &mut EscapableHandleScope::new(l1_cs); - AssertTypeOf(l2_ehs).is::(); - AssertTypeOf(&ContextScope::new(l2_ehs, context)) + let l3_tc = &mut TryCatch::new(l2_ehs); + AssertTypeOf(l3_tc).is::>>(); + AssertTypeOf(&ContextScope::new(l3_tc, context)) .is::>(); - AssertTypeOf(&HandleScope::new(l2_ehs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l2_ehs)) - .is::(); + AssertTypeOf(&HandleScope::new(l3_tc)).is::>(); + AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + .is::>(); + AssertTypeOf(&TryCatch::new(l3_tc)) + .is::>>(); } } { - let l1_hs = &mut HandleScope::new(l0_hs); - AssertTypeOf(l1_hs).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) }; + AssertTypeOf(l2_cbs).is::(); + AssertTypeOf(&ContextScope::new(l2_cbs, context)) + .is::>(); { - let l2_cs = &mut ContextScope::new(l1_hs, context); - AssertTypeOf(l2_cs).is::>(); - AssertTypeOf(&EscapableHandleScope::new(l2_cs)) + let l3_hs = &mut HandleScope::new(l2_cbs); + AssertTypeOf(l3_hs).is::(); + AssertTypeOf(&ContextScope::new(l3_hs, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_hs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_hs)) .is::(); + AssertTypeOf(&TryCatch::new(l3_hs)).is::>(); } { - let l2_ehs = &mut EscapableHandleScope::new(l1_hs); - AssertTypeOf(l2_ehs).is::>(); - AssertTypeOf(&ContextScope::new(l2_ehs, context)) - .is::>(); - } - } - { - let l1_ehs = &mut EscapableHandleScope::new(l0_hs); - AssertTypeOf(l1_ehs).is::>(); - { - let l2_cs = &mut ContextScope::new(l1_ehs, context); - AssertTypeOf(l2_cs).is::>(); - AssertTypeOf(&ContextScope::new(l2_cs, context)) + let l3_ehs = &mut EscapableHandleScope::new(l2_cbs); + AssertTypeOf(l3_ehs).is::(); + AssertTypeOf(&ContextScope::new(l3_ehs, context)) .is::>(); - AssertTypeOf(&HandleScope::new(l2_cs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l2_cs)) + AssertTypeOf(&HandleScope::new(l3_ehs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_ehs)) .is::(); + AssertTypeOf(&TryCatch::new(l3_ehs)) + .is::>(); } { - let l2_hs = &mut HandleScope::new(l1_ehs); - AssertTypeOf(l2_hs).is::>(); - AssertTypeOf(&ContextScope::new(l2_hs, context)) - .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::>(); } - AssertTypeOf(&EscapableHandleScope::new(l1_ehs)) - .is::>(); - } - { - // CallbackScope is not used as intended here. Push a ContextScope onto - // the stack so that its assumptions aren't violated. - let _ = ContextScope::new(l0_hs, context); - let l0_cbs = &mut unsafe { CallbackScope::new(context) }; - AssertTypeOf(l0_cbs).is::(); - AssertTypeOf(&ContextScope::new(l0_cbs, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l0_cbs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l0_cbs)) - .is::(); } } } diff --git a/src/try_catch.rs b/src/try_catch.rs deleted file mode 100644 index 5aaec22242..0000000000 --- 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 bc5b8c4940..5056075785 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 d583eeaeea..664f164e52 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 a59227e188..98172495f4 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 411142ad57..e38d693242 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 b7b15c20eb..313e3504c9 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -549,48 +549,45 @@ 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 try_catch = &mut v8::TryCatch::new(scope); + let result = eval(try_catch, 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!(try_catch.has_caught()); + assert!(try_catch.exception().is_some()); + assert!(try_catch.stack_trace().is_some()); + assert!(try_catch.message().is_some()); assert_eq!( - tc.message(scope) + try_catch + .message() .unwrap() - .get(scope) - .to_rust_string_lossy(scope), + .get(try_catch) + .to_rust_string_lossy(try_catch), "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 try_catch = &mut v8::TryCatch::new(scope); + let result = eval(try_catch, 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.rethrow().is_none()); + assert!(!try_catch.has_caught()); + assert!(try_catch.exception().is_none()); + assert!(try_catch.stack_trace().is_none()); + assert!(try_catch.message().is_none()); + assert!(try_catch.rethrow().is_none()); }; { // Rethrow and reset. - let mut try_catch_1 = v8::TryCatch::new(scope); - let tc1 = try_catch_1.enter(); + let try_catch_1 = &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'"); - assert!(tc2.has_caught()); - assert!(tc2.rethrow().is_some()); - tc2.reset(); - assert!(!tc2.has_caught()); + let try_catch_2 = &mut v8::TryCatch::new(try_catch_1); + eval(try_catch_2, context, "throw 'bar'"); + assert!(try_catch_2.has_caught()); + assert!(try_catch_2.rethrow().is_some()); + try_catch_2.reset(); + assert!(!try_catch_2.has_caught()); } - assert!(tc1.has_caught()); + assert!(try_catch_1.has_caught()); }; } } @@ -603,15 +600,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 try_catch = &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(try_catch, "DANG!").unwrap(); + let exc = v8::Exception::type_error(try_catch, msg); + try_catch.throw_exception(exc); // Catch exception. - let caught_exc = try_catch.exception(scope).unwrap(); - let caught_msg = try_catch.message(scope).unwrap(); + let caught_exc = try_catch.exception().unwrap(); + let caught_msg = try_catch.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 +633,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()); - assert!(tc.has_caught()); - assert!(tc - .exception(scope) + let try_catch = &mut v8::TryCatch::new(scope); + let exception = v8::String::new(try_catch, "boom").unwrap(); + try_catch.throw_exception(exception.into()); + assert!(try_catch.has_caught()); + assert!(try_catch + .exception() .unwrap() - .strict_equals(v8::String::new(scope, "boom").unwrap().into())); + .strict_equals(v8::String::new(try_catch, "boom").unwrap().into())); }; } } @@ -1593,8 +1588,7 @@ fn module_instantiation_failures1() { // Instantiation should fail. { - let mut try_catch = v8::TryCatch::new(scope); - let tc = try_catch.enter(); + let try_catch = &mut v8::TryCatch::new(scope); fn resolve_callback<'a>( context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, @@ -1608,11 +1602,11 @@ fn module_instantiation_failures1() { } let result = module.instantiate_module(context, resolve_callback); assert!(result.is_none()); - assert!(tc.has_caught()); - assert!(tc - .exception(scope) + assert!(try_catch.has_caught()); + assert!(try_catch + .exception() .unwrap() - .strict_equals(v8::String::new(scope, "boom").unwrap().into())); + .strict_equals(v8::String::new(try_catch, "boom").unwrap().into())); assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status()); } }