mirror of
https://github.com/rubyjs/mini_racer
synced 2023-03-27 23:21:28 -04:00
Merge pull request #88 from cataphract/call-ret-improvs
Improvements on returning values
This commit is contained in:
commit
4c104f6a91
2 changed files with 308 additions and 285 deletions
|
@ -120,7 +120,7 @@ typedef struct {
|
|||
bool error;
|
||||
Local<Function> fun;
|
||||
Local<Value> *argv;
|
||||
Local<Value> result;
|
||||
EvalResult result;
|
||||
} FunctionCall;
|
||||
|
||||
enum IsolateFlags {
|
||||
|
@ -130,6 +130,7 @@ enum IsolateFlags {
|
|||
MEM_SOFTLIMIT_REACHED,
|
||||
};
|
||||
|
||||
static VALUE rb_cContext;
|
||||
static VALUE rb_cSnapshot;
|
||||
static VALUE rb_cIsolate;
|
||||
|
||||
|
@ -205,6 +206,85 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
|
|||
}
|
||||
}
|
||||
|
||||
// to be called with active lock and scope
|
||||
static void prepare_result(MaybeLocal<Value> v8res,
|
||||
TryCatch& trycatch,
|
||||
Isolate* isolate,
|
||||
Local<Context> context,
|
||||
EvalResult& evalRes /* out */) {
|
||||
|
||||
// just don't touch .parsed
|
||||
evalRes.terminated = false;
|
||||
evalRes.json = false;
|
||||
evalRes.value = nullptr;
|
||||
evalRes.message = nullptr;
|
||||
evalRes.backtrace = nullptr;
|
||||
evalRes.executed = !v8res.IsEmpty();
|
||||
|
||||
if (evalRes.executed) {
|
||||
// arrays and objects get converted to json
|
||||
Local<Value> local_value = v8res.ToLocalChecked();
|
||||
if ((local_value->IsObject() || local_value->IsArray()) &&
|
||||
!local_value->IsDate() && !local_value->IsFunction()) {
|
||||
Local<Object> JSON = context->Global()->Get(
|
||||
String::NewFromUtf8(isolate, "JSON"))->ToObject();
|
||||
|
||||
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
|
||||
.As<Function>();
|
||||
|
||||
Local<Object> object = local_value->ToObject();
|
||||
const unsigned argc = 1;
|
||||
Local<Value> argv[argc] = { object };
|
||||
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
|
||||
|
||||
if (json.IsEmpty()) {
|
||||
evalRes.executed = false;
|
||||
} else {
|
||||
evalRes.json = true;
|
||||
Persistent<Value>* persistent = new Persistent<Value>();
|
||||
persistent->Reset(isolate, json.ToLocalChecked());
|
||||
evalRes.value = persistent;
|
||||
}
|
||||
|
||||
} else {
|
||||
Persistent<Value>* persistent = new Persistent<Value>();
|
||||
persistent->Reset(isolate, local_value);
|
||||
evalRes.value = persistent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!evalRes.executed || !evalRes.parsed) {
|
||||
if (trycatch.HasCaught()) {
|
||||
if (!trycatch.Exception()->IsNull()) {
|
||||
evalRes.message = new Persistent<Value>();
|
||||
Local<Message> message = trycatch.Message();
|
||||
char buf[1000];
|
||||
int len;
|
||||
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
|
||||
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
|
||||
message->GetLineNumber(),
|
||||
message->GetStartColumn());
|
||||
if ((size_t) len >= sizeof(buf)) {
|
||||
len = sizeof(buf) - 1;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
|
||||
evalRes.message->Reset(isolate, v8_message);
|
||||
} else if(trycatch.HasTerminated()) {
|
||||
evalRes.terminated = true;
|
||||
evalRes.message = new Persistent<Value>();
|
||||
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
||||
evalRes.message->Reset(isolate, tmp);
|
||||
}
|
||||
if (!trycatch.StackTrace().IsEmpty()) {
|
||||
evalRes.backtrace = new Persistent<Value>();
|
||||
evalRes.backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void*
|
||||
nogvl_context_eval(void* arg) {
|
||||
|
||||
|
@ -246,90 +326,30 @@ nogvl_context_eval(void* arg) {
|
|||
result->json = false;
|
||||
result->value = NULL;
|
||||
|
||||
MaybeLocal<Value> maybe_value;
|
||||
if (!result->parsed) {
|
||||
result->message = new Persistent<Value>();
|
||||
result->message->Reset(isolate, trycatch.Exception());
|
||||
} else {
|
||||
// parsing successful
|
||||
if (eval_params->max_memory > 0) {
|
||||
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
||||
isolate->AddGCEpilogueCallback(gc_callback);
|
||||
}
|
||||
|
||||
if(eval_params->max_memory > 0) {
|
||||
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
||||
isolate->AddGCEpilogueCallback(gc_callback);
|
||||
maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
||||
|
||||
result->executed = !maybe_value.IsEmpty();
|
||||
|
||||
if (result->executed) {
|
||||
|
||||
// arrays and objects get converted to json
|
||||
Local<Value> local_value = maybe_value.ToLocalChecked();
|
||||
if ((local_value->IsObject() || local_value->IsArray()) &&
|
||||
!local_value->IsDate() && !local_value->IsFunction()) {
|
||||
Local<Object> JSON = context->Global()->Get(
|
||||
String::NewFromUtf8(isolate, "JSON"))->ToObject();
|
||||
|
||||
Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
|
||||
.As<Function>();
|
||||
|
||||
Local<Object> object = local_value->ToObject();
|
||||
const unsigned argc = 1;
|
||||
Local<Value> argv[argc] = { object };
|
||||
MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
|
||||
|
||||
if (json.IsEmpty()) {
|
||||
result->executed = false;
|
||||
} else {
|
||||
result->json = true;
|
||||
Persistent<Value>* persistent = new Persistent<Value>();
|
||||
persistent->Reset(isolate, json.ToLocalChecked());
|
||||
result->value = persistent;
|
||||
}
|
||||
|
||||
} else {
|
||||
Persistent<Value>* persistent = new Persistent<Value>();
|
||||
persistent->Reset(isolate, local_value);
|
||||
result->value = persistent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result->executed || !result->parsed) {
|
||||
if (trycatch.HasCaught()) {
|
||||
if (!trycatch.Exception()->IsNull()) {
|
||||
result->message = new Persistent<Value>();
|
||||
Local<Message> message = trycatch.Message();
|
||||
char buf[1000];
|
||||
int len;
|
||||
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
|
||||
*String::Utf8Value(message->GetScriptResourceName()->ToString()),
|
||||
message->GetLineNumber(),
|
||||
message->GetStartColumn());
|
||||
|
||||
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
|
||||
result->message->Reset(isolate, v8_message);
|
||||
} else if(trycatch.HasTerminated()) {
|
||||
|
||||
|
||||
result->terminated = true;
|
||||
result->message = new Persistent<Value>();
|
||||
Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
||||
result->message->Reset(isolate, tmp);
|
||||
}
|
||||
if (!trycatch.StackTrace().IsEmpty()) {
|
||||
result->backtrace = new Persistent<Value>();
|
||||
result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
prepare_result(maybe_value, trycatch, isolate, context, *result);
|
||||
|
||||
isolate->SetData(IN_GVL, (void*)true);
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
||||
// assumes isolate locking is in place
|
||||
static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
|
||||
Local<Value> value) {
|
||||
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope scope(isolate);
|
||||
|
@ -359,7 +379,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|||
Local<Array> arr = Local<Array>::Cast(value);
|
||||
for(uint32_t i=0; i < arr->Length(); i++) {
|
||||
Local<Value> element = arr->Get(i);
|
||||
VALUE rb_elem = convert_v8_to_ruby(isolate, element);
|
||||
VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
|
||||
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
||||
return rb_elem;
|
||||
}
|
||||
|
@ -383,16 +403,15 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|||
if (value->IsObject()) {
|
||||
VALUE rb_hash = rb_hash_new();
|
||||
TryCatch trycatch(isolate);
|
||||
Local<Context> context = Context::New(isolate);
|
||||
|
||||
Local<Object> object = value->ToObject();
|
||||
MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
|
||||
auto maybe_props = object->GetOwnPropertyNames(context);
|
||||
if (!maybe_props.IsEmpty()) {
|
||||
Local<Array> props = maybe_props.ToLocalChecked();
|
||||
for(uint32_t i=0; i < props->Length(); i++) {
|
||||
Local<Value> key = props->Get(i);
|
||||
VALUE rb_key = convert_v8_to_ruby(isolate, key);
|
||||
Local<Value> value = object->Get(key);
|
||||
VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
|
||||
Local<Value> prop_value = object->Get(key);
|
||||
// this may have failed due to Get raising
|
||||
|
||||
if (trycatch.HasCaught()) {
|
||||
|
@ -401,7 +420,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|||
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
||||
}
|
||||
|
||||
VALUE rb_value = convert_v8_to_ruby(isolate, value);
|
||||
VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
|
||||
rb_hash_aset(rb_hash, rb_key, rb_value);
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +431,25 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
|
|||
return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
}
|
||||
|
||||
static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
||||
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
||||
const Persistent<Context>& context,
|
||||
Local<Value> value) {
|
||||
HandleScope scope(isolate);
|
||||
return convert_v8_to_ruby(isolate,
|
||||
Local<Context>::New(isolate, context),
|
||||
value);
|
||||
}
|
||||
|
||||
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
||||
const Persistent<Context>& context,
|
||||
const Persistent<Value>& value) {
|
||||
HandleScope scope(isolate);
|
||||
return convert_v8_to_ruby(isolate,
|
||||
Local<Context>::New(isolate, context),
|
||||
Local<Value>::New(isolate, value));
|
||||
}
|
||||
|
||||
static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
|
||||
EscapableHandleScope scope(isolate);
|
||||
|
||||
Local<Array> array;
|
||||
|
@ -640,15 +677,103 @@ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
|
|||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE convert_result_to_ruby(VALUE self /* context */,
|
||||
EvalResult& result) {
|
||||
ContextInfo *context_info;
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
|
||||
Isolate *isolate = context_info->isolate_info->isolate;
|
||||
Persistent<Context> *p_ctx = context_info->context;
|
||||
|
||||
VALUE message = Qnil;
|
||||
VALUE backtrace = Qnil;
|
||||
{
|
||||
Locker lock(isolate);
|
||||
if (result.message) {
|
||||
message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
|
||||
result.message->Reset();
|
||||
delete result.message;
|
||||
result.message = nullptr;
|
||||
}
|
||||
|
||||
if (result.backtrace) {
|
||||
backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
|
||||
result.backtrace->Reset();
|
||||
delete result.backtrace;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this is very important, we can not do an rb_raise from within
|
||||
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
||||
if (!result.parsed) {
|
||||
if(TYPE(message) == T_STRING) {
|
||||
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
||||
} else {
|
||||
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.executed) {
|
||||
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
||||
if (ruby_exception == Qnil) {
|
||||
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
|
||||
// If we were terminated or have the memory softlimit flag set
|
||||
if (result.terminated || mem_softlimit_reached) {
|
||||
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
||||
} else {
|
||||
ruby_exception = rb_eScriptRuntimeError;
|
||||
}
|
||||
|
||||
// exception report about what happened
|
||||
if (TYPE(backtrace) == T_STRING) {
|
||||
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
||||
} else if(TYPE(message) == T_STRING) {
|
||||
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
||||
} else {
|
||||
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
||||
}
|
||||
} else {
|
||||
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
||||
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
||||
}
|
||||
}
|
||||
|
||||
VALUE ret = Qnil;
|
||||
|
||||
// New scope for return value
|
||||
{
|
||||
Locker lock(isolate);
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
Local<Value> tmp = Local<Value>::New(isolate, *result.value);
|
||||
|
||||
if (result.json) {
|
||||
Local<String> rstr = tmp->ToString();
|
||||
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
||||
} else {
|
||||
ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
|
||||
}
|
||||
|
||||
result.value->Reset();
|
||||
delete result.value;
|
||||
}
|
||||
|
||||
if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
||||
// TODO try to recover stack trace from the conversion error
|
||||
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
||||
|
||||
EvalParams eval_params;
|
||||
EvalResult eval_result;
|
||||
ContextInfo* context_info;
|
||||
VALUE result;
|
||||
|
||||
VALUE message = Qnil;
|
||||
VALUE backtrace = Qnil;
|
||||
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
Isolate* isolate = context_info->isolate_info->isolate;
|
||||
|
@ -699,84 +824,9 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
|||
eval_result.backtrace = NULL;
|
||||
|
||||
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
|
||||
|
||||
if (eval_result.message != NULL) {
|
||||
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
|
||||
message = convert_v8_to_ruby(isolate, tmp);
|
||||
eval_result.message->Reset();
|
||||
delete eval_result.message;
|
||||
}
|
||||
|
||||
if (eval_result.backtrace != NULL) {
|
||||
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
|
||||
backtrace = convert_v8_to_ruby(isolate, tmp);
|
||||
eval_result.backtrace->Reset();
|
||||
delete eval_result.backtrace;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this is very important, we can not do an rb_raise from within
|
||||
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
||||
if (!eval_result.parsed) {
|
||||
if(TYPE(message) == T_STRING) {
|
||||
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
||||
} else {
|
||||
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
||||
}
|
||||
}
|
||||
|
||||
if (!eval_result.executed) {
|
||||
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
||||
if (ruby_exception == Qnil) {
|
||||
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
|
||||
// If we were terminated or have the memory softlimit flag set
|
||||
if(eval_result.terminated || mem_softlimit_reached) {
|
||||
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
||||
} else {
|
||||
ruby_exception = rb_eScriptRuntimeError;
|
||||
}
|
||||
|
||||
// exception report about what happened
|
||||
if(TYPE(backtrace) == T_STRING) {
|
||||
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
||||
} else if(TYPE(message) == T_STRING) {
|
||||
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
||||
} else {
|
||||
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
||||
}
|
||||
} else {
|
||||
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
||||
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
||||
}
|
||||
}
|
||||
|
||||
// New scope for return value
|
||||
{
|
||||
Locker lock(isolate);
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
|
||||
|
||||
if (eval_result.json) {
|
||||
Local<String> rstr = tmp->ToString();
|
||||
VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
|
||||
result = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
||||
} else {
|
||||
result = convert_v8_to_ruby(isolate, tmp);
|
||||
}
|
||||
|
||||
eval_result.value->Reset();
|
||||
delete eval_result.value;
|
||||
}
|
||||
|
||||
if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
||||
// TODO try to recover stack trace from the conversion error
|
||||
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
return convert_result_to_ruby(self, eval_result);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
@ -815,23 +865,30 @@ gvl_ruby_callback(void* data) {
|
|||
VALUE callback;
|
||||
VALUE result;
|
||||
VALUE self;
|
||||
|
||||
VALUE parent;
|
||||
{
|
||||
HandleScope scope(args->GetIsolate());
|
||||
Handle<External> external = Handle<External>::Cast(args->Data());
|
||||
HandleScope scope(args->GetIsolate());
|
||||
Local<External> external = Local<External>::Cast(args->Data());
|
||||
|
||||
VALUE* self_pointer = (VALUE*)(external->Value());
|
||||
self = *self_pointer;
|
||||
callback = rb_iv_get(self, "@callback");
|
||||
self = *(VALUE*)(external->Value());
|
||||
callback = rb_iv_get(self, "@callback");
|
||||
|
||||
parent = rb_iv_get(self, "@parent");
|
||||
if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ContextInfo* context_info;
|
||||
Data_Get_Struct(parent, ContextInfo, context_info);
|
||||
|
||||
if (length > 0) {
|
||||
ruby_args = ALLOC_N(VALUE, length);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Local<Value> value = ((*args)[i]).As<Value>();
|
||||
ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
|
||||
ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(),
|
||||
*context_info->context, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -852,7 +909,6 @@ gvl_ruby_callback(void* data) {
|
|||
(VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
|
||||
|
||||
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"));
|
||||
}
|
||||
|
@ -1164,14 +1220,11 @@ nogvl_context_call(void *args) {
|
|||
|
||||
Local<Function> fun = call->fun;
|
||||
|
||||
EvalResult& eval_res = call->result;
|
||||
eval_res.parsed = true;
|
||||
|
||||
MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
|
||||
if (res.IsEmpty()) {
|
||||
// A better error handling should be added, factoring out exception management
|
||||
// code from nogvl_context_eval
|
||||
call->error = true;
|
||||
} else {
|
||||
call->result = handle_scope.Escape(res.ToLocalChecked());
|
||||
}
|
||||
prepare_result(res, trycatch, isolate, context, eval_res);
|
||||
|
||||
isolate->SetData(IN_GVL, (void*)true);
|
||||
|
||||
|
@ -1188,7 +1241,6 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
|
|||
|
||||
ContextInfo* context_info;
|
||||
FunctionCall call;
|
||||
VALUE res = Qnil;
|
||||
VALUE *call_argv = NULL;
|
||||
|
||||
Data_Get_Struct(self, ContextInfo, context_info);
|
||||
|
@ -1245,16 +1297,9 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
|
|||
|
||||
rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
|
||||
free(call.argv);
|
||||
}
|
||||
|
||||
if (!call.error) {
|
||||
res = convert_v8_to_ruby(isolate, call.result);
|
||||
}
|
||||
}
|
||||
if (call.error) {
|
||||
// TODO - better handling of exceptions
|
||||
rb_raise(rb_eScriptRuntimeError, "Error calling %s", call.function_name);
|
||||
}
|
||||
return res;
|
||||
return convert_result_to_ruby(self, call.result);
|
||||
}
|
||||
|
||||
static VALUE rb_context_create_isolate_value(VALUE self) {
|
||||
|
@ -1275,7 +1320,7 @@ extern "C" {
|
|||
void Init_mini_racer_extension ( void )
|
||||
{
|
||||
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
|
||||
VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
||||
rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
||||
rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
|
||||
rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
|
||||
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
|
||||
|
@ -1303,13 +1348,12 @@ extern "C" {
|
|||
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
||||
rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
|
||||
rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
|
||||
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
|
||||
|
||||
rb_define_alloc_func(rb_cContext, allocate);
|
||||
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
||||
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
||||
|
||||
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
||||
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
|
||||
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
||||
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
||||
|
||||
|
|
|
@ -2,108 +2,87 @@ require 'test_helper'
|
|||
require 'timeout'
|
||||
|
||||
class MiniRacerFunctionTest < Minitest::Test
|
||||
def test_fun
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
|
||||
assert_equal context.eval('f(10)'), 'I need 10 foos'
|
||||
|
||||
def test_fun
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
|
||||
assert_equal context.eval('f(10)'), "I need 10 foos"
|
||||
assert_raises(ArgumentError) do
|
||||
context.call
|
||||
end
|
||||
|
||||
assert_raises(ArgumentError) do
|
||||
context.call
|
||||
count = 4
|
||||
res = context.call('f', count)
|
||||
assert_equal "I need #{count} foos", res
|
||||
end
|
||||
|
||||
def test_non_existing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' galettes' }")
|
||||
|
||||
# f is defined, let's call g
|
||||
assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('g')
|
||||
end
|
||||
end
|
||||
|
||||
def test_throwing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval('function f(x) { throw new Error("foo bar") }')
|
||||
|
||||
# f is defined, let's call g
|
||||
err = assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('f', 1)
|
||||
end
|
||||
assert_equal err.message, 'Error: foo bar'
|
||||
assert_match /1:23/, err.backtrace[0]
|
||||
end
|
||||
|
||||
def test_args_types
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x, y) { return 'I need ' + x + ' ' + y }")
|
||||
|
||||
res = context.call('f', 3, 'bars')
|
||||
assert_equal 'I need 3 bars', res
|
||||
|
||||
res = context.call('f', { a: 1 }, 'bars')
|
||||
assert_equal 'I need [object Object] bars', res
|
||||
|
||||
res = context.call('f', [1, 2, 3], 'bars')
|
||||
assert_equal 'I need 1,2,3 bars', res
|
||||
end
|
||||
|
||||
def test_complex_return
|
||||
context = MiniRacer::Context.new
|
||||
context.eval('function f(x, y) { return { vx: x, vy: y, array: [x, y] } }')
|
||||
|
||||
h = { 'vx' => 3, 'vy' => 'bars', 'array' => [3, 'bars'] }
|
||||
res = context.call('f', 3, 'bars')
|
||||
assert_equal h, res
|
||||
end
|
||||
|
||||
def test_do_not_hang_with_concurrent_calls
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
|
||||
|
||||
thread_count = 2
|
||||
|
||||
threads = []
|
||||
thread_count.times do
|
||||
threads << Thread.new do
|
||||
10.times do |i|
|
||||
context.call('f', i)
|
||||
end
|
||||
|
||||
count = 4
|
||||
res = context.call('f', count)
|
||||
assert_equal "I need #{count} foos", res
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_existing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
|
||||
|
||||
# f is defined, let's call g
|
||||
assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('g')
|
||||
end
|
||||
joined_thread_count = 0
|
||||
for t in threads do
|
||||
joined_thread_count += 1
|
||||
t.join
|
||||
end
|
||||
|
||||
def test_throwing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { throw new Error() }")
|
||||
|
||||
# f should throw
|
||||
assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('f', 1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_existing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' galettes' }")
|
||||
|
||||
# f is defined, let's call g
|
||||
assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('g')
|
||||
end
|
||||
end
|
||||
|
||||
def test_throwing_function
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { throw new Error() }")
|
||||
|
||||
# f is defined, let's call g
|
||||
assert_raises(MiniRacer::RuntimeError) do
|
||||
context.call('f', 1)
|
||||
end
|
||||
end
|
||||
|
||||
def test_args_types
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x, y) { return 'I need ' + x + ' ' + y }")
|
||||
|
||||
res = context.call('f', 3, "bars")
|
||||
assert_equal "I need 3 bars", res
|
||||
|
||||
res = context.call('f', {a: 1}, "bars")
|
||||
assert_equal "I need [object Object] bars", res
|
||||
|
||||
res = context.call('f', [1,2,3], "bars")
|
||||
assert_equal "I need 1,2,3 bars", res
|
||||
end
|
||||
|
||||
def test_complex_return
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x, y) { return { vx: x, vy: y, array: [x, y] } }")
|
||||
|
||||
h = { "vx" => 3, "vy" => "bars", "array" => [3, "bars"] }
|
||||
res = context.call('f', 3, "bars")
|
||||
assert_equal h, res
|
||||
end
|
||||
|
||||
def test_do_not_hang_with_concurrent_calls
|
||||
|
||||
context = MiniRacer::Context.new
|
||||
context.eval("function f(x) { return 'I need ' + x + ' foos' }")
|
||||
|
||||
thread_count = 2
|
||||
|
||||
threads = []
|
||||
thread_count.times do
|
||||
threads << Thread.new do
|
||||
10.times do |i|
|
||||
context.call("f", i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
joined_thread_count = 0
|
||||
for t in threads do
|
||||
joined_thread_count += 1
|
||||
t.join
|
||||
end
|
||||
|
||||
# Dummy test, completing should be enough to show we don't hang
|
||||
self.assert_equal thread_count, joined_thread_count
|
||||
end
|
||||
# Dummy test, completing should be enough to show we don't hang
|
||||
assert_equal thread_count, joined_thread_count
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue