mirror of
https://github.com/rubyjs/mini_racer
synced 2023-03-27 23:21:28 -04:00
FIX: rethrow ruby exceptions that you catch from js callbacks
This commit is contained in:
parent
8568f6ed48
commit
5440e3f625
5 changed files with 50 additions and 36 deletions
23
README.md
23
README.md
|
@ -4,7 +4,7 @@ Minimal, modern embedded V8 for Ruby.
|
||||||
|
|
||||||
MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and Ruby.
|
MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and Ruby.
|
||||||
|
|
||||||
It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini_racer only implements a minimal (yet complete) bridge. This reduces the surface area making upgrading v8 much simpler and exahustive testing simpler.
|
It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exahustive testing simpler.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -100,57 +100,36 @@ Or install it yourself as:
|
||||||
###therubyracer
|
###therubyracer
|
||||||
|
|
||||||
- https://github.com/cowboyd/therubyracer
|
- https://github.com/cowboyd/therubyracer
|
||||||
|
|
||||||
- Most comprehensive bridge available
|
- Most comprehensive bridge available
|
||||||
|
|
||||||
- Provides the ability to "eval" JavaScript
|
- Provides the ability to "eval" JavaScript
|
||||||
|
|
||||||
- Provides the ability to invoke Ruby code from JavaScript
|
- Provides the ability to invoke Ruby code from JavaScript
|
||||||
|
|
||||||
- Hold refrences to JavaScript objects and methods in your Ruby code
|
- Hold refrences to JavaScript objects and methods in your Ruby code
|
||||||
|
|
||||||
- Hold refrences to Ruby objects and methods in JavaScript code
|
- Hold refrences to Ruby objects and methods in JavaScript code
|
||||||
|
|
||||||
- Uses libv8, so installation is fast
|
- Uses libv8, so installation is fast
|
||||||
|
|
||||||
- Supports timeouts for JavaScript execution
|
- Supports timeouts for JavaScript execution
|
||||||
|
|
||||||
- Does not release global interpreter lock, so performance is constrained to a single thread
|
- Does not release global interpreter lock, so performance is constrained to a single thread
|
||||||
|
|
||||||
- Currently (May 2016) only supports v8 version 3.16.14 (Released approx November 2013), plans to upgrade by July 2016
|
- Currently (May 2016) only supports v8 version 3.16.14 (Released approx November 2013), plans to upgrade by July 2016
|
||||||
|
|
||||||
|
|
||||||
###v8eval
|
###v8eval
|
||||||
|
|
||||||
- https://github.com/sony/v8eval
|
- https://github.com/sony/v8eval
|
||||||
|
|
||||||
- Provides the ability to "eval" JavaScript using the latest V8 engine
|
- Provides the ability to "eval" JavaScript using the latest V8 engine
|
||||||
|
|
||||||
- Does not depend on the [libv8](https://github.com/cowboyd/libv8) gem, installation can take 10-20 mins as V8 needs to be downloaded and compiled.
|
- Does not depend on the [libv8](https://github.com/cowboyd/libv8) gem, installation can take 10-20 mins as V8 needs to be downloaded and compiled.
|
||||||
|
|
||||||
- Does not release global interpreter lock when executing JavaScript
|
- Does not release global interpreter lock when executing JavaScript
|
||||||
|
|
||||||
- Does not allow you to invoke Ruby code from JavaScript
|
- Does not allow you to invoke Ruby code from JavaScript
|
||||||
|
|
||||||
- Multi runtime support due to SWIG based bindings
|
- Multi runtime support due to SWIG based bindings
|
||||||
|
|
||||||
- Supports a JavaScript debugger
|
- Supports a JavaScript debugger
|
||||||
|
|
||||||
- Does not support timeouts for JavaScript execution
|
- Does not support timeouts for JavaScript execution
|
||||||
|
|
||||||
|
|
||||||
###therubyrhino
|
###therubyrhino
|
||||||
|
|
||||||
- https://github.com/cowboyd/therubyrhino
|
- https://github.com/cowboyd/therubyrhino
|
||||||
|
|
||||||
- API compatible with therubyracer
|
- API compatible with therubyracer
|
||||||
|
|
||||||
- Uses Mozilla's Rhino engine https://github.com/mozilla/rhino
|
- Uses Mozilla's Rhino engine https://github.com/mozilla/rhino
|
||||||
|
|
||||||
- Requires JRuby
|
- Requires JRuby
|
||||||
|
|
||||||
- Support for timeouts for JavaScript execution
|
- Support for timeouts for JavaScript execution
|
||||||
|
|
||||||
- Concurrent cause .... JRuby
|
- Concurrent cause .... JRuby
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ LIBV8_COMPATIBILITY = '~> 5.0.71.35.0'
|
||||||
#
|
#
|
||||||
# Libv8.configure_makefile
|
# Libv8.configure_makefile
|
||||||
|
|
||||||
#NODE_PATH = "/home/sam/Source/libv8"
|
NODE_PATH = "/home/sam/Source/libv8"
|
||||||
NODE_PATH = "/Users/sam/Source/libv8"
|
#NODE_PATH = "/Users/sam/Source/libv8"
|
||||||
#
|
#
|
||||||
NODE_LIBS = NODE_PATH + "/vendor/v8/out/x64.release"
|
NODE_LIBS = NODE_PATH + "/vendor/v8/out/x64.release"
|
||||||
NODE_INCLUDE = NODE_PATH + "/vendor/v8/include"
|
NODE_INCLUDE = NODE_PATH + "/vendor/v8/include"
|
||||||
|
|
|
@ -240,6 +240,9 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eval_result.executed) {
|
if (!eval_result.executed) {
|
||||||
|
|
||||||
|
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
||||||
|
if (ruby_exception == Qnil) {
|
||||||
// exception report about what happened
|
// exception report about what happened
|
||||||
if(TYPE(message) == T_STRING && TYPE(backtrace) == T_STRING) {
|
if(TYPE(message) == T_STRING && TYPE(backtrace) == T_STRING) {
|
||||||
rb_raise(rb_eJavaScriptError, "%s/n%s", RSTRING_PTR(message), RSTRING_PTR(backtrace));
|
rb_raise(rb_eJavaScriptError, "%s/n%s", RSTRING_PTR(message), RSTRING_PTR(backtrace));
|
||||||
|
@ -248,6 +251,9 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
|
||||||
} else {
|
} else {
|
||||||
rb_raise(rb_eJavaScriptError, "Unknown JavaScript Error during execution");
|
rb_raise(rb_eJavaScriptError, "Unknown JavaScript Error during execution");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
rb_raise(CLASS_OF(ruby_exception), RSTRING_PTR(rb_funcall(ruby_exception, rb_intern("to_s"), 0)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New scope for return value
|
// New scope for return value
|
||||||
|
@ -270,8 +276,10 @@ typedef struct {
|
||||||
VALUE callback;
|
VALUE callback;
|
||||||
int length;
|
int length;
|
||||||
VALUE* args;
|
VALUE* args;
|
||||||
|
bool failed;
|
||||||
} protected_callback_data;
|
} protected_callback_data;
|
||||||
|
|
||||||
|
static
|
||||||
VALUE protected_callback(VALUE rdata) {
|
VALUE protected_callback(VALUE rdata) {
|
||||||
protected_callback_data* data = (protected_callback_data*)rdata;
|
protected_callback_data* data = (protected_callback_data*)rdata;
|
||||||
VALUE result;
|
VALUE result;
|
||||||
|
@ -284,6 +292,13 @@ VALUE protected_callback(VALUE rdata) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
VALUE rescue_callback(VALUE rdata, VALUE exception) {
|
||||||
|
protected_callback_data* data = (protected_callback_data*)rdata;
|
||||||
|
data->failed = true;
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
void*
|
void*
|
||||||
gvl_ruby_callback(void* data) {
|
gvl_ruby_callback(void* data) {
|
||||||
|
|
||||||
|
@ -292,13 +307,15 @@ gvl_ruby_callback(void* data) {
|
||||||
int length = args->Length();
|
int length = args->Length();
|
||||||
VALUE callback;
|
VALUE callback;
|
||||||
VALUE result;
|
VALUE result;
|
||||||
|
VALUE self;
|
||||||
|
|
||||||
{
|
{
|
||||||
HandleScope scope(args->GetIsolate());
|
HandleScope scope(args->GetIsolate());
|
||||||
Handle<External> external = Handle<External>::Cast(args->Data());
|
Handle<External> external = Handle<External>::Cast(args->Data());
|
||||||
|
|
||||||
VALUE* self_pointer = (VALUE*)(external->Value());
|
VALUE* self_pointer = (VALUE*)(external->Value());
|
||||||
callback = rb_iv_get(*self_pointer, "@callback");
|
self = *self_pointer;
|
||||||
|
callback = rb_iv_get(self, "@callback");
|
||||||
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
ruby_args = ALLOC_N(VALUE, length);
|
ruby_args = ALLOC_N(VALUE, length);
|
||||||
|
@ -312,15 +329,18 @@ gvl_ruby_callback(void* data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// may raise exception stay clear of handle scope
|
// may raise exception stay clear of handle scope
|
||||||
int state = 0;
|
|
||||||
protected_callback_data callback_data;
|
protected_callback_data callback_data;
|
||||||
callback_data.length = length;
|
callback_data.length = length;
|
||||||
callback_data.callback = callback;
|
callback_data.callback = callback;
|
||||||
callback_data.args = ruby_args;
|
callback_data.args = ruby_args;
|
||||||
|
callback_data.failed = false;
|
||||||
|
|
||||||
result = rb_protect(protected_callback, (VALUE)(&callback_data), &state);
|
result = rb_rescue(protected_callback, (VALUE)(&callback_data),
|
||||||
|
rescue_callback, (VALUE)(&callback_data));
|
||||||
|
|
||||||
if(state) {
|
if(callback_data.failed) {
|
||||||
|
VALUE parent = rb_iv_get(self, "@parent");
|
||||||
|
rb_iv_set(parent, "@current_exception", result);
|
||||||
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
|
args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -48,6 +48,7 @@ module MiniRacer
|
||||||
|
|
||||||
def eval(str)
|
def eval(str)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
|
@current_exception = nil
|
||||||
eval_unsafe(str)
|
eval_unsafe(str)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,11 +105,25 @@ class MiniRacerTest < Minitest::Test
|
||||||
assert_equal 10, context.eval("counter")
|
assert_equal 10, context.eval("counter")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class FooError < StandardError
|
||||||
|
def initialize(message)
|
||||||
|
super(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_attached_exceptions
|
def test_attached_exceptions
|
||||||
context = MiniRacer::Context.new
|
context = MiniRacer::Context.new
|
||||||
context.attach("adder", proc{raise StandardError})
|
context.attach("adder", proc{ raise FooError, "I like foos" })
|
||||||
assert_raises do
|
assert_raises do
|
||||||
context.eval('adder(1,2,3)')
|
begin
|
||||||
|
raise FooError, "I like foos"
|
||||||
|
context.eval('adder()')
|
||||||
|
rescue => e
|
||||||
|
assert_equal FooError, e.class
|
||||||
|
assert_match( /I like foos/, e.message)
|
||||||
|
# TODO backtrace splicing so js frames are injected
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue