mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-26 00:59:28 -05:00
Add TryCatch (#97)
This commit is contained in:
parent
331582561b
commit
f839aa221a
5 changed files with 364 additions and 0 deletions
|
@ -27,6 +27,9 @@ static_assert(sizeof(v8::ScriptCompiler::Source) == sizeof(size_t) * 8,
|
|||
static_assert(sizeof(v8::ReturnValue<v8::Value>) == sizeof(size_t) * 1,
|
||||
"ReturnValue size mismatch");
|
||||
|
||||
static_assert(sizeof(v8::TryCatch) == sizeof(size_t) * 6,
|
||||
"TryCatch size mismatch");
|
||||
|
||||
extern "C" {
|
||||
|
||||
void v8__V8__SetFlagsFromCommandLine(int* argc, char** argv) {
|
||||
|
@ -325,6 +328,56 @@ int v8__StackTrace__GetFrameCount(v8::StackTrace* self) {
|
|||
return self->GetFrameCount();
|
||||
}
|
||||
|
||||
void v8__TryCatch__CONSTRUCT(uninit_t<v8::TryCatch>& buf,
|
||||
v8::Isolate* isolate) {
|
||||
construct_in_place<v8::TryCatch>(buf, isolate);
|
||||
}
|
||||
|
||||
void v8__TryCatch__DESTRUCT(v8::TryCatch& self) { self.~TryCatch(); }
|
||||
|
||||
bool v8__TryCatch__HasCaught(const v8::TryCatch& self) {
|
||||
return self.HasCaught();
|
||||
}
|
||||
|
||||
bool v8__TryCatch__CanContinue(const v8::TryCatch& self) {
|
||||
return self.CanContinue();
|
||||
}
|
||||
|
||||
bool v8__TryCatch__HasTerminated(const v8::TryCatch& self) {
|
||||
return self.HasTerminated();
|
||||
}
|
||||
|
||||
v8::Value* v8__TryCatch__Exception(const v8::TryCatch& self) {
|
||||
return local_to_ptr(self.Exception());
|
||||
}
|
||||
|
||||
v8::Value* v8__TryCatch__StackTrace(const v8::TryCatch& self,
|
||||
v8::Local<v8::Context> context) {
|
||||
return maybe_local_to_ptr(self.StackTrace(context));
|
||||
}
|
||||
|
||||
v8::Message* v8__TryCatch__Message(const v8::TryCatch& self) {
|
||||
return local_to_ptr(self.Message());
|
||||
}
|
||||
|
||||
void v8__TryCatch__Reset(v8::TryCatch& self) { self.Reset(); }
|
||||
|
||||
v8::Value* v8__TryCatch__ReThrow(v8::TryCatch& self) {
|
||||
return local_to_ptr(self.ReThrow());
|
||||
}
|
||||
|
||||
bool v8__TryCatch__IsVerbose(const v8::TryCatch& self) {
|
||||
return self.IsVerbose();
|
||||
}
|
||||
|
||||
void v8__TryCatch__SetVerbose(v8::TryCatch& self, bool value) {
|
||||
self.SetVerbose(value);
|
||||
}
|
||||
|
||||
void v8__TryCatch__SetCaptureMessage(v8::TryCatch& self, bool value) {
|
||||
self.SetCaptureMessage(value);
|
||||
}
|
||||
|
||||
v8::Script* v8__Script__Compile(v8::Context* context, v8::String* source,
|
||||
v8::ScriptOrigin* origin) {
|
||||
return maybe_local_to_ptr(
|
||||
|
|
|
@ -31,6 +31,18 @@ impl<'sc> HandleScope<'sc> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'sc> AsRef<HandleScope<'sc>> for HandleScope<'sc> {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'sc> AsMut<HandleScope<'sc>> for HandleScope<'sc> {
|
||||
fn as_mut(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'sc> AsRef<Isolate> for HandleScope<'sc> {
|
||||
fn as_ref(&self) -> &Isolate {
|
||||
unsafe { v8__HandleScope__GetIsolate(self) }
|
||||
|
|
|
@ -25,6 +25,7 @@ mod property;
|
|||
mod script;
|
||||
mod string;
|
||||
mod support;
|
||||
mod try_catch;
|
||||
mod value;
|
||||
|
||||
pub mod array_buffer;
|
||||
|
@ -59,4 +60,5 @@ pub use property::PropertyCallbackInfo;
|
|||
pub use script::{Script, ScriptOrigin};
|
||||
pub use string::NewStringType;
|
||||
pub use string::String;
|
||||
pub use try_catch::TryCatch;
|
||||
pub use value::Value;
|
||||
|
|
228
src/try_catch.rs
Normal file
228
src/try_catch.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
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<CxxTryCatch>,
|
||||
isolate: *mut Isolate,
|
||||
);
|
||||
|
||||
fn v8__TryCatch__DESTRUCT(this: &mut CxxTryCatch);
|
||||
|
||||
fn v8__TryCatch__HasCaught(this: &CxxTryCatch) -> bool;
|
||||
|
||||
fn v8__TryCatch__CanContinue(this: &CxxTryCatch) -> bool;
|
||||
|
||||
fn v8__TryCatch__HasTerminated(this: &CxxTryCatch) -> bool;
|
||||
|
||||
fn v8__TryCatch__Exception(this: &CxxTryCatch) -> *mut Value;
|
||||
|
||||
fn v8__TryCatch__StackTrace(
|
||||
this: &CxxTryCatch,
|
||||
context: Local<Context>,
|
||||
) -> *mut Value;
|
||||
|
||||
fn v8__TryCatch__Message(this: &CxxTryCatch) -> *mut Message;
|
||||
|
||||
fn v8__TryCatch__Reset(this: &mut CxxTryCatch);
|
||||
|
||||
fn v8__TryCatch__ReThrow(this: &mut CxxTryCatch) -> *mut Value;
|
||||
|
||||
fn v8__TryCatch__IsVerbose(this: &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.
|
||||
pub struct TryCatch<'tc>(TryCatchState<'tc>);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct TryCatchScope<'tc>(CxxTryCatch, PhantomData<&'tc ()>);
|
||||
|
||||
#[repr(C)]
|
||||
struct CxxTryCatch([usize; 6]);
|
||||
|
||||
enum TryCatchState<'tc> {
|
||||
New { isolate: *mut Isolate },
|
||||
Uninit(MaybeUninit<TryCatchScope<'tc>>),
|
||||
Constructed(TryCatchScope<'tc>),
|
||||
}
|
||||
|
||||
impl<'tc> TryCatch<'tc> {
|
||||
/// Creates a new try/catch block and registers it with v8. Note that
|
||||
/// all TryCatch blocks should be stack allocated because the memory
|
||||
/// location itself is compared against JavaScript try/catch blocks.
|
||||
pub fn new(scope: &mut impl AsMut<Isolate>) -> Self {
|
||||
Self(TryCatchState::New {
|
||||
isolate: scope.as_mut(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enter(&'tc mut self) -> &'tc mut TryCatchScope {
|
||||
use TryCatchState::*;
|
||||
let state = &mut self.0;
|
||||
|
||||
let isolate = match take(state) {
|
||||
New { isolate } => isolate,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let buf = match state {
|
||||
Uninit(b) => b,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
TryCatchScope::construct(buf, isolate);
|
||||
|
||||
*state = match take(state) {
|
||||
Uninit(b) => Constructed(unsafe { b.assume_init() }),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match state {
|
||||
Constructed(v) => v,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tc> TryCatchScope<'tc> {
|
||||
/// 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.
|
||||
///
|
||||
/// The returned handle is valid until this TryCatch block has been destroyed.
|
||||
pub fn exception(&self) -> Option<Local<'tc, Value>> {
|
||||
unsafe { Local::from_raw(v8__TryCatch__Exception(&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<'sc>(
|
||||
&self,
|
||||
_scope: &mut impl AsMut<HandleScope<'sc>>,
|
||||
context: Local<Context>,
|
||||
) -> Option<Local<'sc, Value>> {
|
||||
unsafe { Local::from_raw(v8__TryCatch__StackTrace(&self.0, context)) }
|
||||
}
|
||||
|
||||
/// Returns the message associated with this exception. If there is
|
||||
/// no message associated an empty handle is returned.
|
||||
///
|
||||
/// The returned handle is valid until this TryCatch block has been
|
||||
/// destroyed.
|
||||
pub fn message(&self) -> Option<Local<'tc, Message>>
|
||||
where
|
||||
Self: 'tc,
|
||||
{
|
||||
unsafe { Local::from_raw(v8__TryCatch__Message(&self.0)) }
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn rethrow<'a>(&'_ mut self) -> Option<Local<'a, Value>> {
|
||||
unsafe { Local::from_raw(v8__TryCatch__ReThrow(&mut self.0)) }
|
||||
}
|
||||
|
||||
/// 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<TryCatchScope>, isolate: *mut Isolate) {
|
||||
unsafe {
|
||||
assert_eq!(size_of_val(buf), size_of::<CxxTryCatch>());
|
||||
let buf = &mut *(buf as *mut _ as *mut MaybeUninit<CxxTryCatch>);
|
||||
v8__TryCatch__CONSTRUCT(buf, isolate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CxxTryCatch {
|
||||
fn drop(&mut self) {
|
||||
unsafe { v8__TryCatch__DESTRUCT(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tc> Default for TryCatchState<'tc> {
|
||||
fn default() -> Self {
|
||||
Self::Uninit(MaybeUninit::uninit())
|
||||
}
|
||||
}
|
|
@ -108,6 +108,75 @@ fn v8_str<'sc>(
|
|||
v8::String::new(scope, s, v8::NewStringType::Normal).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_catch() {
|
||||
fn eval<'sc>(
|
||||
scope: &mut HandleScope<'sc>,
|
||||
context: Local<v8::Context>,
|
||||
code: &'static str,
|
||||
) -> Option<Local<'sc, v8::Value>> {
|
||||
let source = v8_str(scope, code);
|
||||
let mut script =
|
||||
v8::Script::compile(&mut *scope, context, source, None).unwrap();
|
||||
script.run(scope, context)
|
||||
};
|
||||
|
||||
let _g = setup();
|
||||
let mut params = v8::Isolate::create_params();
|
||||
params.set_array_buffer_allocator(
|
||||
v8::array_buffer::Allocator::new_default_allocator(),
|
||||
);
|
||||
let isolate = v8::Isolate::new(params);
|
||||
let _locker = v8::Locker::new(&isolate);
|
||||
v8::HandleScope::enter(&isolate, |scope| {
|
||||
let mut context = v8::Context::new(scope);
|
||||
context.enter();
|
||||
{
|
||||
// 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')");
|
||||
assert!(result.is_none());
|
||||
assert!(tc.has_caught());
|
||||
assert!(tc.exception().is_some());
|
||||
assert!(tc.stack_trace(scope, context).is_some());
|
||||
assert!(tc.message().is_some());
|
||||
assert_eq!(
|
||||
tc.message().unwrap().get(scope).to_rust_string_lossy(scope),
|
||||
"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");
|
||||
assert!(result.is_some());
|
||||
assert!(!tc.has_caught());
|
||||
assert!(tc.exception().is_none());
|
||||
assert!(tc.stack_trace(scope, context).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 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());
|
||||
}
|
||||
assert!(tc1.has_caught());
|
||||
};
|
||||
context.exit();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolate_add_message_listener() {
|
||||
let g = setup();
|
||||
|
|
Loading…
Reference in a new issue