1
0
Fork 0
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:
Sam 2016-05-11 17:58:33 +10:00
parent 8568f6ed48
commit 5440e3f625
5 changed files with 50 additions and 36 deletions

View file

@ -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

View file

@ -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"

View file

@ -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 {

View file

@ -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

View file

@ -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