Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions ext/mini_racer_extension/mini_racer_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,10 @@ static void des_object_ref(void *arg, uint32_t id)

static void des_error_begin(void *arg)
{
push(arg, rb_class_new_instance(0, NULL, rb_eRuntimeError));
}

static void des_error_end(void *arg)
{
pop(arg);
}

static int collect(VALUE k, VALUE v, VALUE a)
Expand Down Expand Up @@ -894,6 +892,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
static void *rendezvous_callback(void *arg)
{
struct rendezvous_nogvl *a;
const char *err;
Context *c;
int exc;
VALUE r;
Expand All @@ -917,7 +916,12 @@ static void *rendezvous_callback(void *arg)
buf_move(&s.b, a->req);
return NULL;
fail:
ser_init1(&s, 'e'); // exception pending
ser_init0(&s); // ruby exception pending
w_byte(&s, 'e'); // send ruby error message to v8 thread
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
err = StringValueCStr(r);
if (err)
w(&s, err, strlen(err));
goto out;
}

Expand Down Expand Up @@ -975,18 +979,20 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
int exc;

rendezvous_no_des(c, req, &res); // takes ownership of |req|
r = c->exception;
c->exception = Qnil;
// if js land didn't handle exception from ruby callback, re-raise it now
if (res.len == 1 && *res.buf == 'e') {
assert(!NIL_P(r));
rb_exc_raise(r);
}
r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
buf_reset(&res);
if (exc) {
r = rb_errinfo();
rb_set_errinfo(Qnil);
rb_exc_raise(r);
}
if (!NIL_P(c->exception)) {
r = c->exception;
c->exception = Qnil;
rb_exc_raise(r);
}
return r;
}

Expand Down
31 changes: 29 additions & 2 deletions ext/mini_racer_extension/mini_racer_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ struct State
v8::Persistent<v8::Context> persistent_context; // single-thread mode only
v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
v8::Persistent<v8::Function> persistent_safe_context_function; // single-thread mode only
v8::Persistent<v8::Value> ruby_exception;
Context *ruby_context;
int64_t max_memory;
int err_reason;
Expand Down Expand Up @@ -122,6 +123,20 @@ struct Serialized
}
};

bool bubble_up_ruby_exception(State& st, v8::TryCatch *try_catch)
{
auto exception = try_catch->Exception();
if (exception.IsEmpty()) return false;
auto ruby_exception = v8::Local<v8::Value>::New(st.isolate, st.ruby_exception);
if (ruby_exception.IsEmpty()) return false;
if (!ruby_exception->SameValue(exception)) return false;
// signal that the ruby thread should reraise the exception
// that it caught earlier when executing a js->ruby callback
uint8_t c = 'e';
v8_reply(st.ruby_context, &c, 1);
return true;
}

// throws JS exception on serialization error
bool reply(State& st, v8::Local<v8::Value> v)
{
Expand Down Expand Up @@ -388,8 +403,17 @@ void v8_api_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
v8_roundtrip(st.ruby_context, &p, &n);
if (*p == 'c') // callback reply
break;
if (*p == 'e') // ruby exception pending
return st.isolate->TerminateExecution();
if (*p == 'e') { // ruby exception pending
v8::Local<v8::String> message;
auto type = v8::NewStringType::kNormal;
if (!v8::String::NewFromOneByte(st.isolate, p+1, type, n-1).ToLocal(&message)) {
message = v8::String::NewFromUtf8Literal(st.isolate, "Ruby exception");
}
auto exception = v8::Exception::Error(message);
st.ruby_exception.Reset(st.isolate, exception);
st.isolate->ThrowException(exception);
return;
}
v8_dispatch(st.ruby_context);
}
v8::ValueDeserializer des(st.isolate, p+1, n-1);
Expand Down Expand Up @@ -523,6 +547,7 @@ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
st.err_reason = NO_ERROR;
}
if (bubble_up_ruby_exception(st, &try_catch)) return;
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
if (cause) result = v8::Undefined(st.isolate);
auto err = to_error(st, &try_catch, cause);
Expand Down Expand Up @@ -571,6 +596,7 @@ extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
st.err_reason = NO_ERROR;
}
if (bubble_up_ruby_exception(st, &try_catch)) return;
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
if (cause) result = v8::Undefined(st.isolate);
auto err = to_error(st, &try_catch, cause);
Expand Down Expand Up @@ -895,6 +921,7 @@ State::~State()
v8::Isolate::Scope isolate_scope(isolate);
persistent_safe_context.Reset();
persistent_context.Reset();
ruby_exception.Reset();
}
isolate->Dispose();
for (Callback *cb : callbacks)
Expand Down
10 changes: 7 additions & 3 deletions ext/mini_racer_extension/serde.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,17 +194,21 @@ static inline int r_zigzag(const uint8_t **p, const uint8_t *pe, int64_t *r)
return 0;
}

static inline void ser_init(Ser *s)
static void ser_init0(Ser *s)
{
memset(s, 0, sizeof(*s));
buf_init(&s->b);
}

static inline void ser_init(Ser *s)
{
ser_init0(s);
w(s, "\xFF\x0F", 2);
}

static void ser_init1(Ser *s, uint8_t c)
{
memset(s, 0, sizeof(*s));
buf_init(&s->b);
ser_init0(s);
w_byte(s, c);
w(s, "\xFF\x0F", 2);
}
Expand Down
14 changes: 14 additions & 0 deletions test/mini_racer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,20 @@ def test_termination_exception
b.kill
end

def test_ruby_exception
if RUBY_ENGINE == "truffleruby"
skip "TruffleRuby doesn't return JS exceptions as dictionaries"
end
context = MiniRacer::Context.new
context.attach("test", proc { raise "boom" })
actual = context.eval("try { test() } catch (e) { e }")
expected = {
"message" => "boom",
"stack" => "Error: boom\n at <eval>:1:7",
}
assert_equal(actual, expected)
end

def test_large_integer
[10_000_000_001, -2**63, 2**63-1].each { |big_int|
context = MiniRacer::Context.new
Expand Down