diff --git a/ext/mini_racer_extension/mini_racer_extension.cc b/ext/mini_racer_extension/mini_racer_extension.cc index 2f167fd..4b611f9 100644 --- a/ext/mini_racer_extension/mini_racer_extension.cc +++ b/ext/mini_racer_extension/mini_racer_extension.cc @@ -586,10 +586,6 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local context, return INT2FIX(value->Int32Value(context).ToChecked()); } - if (value->IsNumber()) { - return rb_float_new(value->NumberValue(context).ToChecked()); - } - if (value->IsTrue()) { return Qtrue; } @@ -598,81 +594,107 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local context, return Qfalse; } - if (value->IsArray()) { - VALUE rb_array = rb_ary_new(); - Local arr = Local::Cast(value); - for(uint32_t i=0; i < arr->Length(); i++) { - MaybeLocal element = arr->Get(context, i); - if (element.IsEmpty()) { - continue; - } - VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked()); - if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) { - return rb_elem; - } - rb_ary_push(rb_array, rb_elem); - } - return rb_array; - } - - if (value->IsFunction()){ - return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0); - } - - if (value->IsDate()){ - double ts = Local::Cast(value)->ValueOf(); - double secs = ts/1000; - long nanos = round((secs - floor(secs)) * 1000000); - - return rb_time_new(secs, nanos); - } - - if (value->IsObject()) { - VALUE rb_hash = rb_hash_new(); - TryCatch trycatch(isolate); - - Local object = value->ToObject(context).ToLocalChecked(); - auto maybe_props = object->GetOwnPropertyNames(context); - if (!maybe_props.IsEmpty()) { - Local props = maybe_props.ToLocalChecked(); - for(uint32_t i=0; i < props->Length(); i++) { - MaybeLocal key = props->Get(context, i); - if (key.IsEmpty()) { - return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2("")); - } - VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked()); - - MaybeLocal prop_value = object->Get(context, key.ToLocalChecked()); - // this may have failed due to Get raising - if (prop_value.IsEmpty() || trycatch.HasCaught()) { - return new_empty_failed_conv_obj(); - } - - VALUE rb_value = convert_v8_to_ruby( - isolate, context, prop_value.ToLocalChecked()); - rb_hash_aset(rb_hash, rb_key, rb_value); + struct State { + Isolate* isolate; + Local value; + Local context; + } state = {isolate, value, context}; + + // calls to rb_*() functions can raise exceptions and longjmp, + // and therefore must be inside this protected block + auto can_raise = [](VALUE arg) -> VALUE { + State* state = + reinterpret_cast(static_cast(RB_NUM2ULL(arg))); + Isolate* isolate = state->isolate; + Local value = state->value; + Local context = state->context; + + if (value->IsNumber()) { + return rb_float_new(value->NumberValue(context).ToChecked()); + } + + if (value->IsArray()) { + VALUE rb_array = rb_ary_new(); + Local arr = Local::Cast(value); + for(uint32_t i = 0; i < arr->Length(); i++) { + MaybeLocal element = arr->Get(context, i); + if (element.IsEmpty()) { + continue; + } + VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked()); + if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) { + return rb_elem; + } + rb_ary_push(rb_array, rb_elem); } + return rb_array; } - return rb_hash; - } - if (value->IsSymbol()) { - v8::String::Utf8Value symbol_name(isolate, - Local::Cast(value)->Description(isolate)); + if (value->IsFunction()){ + return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0); + } - VALUE str_symbol = rb_utf8_str_new(*symbol_name, symbol_name.length()); + if (value->IsDate()){ + double ts = Local::Cast(value)->ValueOf(); + double secs = ts/1000; + long nanos = round((secs - floor(secs)) * 1000000); - return rb_str_intern(str_symbol); - } + return rb_time_new(secs, nanos); + } - MaybeLocal rstr_maybe = value->ToString(context); + if (value->IsObject()) { + VALUE rb_hash = rb_hash_new(); + TryCatch trycatch(isolate); + + Local object = value->ToObject(context).ToLocalChecked(); + auto maybe_props = object->GetOwnPropertyNames(context); + if (!maybe_props.IsEmpty()) { + Local props = maybe_props.ToLocalChecked(); + for (uint32_t i = 0; i < props->Length(); i++) { + MaybeLocal key = props->Get(context, i); + if (key.IsEmpty()) { + return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2("")); + } + VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked()); - if (rstr_maybe.IsEmpty()) { - return Qnil; - } else { - Local rstr = rstr_maybe.ToLocalChecked(); - return rb_utf8_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate)); - } + MaybeLocal prop_value = object->Get(context, key.ToLocalChecked()); + // this may have failed due to Get raising + if (prop_value.IsEmpty() || trycatch.HasCaught()) { + return new_empty_failed_conv_obj(); + } + + VALUE rb_value = convert_v8_to_ruby( + isolate, context, prop_value.ToLocalChecked()); + rb_hash_aset(rb_hash, rb_key, rb_value); + } + } + return rb_hash; + } + + if (value->IsSymbol()) { + v8::String::Utf8Value symbol_name(isolate, + Local::Cast(value)->Description(isolate)); + + VALUE str_symbol = rb_utf8_str_new(*symbol_name, symbol_name.length()); + + return rb_str_intern(str_symbol); + } + + MaybeLocal rstr_maybe = value->ToString(context); + + if (rstr_maybe.IsEmpty()) { + return Qnil; + } else { + Local rstr = rstr_maybe.ToLocalChecked(); + return rb_utf8_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate)); + } + }; + + // this is kind of slow because RB_ULL2NUM allocs when the pointer + // doesn't fit in a fixnum but yolo'ing and reinterpret_casting the + // pointer to a VALUE is probably not very sound + VALUE arg = RB_ULL2NUM(reinterpret_cast(&state)); + return rb_protect(can_raise, arg, nullptr); } static VALUE convert_v8_to_ruby(Isolate* isolate, diff --git a/test/mini_racer_test.rb b/test/mini_racer_test.rb index c82d06b..134021a 100644 --- a/test/mini_racer_test.rb +++ b/test/mini_racer_test.rb @@ -252,6 +252,16 @@ def test_return_hash ) end + def test_date_nan + # NoMethodError: undefined method `source_location' for " + # core/float.rb:114:in `to_i'":Thread::Backtrace::Location + if RUBY_ENGINE == "truffleruby" + skip "TruffleRuby bug" + end + context = MiniRacer::Context.new + context.eval("new Date(NaN)") # should not crash process + end + def test_return_date context = MiniRacer::Context.new test_time = Time.new