ruby_box 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,910 @@
1
+ #include <stdio.h>
2
+ #include <ruby.h>
3
+ #include <ruby/thread.h>
4
+ #include <v8.h>
5
+ #include <libplatform/libplatform.h>
6
+ #include <ruby/encoding.h>
7
+ #include <pthread.h>
8
+ #include <unistd.h>
9
+ #include <mutex>
10
+
11
+ using namespace v8;
12
+
13
+ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
14
+ public:
15
+ virtual void* Allocate(size_t length) {
16
+ void* data = AllocateUninitialized(length);
17
+ return data == NULL ? data : memset(data, 0, length);
18
+ }
19
+ virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
20
+ virtual void Free(void* data, size_t) { free(data); }
21
+ };
22
+
23
+ typedef struct {
24
+ const char* data;
25
+ int raw_size;
26
+ } SnapshotInfo;
27
+
28
+ typedef struct {
29
+ Isolate* isolate;
30
+ ArrayBufferAllocator* allocator;
31
+ StartupData* startup_data;
32
+ bool interrupted;
33
+
34
+ // how many references to this isolate exist
35
+ // we can't rely on Ruby's GC for this, because when destroying
36
+ // objects, Ruby will destroy ruby objects first, then call the
37
+ // extenstion's deallocators. In this case, that means it would
38
+ // call `deallocate_isolate` _before_ `deallocate`, causing a segfault
39
+ int refs_count;
40
+ } IsolateInfo;
41
+
42
+ typedef struct {
43
+ IsolateInfo* isolate_info;
44
+ Persistent<Context>* context;
45
+ } ContextInfo;
46
+
47
+ typedef struct {
48
+ bool parsed;
49
+ bool executed;
50
+ bool terminated;
51
+ Persistent<Value>* value;
52
+ Persistent<Value>* message;
53
+ Persistent<Value>* backtrace;
54
+ } EvalResult;
55
+
56
+ typedef struct {
57
+ ContextInfo* context_info;
58
+ Local<String>* eval;
59
+ useconds_t timeout;
60
+ EvalResult* result;
61
+ } EvalParams;
62
+
63
+ static VALUE rb_eScriptTerminatedError;
64
+ static VALUE rb_eParseError;
65
+ static VALUE rb_eScriptRuntimeError;
66
+ static VALUE rb_cJavaScriptFunction;
67
+ static VALUE rb_eSnapshotError;
68
+ static VALUE rb_ePlatformAlreadyInitializedError;
69
+
70
+ static VALUE rb_cFailedV8Conversion;
71
+ static VALUE rb_cDateTime = Qnil;
72
+
73
+ static Platform* current_platform = NULL;
74
+ static std::mutex platform_lock;
75
+
76
+ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
77
+ bool platform_already_initialized = false;
78
+
79
+ platform_lock.lock();
80
+
81
+ if (current_platform == NULL) {
82
+ V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
83
+ } else {
84
+ platform_already_initialized = true;
85
+ }
86
+
87
+ platform_lock.unlock();
88
+
89
+ // important to raise outside of the lock
90
+ if (platform_already_initialized) {
91
+ rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
92
+ }
93
+
94
+ return Qnil;
95
+ }
96
+
97
+ static void init_v8() {
98
+ // no need to wait for the lock if already initialized
99
+ if (current_platform != NULL) return;
100
+
101
+ platform_lock.lock();
102
+
103
+ if (current_platform == NULL) {
104
+ V8::InitializeICU();
105
+ current_platform = platform::CreateDefaultPlatform();
106
+ V8::InitializePlatform(current_platform);
107
+ V8::Initialize();
108
+ }
109
+
110
+ platform_lock.unlock();
111
+ }
112
+
113
+ void*
114
+ nogvl_context_eval(void* arg) {
115
+
116
+ EvalParams* eval_params = (EvalParams*)arg;
117
+ EvalResult* result = eval_params->result;
118
+ Isolate* isolate = eval_params->context_info->isolate_info->isolate;
119
+ Isolate::Scope isolate_scope(isolate);
120
+ HandleScope handle_scope(isolate);
121
+
122
+ TryCatch trycatch(isolate);
123
+
124
+ Local<Context> context = eval_params->context_info->context->Get(isolate);
125
+ Context::Scope context_scope(context);
126
+
127
+ // in gvl flag
128
+ isolate->SetData(0, (void*)false);
129
+ // terminate ASAP
130
+ isolate->SetData(1, (void*)false);
131
+
132
+ MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
133
+ result->parsed = !parsed_script.IsEmpty();
134
+ result->executed = false;
135
+ result->terminated = false;
136
+ result->value = NULL;
137
+
138
+ if (!result->parsed) {
139
+ result->message = new Persistent<Value>();
140
+ result->message->Reset(isolate, trycatch.Exception());
141
+ } else {
142
+
143
+ MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
144
+
145
+ result->executed = !maybe_value.IsEmpty();
146
+
147
+ if (result->executed) {
148
+ Persistent<Value>* persistent = new Persistent<Value>();
149
+ persistent->Reset(isolate, maybe_value.ToLocalChecked());
150
+ result->value = persistent;
151
+ }
152
+ }
153
+
154
+ if (!result->executed || !result->parsed) {
155
+ if (trycatch.HasCaught()) {
156
+ if (!trycatch.Exception()->IsNull()) {
157
+ result->message = new Persistent<Value>();
158
+ Local<Message> message = trycatch.Message();
159
+ char buf[1000];
160
+ int len;
161
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
162
+ *String::Utf8Value(message->GetScriptResourceName()->ToString()),
163
+ message->GetLineNumber(),
164
+ message->GetStartColumn());
165
+
166
+ Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
167
+ result->message->Reset(isolate, v8_message);
168
+ } else if(trycatch.HasTerminated()) {
169
+
170
+
171
+ result->terminated = true;
172
+ result->message = new Persistent<Value>();
173
+ Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
174
+ result->message->Reset(isolate, tmp);
175
+ }
176
+ if (!trycatch.StackTrace().IsEmpty()) {
177
+ result->backtrace = new Persistent<Value>();
178
+ result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
179
+ }
180
+ }
181
+ }
182
+
183
+ isolate->SetData(0, (void*)true);
184
+
185
+
186
+ return NULL;
187
+ }
188
+
189
+ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
190
+
191
+ HandleScope scope(isolate);
192
+
193
+ if (value->IsNull() || value->IsUndefined()){
194
+ return Qnil;
195
+ }
196
+
197
+ if (value->IsInt32()) {
198
+ return INT2FIX(value->Int32Value());
199
+ }
200
+
201
+ if (value->IsNumber()) {
202
+ return rb_float_new(value->NumberValue());
203
+ }
204
+
205
+ if (value->IsTrue()) {
206
+ return Qtrue;
207
+ }
208
+
209
+ if (value->IsFalse()) {
210
+ return Qfalse;
211
+ }
212
+
213
+ if (value->IsArray()) {
214
+ VALUE rb_array = rb_ary_new();
215
+ Local<Array> arr = Local<Array>::Cast(value);
216
+ for(uint32_t i=0; i < arr->Length(); i++) {
217
+ Local<Value> element = arr->Get(i);
218
+ VALUE rb_elem = convert_v8_to_ruby(isolate, element);
219
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
220
+ return rb_elem;
221
+ }
222
+ rb_ary_push(rb_array, rb_elem);
223
+ }
224
+ return rb_array;
225
+ }
226
+
227
+ if (value->IsFunction()){
228
+ return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
229
+ }
230
+
231
+ if (value->IsDate()){
232
+ double ts = Local<Date>::Cast(value)->ValueOf();
233
+ double secs = ts/1000;
234
+ long nanos = round((secs - floor(secs)) * 1000000);
235
+
236
+ return rb_time_new(secs, nanos);
237
+ }
238
+
239
+ if (value->IsObject()) {
240
+
241
+ TryCatch trycatch(isolate);
242
+
243
+ VALUE rb_hash = rb_hash_new();
244
+ Local<Context> context = Context::New(isolate);
245
+ Local<Object> object = value->ToObject();
246
+ MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
247
+ if (!maybe_props.IsEmpty()) {
248
+ Local<Array> props = maybe_props.ToLocalChecked();
249
+ for(uint32_t i=0; i < props->Length(); i++) {
250
+ Local<Value> key = props->Get(i);
251
+ VALUE rb_key = convert_v8_to_ruby(isolate, key);
252
+ Local<Value> value = object->Get(key);
253
+ // this may have failed due to Get raising
254
+
255
+ if (trycatch.HasCaught()) {
256
+ // TODO isolate code that translates execption to ruby
257
+ // exception so we can properly return it
258
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
259
+ }
260
+
261
+ VALUE rb_value = convert_v8_to_ruby(isolate, value);
262
+ rb_hash_aset(rb_hash, rb_key, rb_value);
263
+ }
264
+ }
265
+ return rb_hash;
266
+ }
267
+
268
+ Local<String> rstr = value->ToString();
269
+ return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
270
+ }
271
+
272
+ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
273
+ EscapableHandleScope scope(isolate);
274
+
275
+ Local<Array> array;
276
+ Local<Object> object;
277
+ VALUE hash_as_array;
278
+ VALUE pair;
279
+ int i;
280
+ long length;
281
+ long fixnum;
282
+ VALUE klass;
283
+
284
+ switch (TYPE(value)) {
285
+ case T_FIXNUM:
286
+ fixnum = NUM2LONG(value);
287
+ if (fixnum > INT_MAX)
288
+ {
289
+ return scope.Escape(Number::New(isolate, (double)fixnum));
290
+ }
291
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
292
+ case T_FLOAT:
293
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
294
+ case T_STRING:
295
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
296
+ case T_NIL:
297
+ return scope.Escape(Null(isolate));
298
+ case T_TRUE:
299
+ return scope.Escape(True(isolate));
300
+ case T_FALSE:
301
+ return scope.Escape(False(isolate));
302
+ case T_ARRAY:
303
+ length = RARRAY_LEN(value);
304
+ array = Array::New(isolate, (int)length);
305
+ for(i=0; i<length; i++) {
306
+ array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
307
+ }
308
+ return scope.Escape(array);
309
+ case T_HASH:
310
+ object = Object::New(isolate);
311
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
312
+ length = RARRAY_LEN(hash_as_array);
313
+ for(i=0; i<length; i++) {
314
+ pair = rb_ary_entry(hash_as_array, i);
315
+ object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
316
+ convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
317
+ }
318
+ return scope.Escape(object);
319
+ case T_SYMBOL:
320
+ value = rb_funcall(value, rb_intern("to_s"), 0);
321
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
322
+ case T_DATA:
323
+ klass = rb_funcall(value, rb_intern("class"), 0);
324
+ if (klass == rb_cTime || klass == rb_cDateTime)
325
+ {
326
+ if (klass == rb_cDateTime)
327
+ {
328
+ value = rb_funcall(value, rb_intern("to_time"), 0);
329
+ }
330
+ value = rb_funcall(value, rb_intern("to_f"), 0);
331
+ return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
332
+ }
333
+ case T_OBJECT:
334
+ case T_CLASS:
335
+ case T_ICLASS:
336
+ case T_MODULE:
337
+ case T_REGEXP:
338
+ case T_MATCH:
339
+ case T_STRUCT:
340
+ case T_BIGNUM:
341
+ case T_FILE:
342
+ case T_UNDEF:
343
+ case T_NODE:
344
+ default:
345
+ return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
346
+ }
347
+
348
+ }
349
+
350
+ static void unblock_eval(void *ptr) {
351
+ EvalParams* eval = (EvalParams*)ptr;
352
+ eval->context_info->isolate_info->interrupted = true;
353
+ }
354
+
355
+ static VALUE rb_snapshot_size(VALUE self, VALUE str) {
356
+ SnapshotInfo* snapshot_info;
357
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
358
+
359
+ return INT2NUM(snapshot_info->raw_size);
360
+ }
361
+
362
+ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
363
+ SnapshotInfo* snapshot_info;
364
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
365
+
366
+ init_v8();
367
+
368
+ StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
369
+
370
+ if (startup_data.data == NULL && startup_data.raw_size == 0) {
371
+ rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
372
+ }
373
+
374
+ snapshot_info->data = startup_data.data;
375
+ snapshot_info->raw_size = startup_data.raw_size;
376
+
377
+ return Qnil;
378
+ }
379
+
380
+ static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
381
+ SnapshotInfo* snapshot_info;
382
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
383
+
384
+ init_v8();
385
+
386
+ StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
387
+ StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
388
+
389
+ if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
390
+ rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
391
+ } else {
392
+ delete[] snapshot_info->data;
393
+
394
+ snapshot_info->data = warm_startup_data.data;
395
+ snapshot_info->raw_size = warm_startup_data.raw_size;
396
+ }
397
+
398
+ return self;
399
+ }
400
+
401
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
402
+ IsolateInfo* isolate_info;
403
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
404
+
405
+ init_v8();
406
+
407
+ isolate_info->allocator = new ArrayBufferAllocator();
408
+ isolate_info->interrupted = false;
409
+ isolate_info->refs_count = 1;
410
+
411
+ Isolate::CreateParams create_params;
412
+ create_params.array_buffer_allocator = isolate_info->allocator;
413
+
414
+ StartupData* startup_data = NULL;
415
+ if (!NIL_P(snapshot)) {
416
+ SnapshotInfo* snapshot_info;
417
+ Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
418
+
419
+ int raw_size = snapshot_info->raw_size;
420
+ char* data = new char[raw_size];
421
+ memcpy(data, snapshot_info->data, sizeof(char) * raw_size);
422
+
423
+ startup_data = new StartupData;
424
+ startup_data->data = data;
425
+ startup_data->raw_size = raw_size;
426
+
427
+ create_params.snapshot_blob = startup_data;
428
+ }
429
+
430
+ isolate_info->startup_data = startup_data;
431
+ isolate_info->isolate = Isolate::New(create_params);
432
+
433
+ return Qnil;
434
+ }
435
+
436
+ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
437
+ IsolateInfo* isolate_info;
438
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
439
+
440
+ return isolate_info->isolate->IdleNotification(NUM2INT(idle_time_in_ms)) ? Qtrue : Qfalse;
441
+ }
442
+
443
+ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
444
+ ContextInfo* context_info;
445
+ Data_Get_Struct(self, ContextInfo, context_info);
446
+
447
+ init_v8();
448
+
449
+ IsolateInfo* isolate_info;
450
+ Data_Get_Struct(isolate, IsolateInfo, isolate_info);
451
+
452
+ context_info->isolate_info = isolate_info;
453
+ isolate_info->refs_count++;
454
+
455
+ {
456
+ Locker lock(isolate_info->isolate);
457
+ Isolate::Scope isolate_scope(isolate_info->isolate);
458
+ HandleScope handle_scope(isolate_info->isolate);
459
+
460
+ Local<Context> context = Context::New(isolate_info->isolate);
461
+
462
+ context_info->context = new Persistent<Context>();
463
+ context_info->context->Reset(isolate_info->isolate, context);
464
+ }
465
+
466
+ if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
467
+ {
468
+ rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
469
+ }
470
+
471
+ return Qnil;
472
+ }
473
+
474
+ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
475
+
476
+ EvalParams eval_params;
477
+ EvalResult eval_result;
478
+ ContextInfo* context_info;
479
+ VALUE result;
480
+
481
+ VALUE message = Qnil;
482
+ VALUE backtrace = Qnil;
483
+
484
+ Data_Get_Struct(self, ContextInfo, context_info);
485
+ Isolate* isolate = context_info->isolate_info->isolate;
486
+
487
+
488
+
489
+ {
490
+ Locker lock(isolate);
491
+ Isolate::Scope isolate_scope(isolate);
492
+ HandleScope handle_scope(isolate);
493
+
494
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
495
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
496
+
497
+ eval_params.context_info = context_info;
498
+ eval_params.eval = &eval;
499
+ eval_params.result = &eval_result;
500
+ eval_params.timeout = 0;
501
+ VALUE timeout = rb_iv_get(self, "@timeout");
502
+ if (timeout != Qnil) {
503
+ eval_params.timeout = (useconds_t)NUM2LONG(timeout);
504
+ }
505
+
506
+ eval_result.message = NULL;
507
+ eval_result.backtrace = NULL;
508
+
509
+ rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
510
+
511
+ if (eval_result.message != NULL) {
512
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
513
+ message = convert_v8_to_ruby(isolate, tmp);
514
+ eval_result.message->Reset();
515
+ delete eval_result.message;
516
+ }
517
+
518
+ if (eval_result.backtrace != NULL) {
519
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
520
+ backtrace = convert_v8_to_ruby(isolate, tmp);
521
+ eval_result.backtrace->Reset();
522
+ delete eval_result.backtrace;
523
+ }
524
+ }
525
+
526
+ // NOTE: this is very important, we can not do an rb_raise from within
527
+ // a v8 scope, if we do the scope is never cleaned up properly and we leak
528
+ if (!eval_result.parsed) {
529
+ if(TYPE(message) == T_STRING) {
530
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
531
+ } else {
532
+ rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
533
+ }
534
+ }
535
+
536
+ if (!eval_result.executed) {
537
+ VALUE ruby_exception = rb_iv_get(self, "@current_exception");
538
+ if (ruby_exception == Qnil) {
539
+ ruby_exception = eval_result.terminated ? rb_eScriptTerminatedError : rb_eScriptRuntimeError;
540
+ // exception report about what happened
541
+ if(TYPE(backtrace) == T_STRING) {
542
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
543
+ } else if(TYPE(message) == T_STRING) {
544
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
545
+ } else {
546
+ rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
547
+ }
548
+ } else {
549
+ VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
550
+ rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
551
+ }
552
+ }
553
+
554
+ // New scope for return value, must release GVL which
555
+ {
556
+ Locker lock(isolate);
557
+ Isolate::Scope isolate_scope(isolate);
558
+ HandleScope handle_scope(isolate);
559
+
560
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
561
+ result = convert_v8_to_ruby(isolate, tmp);
562
+
563
+ eval_result.value->Reset();
564
+ delete eval_result.value;
565
+ }
566
+
567
+ if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
568
+ // TODO try to recover stack trace from the conversion error
569
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
570
+ }
571
+
572
+
573
+ return result;
574
+ }
575
+
576
+ typedef struct {
577
+ VALUE callback;
578
+ int length;
579
+ VALUE* args;
580
+ bool failed;
581
+ } protected_callback_data;
582
+
583
+ static
584
+ VALUE protected_callback(VALUE rdata) {
585
+ protected_callback_data* data = (protected_callback_data*)rdata;
586
+ VALUE result;
587
+
588
+ if (data->length > 0) {
589
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length, data->args);
590
+ } else {
591
+ result = rb_funcall(data->callback, rb_intern("call"), 0);
592
+ }
593
+ return result;
594
+ }
595
+
596
+ static
597
+ VALUE rescue_callback(VALUE rdata, VALUE exception) {
598
+ protected_callback_data* data = (protected_callback_data*)rdata;
599
+ data->failed = true;
600
+ return exception;
601
+ }
602
+
603
+ void*
604
+ gvl_ruby_callback(void* data) {
605
+
606
+ FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
607
+ VALUE* ruby_args = NULL;
608
+ int length = args->Length();
609
+ VALUE callback;
610
+ VALUE result;
611
+ VALUE self;
612
+
613
+ {
614
+ HandleScope scope(args->GetIsolate());
615
+ Handle<External> external = Handle<External>::Cast(args->Data());
616
+
617
+ VALUE* self_pointer = (VALUE*)(external->Value());
618
+ self = *self_pointer;
619
+ callback = rb_iv_get(self, "@callback");
620
+
621
+ if (length > 0) {
622
+ ruby_args = ALLOC_N(VALUE, length);
623
+ }
624
+
625
+
626
+ for (int i = 0; i < length; i++) {
627
+ Local<Value> value = ((*args)[i]).As<Value>();
628
+ ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
629
+ }
630
+ }
631
+
632
+ // may raise exception stay clear of handle scope
633
+ protected_callback_data callback_data;
634
+ callback_data.length = length;
635
+ callback_data.callback = callback;
636
+ callback_data.args = ruby_args;
637
+ callback_data.failed = false;
638
+
639
+ if ((bool)args->GetIsolate()->GetData(1) == true) {
640
+ args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during tansition from Ruby to JS"));
641
+ V8::TerminateExecution(args->GetIsolate());
642
+ return NULL;
643
+ }
644
+
645
+ result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
646
+ (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
647
+
648
+ if(callback_data.failed) {
649
+ VALUE parent = rb_iv_get(self, "@parent");
650
+ rb_iv_set(parent, "@current_exception", result);
651
+ args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
652
+ }
653
+ else {
654
+ HandleScope scope(args->GetIsolate());
655
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
656
+ args->GetReturnValue().Set(v8_result);
657
+ }
658
+
659
+ if (length > 0) {
660
+ xfree(ruby_args);
661
+ }
662
+
663
+ if ((bool)args->GetIsolate()->GetData(1) == true) {
664
+ Isolate* isolate = args->GetIsolate();
665
+ V8::TerminateExecution(isolate);
666
+ }
667
+
668
+ return NULL;
669
+ }
670
+
671
+ static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
672
+
673
+ bool has_gvl = (bool)args.GetIsolate()->GetData(0);
674
+
675
+ if(has_gvl) {
676
+ gvl_ruby_callback((void*)&args);
677
+ } else {
678
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
679
+ }
680
+ }
681
+
682
+
683
+ static VALUE rb_external_function_notify_v8(VALUE self) {
684
+
685
+ ContextInfo* context_info;
686
+
687
+ VALUE parent = rb_iv_get(self, "@parent");
688
+ VALUE name = rb_iv_get(self, "@name");
689
+ VALUE parent_object = rb_iv_get(self, "@parent_object");
690
+ VALUE parent_object_eval = rb_iv_get(self, "@parent_object_eval");
691
+
692
+ bool parse_error = false;
693
+ bool attach_error = false;
694
+
695
+ Data_Get_Struct(parent, ContextInfo, context_info);
696
+ Isolate* isolate = context_info->isolate_info->isolate;
697
+
698
+ {
699
+ Locker lock(isolate);
700
+ Isolate::Scope isolate_scope(isolate);
701
+ HandleScope handle_scope(isolate);
702
+
703
+ Local<Context> context = context_info->context->Get(isolate);
704
+ Context::Scope context_scope(context);
705
+
706
+ Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
707
+ NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
708
+
709
+ // copy self so we can access from v8 external
710
+ VALUE* self_copy;
711
+ Data_Get_Struct(self, VALUE, self_copy);
712
+ *self_copy = self;
713
+
714
+ Local<Value> external = External::New(isolate, self_copy);
715
+
716
+ if (parent_object == Qnil) {
717
+ context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
718
+ } else {
719
+
720
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
721
+ NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
722
+
723
+ MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
724
+ if (parsed_script.IsEmpty()) {
725
+ parse_error = true;
726
+ } else {
727
+ MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
728
+ attach_error = true;
729
+
730
+ if (!maybe_value.IsEmpty()) {
731
+ Local<Value> value = maybe_value.ToLocalChecked();
732
+ if (value->IsObject()){
733
+ value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
734
+ attach_error = false;
735
+ }
736
+ }
737
+ }
738
+ }
739
+ }
740
+
741
+ // always raise out of V8 context
742
+ if (parse_error) {
743
+ rb_raise(rb_eParseError, "Invalid object %s", RSTRING_PTR(parent_object));
744
+ }
745
+
746
+ if (attach_error) {
747
+ rb_raise(rb_eParseError, "Was expecting %s to be an object", RSTRING_PTR(parent_object));
748
+ }
749
+
750
+ return Qnil;
751
+ }
752
+
753
+ void maybe_free_isolate_info(IsolateInfo* isolate_info) {
754
+ // an isolate can only be freed if no Isolate or Context (ruby) object
755
+ // still need it
756
+ if (isolate_info == NULL || isolate_info->refs_count > 0) {
757
+ return;
758
+ }
759
+
760
+ if (isolate_info->isolate) {
761
+ Locker lock(isolate_info->isolate);
762
+ }
763
+
764
+ if (isolate_info->isolate) {
765
+ if (isolate_info->interrupted) {
766
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.");
767
+ } else {
768
+
769
+ isolate_info->isolate->Dispose();
770
+ }
771
+ isolate_info->isolate = NULL;
772
+ }
773
+
774
+ if (isolate_info->startup_data) {
775
+ delete[] isolate_info->startup_data->data;
776
+ delete isolate_info->startup_data;
777
+ }
778
+
779
+ delete isolate_info->allocator;
780
+ xfree(isolate_info);
781
+ }
782
+
783
+ void deallocate_isolate(void* data) {
784
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
785
+
786
+ isolate_info->refs_count--;
787
+
788
+ maybe_free_isolate_info(isolate_info);
789
+ }
790
+
791
+ void deallocate(void* data) {
792
+ ContextInfo* context_info = (ContextInfo*)data;
793
+ IsolateInfo* isolate_info = context_info->isolate_info;
794
+
795
+ if (context_info->context && isolate_info && isolate_info->isolate) {
796
+ Locker lock(isolate_info->isolate);
797
+ v8::Isolate::Scope isolate_scope(isolate_info->isolate);
798
+ context_info->context->Reset();
799
+ delete context_info->context;
800
+ }
801
+
802
+ if (isolate_info) {
803
+ isolate_info->refs_count--;
804
+ maybe_free_isolate_info(isolate_info);
805
+ }
806
+ }
807
+
808
+ void deallocate_external_function(void * data) {
809
+ xfree(data);
810
+ }
811
+
812
+ void deallocate_snapshot(void * data) {
813
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
814
+
815
+ delete[] snapshot_info->data;
816
+
817
+ xfree(snapshot_info);
818
+ }
819
+
820
+ VALUE allocate_external_function(VALUE klass) {
821
+ VALUE* self = ALLOC(VALUE);
822
+ return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
823
+ }
824
+
825
+ VALUE allocate(VALUE klass) {
826
+ ContextInfo* context_info = ALLOC(ContextInfo);
827
+ context_info->isolate_info = NULL;
828
+ context_info->context = NULL;
829
+
830
+ return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
831
+ }
832
+
833
+ VALUE allocate_snapshot(VALUE klass) {
834
+ SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
835
+ snapshot_info->data = NULL;
836
+ snapshot_info->raw_size = 0;
837
+
838
+ return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
839
+ }
840
+
841
+ VALUE allocate_isolate(VALUE klass) {
842
+ IsolateInfo* isolate_info = ALLOC(IsolateInfo);
843
+
844
+ isolate_info->isolate = NULL;
845
+ isolate_info->allocator = NULL;
846
+ isolate_info->startup_data = NULL;
847
+ isolate_info->interrupted = false;
848
+ isolate_info->refs_count = 0;
849
+
850
+ return Data_Wrap_Struct(klass, NULL, deallocate_isolate, (void*)isolate_info);
851
+ }
852
+
853
+ static VALUE
854
+ rb_context_stop(VALUE self) {
855
+
856
+ ContextInfo* context_info;
857
+ Data_Get_Struct(self, ContextInfo, context_info);
858
+
859
+ Isolate* isolate = context_info->isolate_info->isolate;
860
+
861
+ // flag for termination
862
+ isolate->SetData(1, (void*)true);
863
+
864
+ V8::TerminateExecution(isolate);
865
+ rb_funcall(self, rb_intern("stop_attached"), 0);
866
+
867
+ return Qnil;
868
+ }
869
+
870
+ extern "C" {
871
+
872
+ void Init_mini_racer_extension ( void )
873
+ {
874
+ VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
875
+ VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
876
+ VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
877
+ VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
878
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
879
+
880
+ VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
881
+ rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
882
+ rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
883
+ rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
884
+ rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
885
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eStandardError);
886
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eStandardError);
887
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
888
+
889
+ VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
890
+ rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
891
+ rb_define_alloc_func(rb_cContext, allocate);
892
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
893
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
894
+
895
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
896
+ rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
897
+ rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
898
+ rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
899
+
900
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
901
+ rb_define_method(rb_cSnapshot, "warmup!", (VALUE(*)(...))&rb_snapshot_warmup, 1);
902
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
903
+
904
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
905
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
906
+
907
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
908
+ }
909
+
910
+ }