mini_racer 0.1.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,108 @@
1
1
  #include <stdio.h>
2
2
  #include <ruby.h>
3
3
  #include <ruby/thread.h>
4
- #include <include/v8.h>
5
- #include <include/libplatform/libplatform.h>
4
+ #include <ruby/io.h>
5
+ #include <v8.h>
6
+ #include <v8-profiler.h>
7
+ #include <libplatform/libplatform.h>
6
8
  #include <ruby/encoding.h>
7
9
  #include <pthread.h>
8
10
  #include <unistd.h>
11
+ #include <mutex>
12
+ #include <atomic>
13
+ #include <math.h>
9
14
 
10
15
  using namespace v8;
11
16
 
12
- class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
13
- public:
14
- virtual void* Allocate(size_t length) {
15
- void* data = AllocateUninitialized(length);
16
- return data == NULL ? data : memset(data, 0, length);
17
- }
18
- virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
19
- virtual void Free(void* data, size_t) { free(data); }
17
+ typedef struct {
18
+ const char* data;
19
+ int raw_size;
20
+ } SnapshotInfo;
21
+
22
+ class IsolateInfo {
23
+ public:
24
+ Isolate* isolate;
25
+ ArrayBuffer::Allocator* allocator;
26
+ StartupData* startup_data;
27
+ bool interrupted;
28
+ bool added_gc_cb;
29
+ pid_t pid;
30
+ VALUE mutex;
31
+
32
+ class Lock {
33
+ VALUE &mutex;
34
+
35
+ public:
36
+ Lock(VALUE &mutex) : mutex(mutex) {
37
+ rb_mutex_lock(mutex);
38
+ }
39
+ ~Lock() {
40
+ rb_mutex_unlock(mutex);
41
+ }
42
+ };
43
+
44
+
45
+ IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
46
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
47
+ VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
48
+ mutex = rb_class_new_instance(0, nullptr, cMutex);
49
+ }
50
+
51
+ ~IsolateInfo();
52
+
53
+ void init(SnapshotInfo* snapshot_info = nullptr);
54
+
55
+ void mark() {
56
+ rb_gc_mark(mutex);
57
+ }
58
+
59
+ Lock createLock() {
60
+ Lock lock(mutex);
61
+ return lock;
62
+ }
63
+
64
+ void hold() {
65
+ refs_count++;
66
+ }
67
+ void release() {
68
+ if (--refs_count <= 0) {
69
+ delete this;
70
+ }
71
+ }
72
+
73
+ int refs() {
74
+ return refs_count;
75
+ }
76
+
77
+ static void* operator new(size_t size) {
78
+ return ruby_xmalloc(size);
79
+ }
80
+
81
+ static void operator delete(void *block) {
82
+ xfree(block);
83
+ }
84
+ private:
85
+ // how many references to this isolate exist
86
+ // we can't rely on Ruby's GC for this, because Ruby could destroy the
87
+ // isolate before destroying the contexts that depend on them. We'd need to
88
+ // keep a list of linked contexts in the isolate to destroy those first when
89
+ // isolate destruction was requested. Keeping such a list would require
90
+ // notification from the context VALUEs when they are constructed and
91
+ // destroyed. With a ref count, those notifications are still needed, but
92
+ // we keep a simple int rather than a list of pointers.
93
+ std::atomic_int refs_count;
20
94
  };
21
95
 
22
96
  typedef struct {
23
- Isolate* isolate;
97
+ IsolateInfo* isolate_info;
24
98
  Persistent<Context>* context;
25
- ArrayBufferAllocator* allocator;
26
- bool interrupted;
27
99
  } ContextInfo;
28
100
 
29
101
  typedef struct {
30
102
  bool parsed;
31
103
  bool executed;
32
104
  bool terminated;
105
+ bool json;
33
106
  Persistent<Value>* value;
34
107
  Persistent<Value>* message;
35
108
  Persistent<Value>* backtrace;
@@ -38,168 +111,575 @@ typedef struct {
38
111
  typedef struct {
39
112
  ContextInfo* context_info;
40
113
  Local<String>* eval;
114
+ Local<String>* filename;
41
115
  useconds_t timeout;
42
116
  EvalResult* result;
117
+ size_t max_memory;
118
+ size_t marshal_stackdepth;
43
119
  } EvalParams;
44
120
 
121
+ typedef struct {
122
+ ContextInfo *context_info;
123
+ char *function_name;
124
+ int argc;
125
+ bool error;
126
+ Local<Function> fun;
127
+ Local<Value> *argv;
128
+ EvalResult result;
129
+ size_t max_memory;
130
+ size_t marshal_stackdepth;
131
+ } FunctionCall;
132
+
133
+ class IsolateData {
134
+ public:
135
+ enum Flag {
136
+ // first flags are bitfield
137
+ // max count: sizeof(uintptr_t) * 8
138
+ IN_GVL, // whether we are inside of ruby gvl or not
139
+ DO_TERMINATE, // terminate as soon as possible
140
+ MEM_SOFTLIMIT_REACHED, // we've hit the memory soft limit
141
+ MEM_SOFTLIMIT_MAX, // maximum memory value
142
+ MARSHAL_STACKDEPTH_REACHED, // we've hit our max stack depth
143
+ MARSHAL_STACKDEPTH_VALUE, // current stackdepth
144
+ MARSHAL_STACKDEPTH_MAX, // maximum stack depth during marshal
145
+ };
146
+
147
+ static void Init(Isolate *isolate) {
148
+ // zero out all fields in the bitfield
149
+ isolate->SetData(0, 0);
150
+ }
151
+
152
+ static uintptr_t Get(Isolate *isolate, Flag flag) {
153
+ Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
154
+ switch (flag) {
155
+ case IN_GVL: return u.IN_GVL;
156
+ case DO_TERMINATE: return u.DO_TERMINATE;
157
+ case MEM_SOFTLIMIT_REACHED: return u.MEM_SOFTLIMIT_REACHED;
158
+ case MEM_SOFTLIMIT_MAX: return static_cast<uintptr_t>(u.MEM_SOFTLIMIT_MAX) << 10;
159
+ case MARSHAL_STACKDEPTH_REACHED: return u.MARSHAL_STACKDEPTH_REACHED;
160
+ case MARSHAL_STACKDEPTH_VALUE: return u.MARSHAL_STACKDEPTH_VALUE;
161
+ case MARSHAL_STACKDEPTH_MAX: return u.MARSHAL_STACKDEPTH_MAX;
162
+ }
163
+
164
+ // avoid compiler warning
165
+ return u.IN_GVL;
166
+ }
167
+
168
+ static void Set(Isolate *isolate, Flag flag, uintptr_t value) {
169
+ Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
170
+ switch (flag) {
171
+ case IN_GVL: u.IN_GVL = value; break;
172
+ case DO_TERMINATE: u.DO_TERMINATE = value; break;
173
+ case MEM_SOFTLIMIT_REACHED: u.MEM_SOFTLIMIT_REACHED = value; break;
174
+ // drop least significant 10 bits 'store memory amount in kb'
175
+ case MEM_SOFTLIMIT_MAX: u.MEM_SOFTLIMIT_MAX = value >> 10; break;
176
+ case MARSHAL_STACKDEPTH_REACHED: u.MARSHAL_STACKDEPTH_REACHED = value; break;
177
+ case MARSHAL_STACKDEPTH_VALUE: u.MARSHAL_STACKDEPTH_VALUE = value; break;
178
+ case MARSHAL_STACKDEPTH_MAX: u.MARSHAL_STACKDEPTH_MAX = value; break;
179
+ }
180
+ isolate->SetData(0, reinterpret_cast<void*>(u.dataPtr));
181
+ }
182
+
183
+ private:
184
+ struct Bitfield {
185
+ // WARNING: this would explode on platforms below 64 bit ptrs
186
+ // compiler will fail here, making it clear for them.
187
+ // Additionally, using the other part of the union to reinterpret the
188
+ // memory is undefined behavior according to spec, but is / has been stable
189
+ // across major compilers for decades.
190
+ static_assert(sizeof(uintptr_t) >= sizeof(uint64_t), "mini_racer not supported on this platform. ptr size must be at least 64 bit.");
191
+ union {
192
+ uint64_t dataPtr: 64;
193
+ // order in this struct matters. For cpu performance keep larger subobjects
194
+ // aligned on their boundaries (8 16 32), try not to straddle
195
+ struct {
196
+ size_t MEM_SOFTLIMIT_MAX:22;
197
+ bool IN_GVL:1;
198
+ bool DO_TERMINATE:1;
199
+ bool MEM_SOFTLIMIT_REACHED:1;
200
+ bool MARSHAL_STACKDEPTH_REACHED:1;
201
+ uint8_t :0; // align to next 8bit bound
202
+ size_t MARSHAL_STACKDEPTH_VALUE:10;
203
+ uint8_t :0; // align to next 8bit bound
204
+ size_t MARSHAL_STACKDEPTH_MAX:10;
205
+ };
206
+ };
207
+ };
208
+ };
209
+
210
+ struct StackCounter {
211
+ static void Reset(Isolate* isolate) {
212
+ if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0) {
213
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
214
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
215
+ }
216
+ }
217
+
218
+ static void SetMax(Isolate* isolate, size_t marshalMaxStackDepth) {
219
+ if (marshalMaxStackDepth > 0) {
220
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX, marshalMaxStackDepth);
221
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
222
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
223
+ }
224
+ }
225
+
226
+ StackCounter(Isolate* isolate) {
227
+ this->isActive = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0;
228
+
229
+ if (this->isActive) {
230
+ this->isolate = isolate;
231
+ this->IncDepth(1);
232
+ }
233
+ }
234
+
235
+ bool IsTooDeep() {
236
+ if (!this->IsActive()) {
237
+ return false;
238
+ }
239
+
240
+ size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
241
+ size_t maxDepth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_MAX);
242
+ if (depth > maxDepth) {
243
+ IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, true);
244
+ return true;
245
+ }
246
+
247
+ return false;
248
+ }
249
+
250
+ bool IsActive() {
251
+ return this->isActive && !IsolateData::Get(this->isolate, IsolateData::DO_TERMINATE);
252
+ }
253
+
254
+ ~StackCounter() {
255
+ if (this->IsActive()) {
256
+ this->IncDepth(-1);
257
+ }
258
+ }
259
+
260
+ private:
261
+ Isolate* isolate;
262
+ bool isActive;
263
+
264
+ void IncDepth(int direction) {
265
+ int inc = direction > 0 ? 1 : -1;
266
+
267
+ size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
268
+
269
+ // don't decrement past 0
270
+ if (inc > 0 || depth > 0) {
271
+ depth += inc;
272
+ }
273
+
274
+ IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, depth);
275
+ }
276
+ };
277
+
278
+ static VALUE rb_cContext;
279
+ static VALUE rb_cSnapshot;
280
+ static VALUE rb_cIsolate;
281
+
45
282
  static VALUE rb_eScriptTerminatedError;
283
+ static VALUE rb_eV8OutOfMemoryError;
46
284
  static VALUE rb_eParseError;
47
285
  static VALUE rb_eScriptRuntimeError;
48
286
  static VALUE rb_cJavaScriptFunction;
287
+ static VALUE rb_eSnapshotError;
288
+ static VALUE rb_ePlatformAlreadyInitializedError;
289
+ static VALUE rb_mJSON;
290
+
291
+ static VALUE rb_cFailedV8Conversion;
292
+ static VALUE rb_cDateTime = Qnil;
49
293
 
50
- static Platform* current_platform = NULL;
294
+ static std::unique_ptr<Platform> current_platform = NULL;
295
+ static std::mutex platform_lock;
296
+
297
+ static pthread_attr_t *thread_attr_p;
298
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
299
+ static bool ruby_exiting = false; // guarded by exit_lock
300
+ static bool single_threaded = false;
301
+
302
+ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
303
+ bool platform_already_initialized = false;
304
+
305
+ if(TYPE(flag_as_str) != T_STRING) {
306
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
307
+ rb_obj_class(flag_as_str));
308
+ }
309
+
310
+ platform_lock.lock();
311
+
312
+ if (current_platform == NULL) {
313
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
314
+ single_threaded = true;
315
+ }
316
+ V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
317
+ } else {
318
+ platform_already_initialized = true;
319
+ }
320
+
321
+ platform_lock.unlock();
322
+
323
+ // important to raise outside of the lock
324
+ if (platform_already_initialized) {
325
+ rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
326
+ }
327
+
328
+ return Qnil;
329
+ }
51
330
 
52
331
  static void init_v8() {
332
+ // no need to wait for the lock if already initialized
333
+ if (current_platform != NULL) return;
334
+
335
+ platform_lock.lock();
336
+
53
337
  if (current_platform == NULL) {
54
- V8::InitializeICU();
55
- current_platform = platform::CreateDefaultPlatform();
56
- V8::InitializePlatform(current_platform);
57
- V8::Initialize();
338
+ V8::InitializeICU();
339
+ current_platform = platform::NewDefaultPlatform();
340
+ V8::InitializePlatform(current_platform.get());
341
+ V8::Initialize();
342
+ }
343
+
344
+ platform_lock.unlock();
345
+ }
346
+
347
+ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
348
+ if (IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED)) {
349
+ return;
350
+ }
351
+
352
+ size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_MAX);
353
+
354
+ HeapStatistics stats;
355
+ isolate->GetHeapStatistics(&stats);
356
+ size_t used = stats.used_heap_size();
357
+
358
+ if(used > softlimit) {
359
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, true);
360
+ isolate->TerminateExecution();
58
361
  }
59
362
  }
60
363
 
61
- void* breaker(void *d) {
62
- EvalParams* data = (EvalParams*)d;
63
- usleep(data->timeout*1000);
64
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
65
- V8::TerminateExecution(data->context_info->isolate);
66
- return NULL;
364
+ // to be called with active lock and scope
365
+ static void prepare_result(MaybeLocal<Value> v8res,
366
+ TryCatch& trycatch,
367
+ Isolate* isolate,
368
+ Local<Context> context,
369
+ EvalResult& evalRes /* out */) {
370
+
371
+ // just don't touch .parsed
372
+ evalRes.terminated = false;
373
+ evalRes.json = false;
374
+ evalRes.value = nullptr;
375
+ evalRes.message = nullptr;
376
+ evalRes.backtrace = nullptr;
377
+ evalRes.executed = !v8res.IsEmpty();
378
+
379
+ if (evalRes.executed) {
380
+ // arrays and objects get converted to json
381
+ Local<Value> local_value = v8res.ToLocalChecked();
382
+ if ((local_value->IsObject() || local_value->IsArray()) &&
383
+ !local_value->IsDate() && !local_value->IsFunction()) {
384
+ Local<Object> JSON = context->Global()->Get(
385
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
386
+ .ToLocalChecked().As<Object>();
387
+
388
+ Local<Function> stringify = JSON->Get(
389
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
390
+ .ToLocalChecked().As<Function>();
391
+
392
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
393
+ const unsigned argc = 1;
394
+ Local<Value> argv[argc] = { object };
395
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
396
+
397
+ if (json.IsEmpty()) {
398
+ evalRes.executed = false;
399
+ } else {
400
+ evalRes.json = true;
401
+ Persistent<Value>* persistent = new Persistent<Value>();
402
+ persistent->Reset(isolate, json.ToLocalChecked());
403
+ evalRes.value = persistent;
404
+ }
405
+
406
+ } else {
407
+ Persistent<Value>* persistent = new Persistent<Value>();
408
+ persistent->Reset(isolate, local_value);
409
+ evalRes.value = persistent;
410
+ }
411
+ }
412
+
413
+ if (!evalRes.executed || !evalRes.parsed) {
414
+ if (trycatch.HasCaught()) {
415
+ if (!trycatch.Exception()->IsNull()) {
416
+ evalRes.message = new Persistent<Value>();
417
+ Local<Message> message = trycatch.Message();
418
+ char buf[1000];
419
+ int len, line, column;
420
+
421
+ if (!message->GetLineNumber(context).To(&line)) {
422
+ line = 0;
423
+ }
424
+
425
+ if (!message->GetStartColumn(context).To(&column)) {
426
+ column = 0;
427
+ }
428
+
429
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
430
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
431
+ line,
432
+ column);
433
+
434
+ if ((size_t) len >= sizeof(buf)) {
435
+ len = sizeof(buf) - 1;
436
+ buf[len] = '\0';
437
+ }
438
+
439
+ Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
440
+ evalRes.message->Reset(isolate, v8_message);
441
+ } else if(trycatch.HasTerminated()) {
442
+ evalRes.terminated = true;
443
+ evalRes.message = new Persistent<Value>();
444
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
445
+ evalRes.message->Reset(isolate, tmp);
446
+ }
447
+ if (!trycatch.StackTrace(context).IsEmpty()) {
448
+ evalRes.backtrace = new Persistent<Value>();
449
+ evalRes.backtrace->Reset(isolate,
450
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
451
+ }
452
+ }
453
+ }
67
454
  }
68
455
 
69
456
  void*
70
457
  nogvl_context_eval(void* arg) {
458
+
71
459
  EvalParams* eval_params = (EvalParams*)arg;
72
460
  EvalResult* result = eval_params->result;
73
- Isolate* isolate = eval_params->context_info->isolate;
461
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
462
+ Isolate* isolate = isolate_info->isolate;
463
+
74
464
  Isolate::Scope isolate_scope(isolate);
75
465
  HandleScope handle_scope(isolate);
76
-
77
466
  TryCatch trycatch(isolate);
78
-
79
467
  Local<Context> context = eval_params->context_info->context->Get(isolate);
80
-
81
468
  Context::Scope context_scope(context);
469
+ v8::ScriptOrigin *origin = NULL;
470
+
471
+ IsolateData::Init(isolate);
472
+
473
+ if (eval_params->max_memory > 0) {
474
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, eval_params->max_memory);
475
+ if (!isolate_info->added_gc_cb) {
476
+ isolate->AddGCEpilogueCallback(gc_callback);
477
+ isolate_info->added_gc_cb = true;
478
+ }
479
+ }
480
+
481
+ MaybeLocal<Script> parsed_script;
482
+
483
+ if (eval_params->filename) {
484
+ origin = new v8::ScriptOrigin(*eval_params->filename);
485
+ }
486
+
487
+ parsed_script = Script::Compile(context, *eval_params->eval, origin);
488
+
489
+ if (origin) {
490
+ delete origin;
491
+ }
82
492
 
83
- MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
84
493
  result->parsed = !parsed_script.IsEmpty();
85
494
  result->executed = false;
86
495
  result->terminated = false;
496
+ result->json = false;
87
497
  result->value = NULL;
88
498
 
499
+ MaybeLocal<Value> maybe_value;
89
500
  if (!result->parsed) {
90
- result->message = new Persistent<Value>();
91
- result->message->Reset(isolate, trycatch.Exception());
501
+ result->message = new Persistent<Value>();
502
+ result->message->Reset(isolate, trycatch.Exception());
92
503
  } else {
504
+ // parsing successful
505
+ if (eval_params->marshal_stackdepth > 0) {
506
+ StackCounter::SetMax(isolate, eval_params->marshal_stackdepth);
507
+ }
93
508
 
94
- pthread_t breaker_thread;
95
-
96
- if (eval_params->timeout > 0) {
97
- pthread_create(&breaker_thread, NULL, breaker, (void*)eval_params);
98
- }
509
+ maybe_value = parsed_script.ToLocalChecked()->Run(context);
510
+ }
99
511
 
100
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
512
+ prepare_result(maybe_value, trycatch, isolate, context, *result);
101
513
 
102
- if (eval_params->timeout > 0) {
103
- pthread_cancel(breaker_thread);
104
- pthread_join(breaker_thread, NULL);
105
- }
106
-
107
- result->executed = !maybe_value.IsEmpty();
108
-
109
- if (!result->executed) {
110
- if (trycatch.HasCaught()) {
111
- if (!trycatch.Exception()->IsNull()) {
112
- result->message = new Persistent<Value>();
113
- result->message->Reset(isolate, trycatch.Exception()->ToString());
114
- } else if(trycatch.HasTerminated()) {
115
- result->terminated = true;
116
- result->message = new Persistent<Value>();
117
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
118
- result->message->Reset(isolate, tmp);
119
- }
120
-
121
- if (!trycatch.StackTrace().IsEmpty()) {
122
- result->backtrace = new Persistent<Value>();
123
- result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
124
- }
125
- }
126
- } else {
127
- Persistent<Value>* persistent = new Persistent<Value>();
128
- persistent->Reset(isolate, maybe_value.ToLocalChecked());
129
- result->value = persistent;
130
- }
131
- }
514
+ IsolateData::Set(isolate, IsolateData::IN_GVL, true);
132
515
 
133
516
  return NULL;
134
517
  }
135
518
 
136
- static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
519
+ static VALUE new_empty_failed_conv_obj() {
520
+ // TODO isolate code that translates execption to ruby
521
+ // exception so we can properly return it
522
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
523
+ }
524
+
525
+ // assumes isolate locking is in place
526
+ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
527
+ Local<Value> value) {
137
528
 
529
+ Isolate::Scope isolate_scope(isolate);
138
530
  HandleScope scope(isolate);
139
531
 
532
+ StackCounter stackCounter(isolate);
533
+
534
+ if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED)) {
535
+ return Qnil;
536
+ }
537
+
538
+ if (stackCounter.IsTooDeep()) {
539
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
540
+ isolate->TerminateExecution();
541
+ return Qnil;
542
+ }
543
+
140
544
  if (value->IsNull() || value->IsUndefined()){
141
- return Qnil;
545
+ return Qnil;
142
546
  }
143
547
 
144
548
  if (value->IsInt32()) {
145
- return INT2FIX(value->Int32Value());
549
+ return INT2FIX(value->Int32Value(context).ToChecked());
146
550
  }
147
551
 
148
552
  if (value->IsNumber()) {
149
- return rb_float_new(value->NumberValue());
553
+ return rb_float_new(value->NumberValue(context).ToChecked());
150
554
  }
151
555
 
152
556
  if (value->IsTrue()) {
153
- return Qtrue;
557
+ return Qtrue;
154
558
  }
155
559
 
156
560
  if (value->IsFalse()) {
157
- return Qfalse;
561
+ return Qfalse;
158
562
  }
159
563
 
160
564
  if (value->IsArray()) {
161
565
  VALUE rb_array = rb_ary_new();
162
566
  Local<Array> arr = Local<Array>::Cast(value);
163
567
  for(uint32_t i=0; i < arr->Length(); i++) {
164
- Local<Value> element = arr->Get(i);
165
- VALUE rb_elem = convert_v8_to_ruby(isolate, element);
568
+ MaybeLocal<Value> element = arr->Get(context, i);
569
+ if (element.IsEmpty()) {
570
+ continue;
571
+ }
572
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
573
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
574
+ return rb_elem;
575
+ }
166
576
  rb_ary_push(rb_array, rb_elem);
167
577
  }
168
578
  return rb_array;
169
579
  }
170
580
 
171
581
  if (value->IsFunction()){
172
- return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
582
+ return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
583
+ }
584
+
585
+ if (value->IsDate()){
586
+ double ts = Local<Date>::Cast(value)->ValueOf();
587
+ double secs = ts/1000;
588
+ long nanos = round((secs - floor(secs)) * 1000000);
589
+
590
+ return rb_time_new(secs, nanos);
173
591
  }
174
592
 
175
593
  if (value->IsObject()) {
176
- VALUE rb_hash = rb_hash_new();
177
- Local<Context> context = Context::New(isolate);
178
- Local<Object> object = value->ToObject();
179
- MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
180
- if (!maybe_props.IsEmpty()) {
181
- Local<Array> props = maybe_props.ToLocalChecked();
182
- for(uint32_t i=0; i < props->Length(); i++) {
183
- Local<Value> key = props->Get(i);
184
- VALUE rb_key = convert_v8_to_ruby(isolate, key);
185
- Local<Value> value = object->Get(key);
186
- VALUE rb_value = convert_v8_to_ruby(isolate, value);
187
- rb_hash_aset(rb_hash, rb_key, rb_value);
188
- }
189
- }
190
- return rb_hash;
594
+ VALUE rb_hash = rb_hash_new();
595
+ TryCatch trycatch(isolate);
596
+
597
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
598
+ auto maybe_props = object->GetOwnPropertyNames(context);
599
+ if (!maybe_props.IsEmpty()) {
600
+ Local<Array> props = maybe_props.ToLocalChecked();
601
+ for(uint32_t i=0; i < props->Length(); i++) {
602
+ MaybeLocal<Value> key = props->Get(context, i);
603
+ if (key.IsEmpty()) {
604
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
605
+ }
606
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
607
+
608
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
609
+ // this may have failed due to Get raising
610
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
611
+ return new_empty_failed_conv_obj();
612
+ }
613
+
614
+ VALUE rb_value = convert_v8_to_ruby(
615
+ isolate, context, prop_value.ToLocalChecked());
616
+ rb_hash_aset(rb_hash, rb_key, rb_value);
617
+ }
618
+ }
619
+ return rb_hash;
191
620
  }
192
621
 
193
- Local<String> rstr = value->ToString();
194
- return rb_enc_str_new(*v8::String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
622
+ if (value->IsSymbol()) {
623
+ v8::String::Utf8Value symbol_name(isolate,
624
+ Local<Symbol>::Cast(value)->Name());
625
+
626
+ VALUE str_symbol = rb_enc_str_new(
627
+ *symbol_name,
628
+ symbol_name.length(),
629
+ rb_enc_find("utf-8")
630
+ );
631
+
632
+ return rb_str_intern(str_symbol);
633
+ }
634
+
635
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
636
+
637
+ if (rstr_maybe.IsEmpty()) {
638
+ return Qnil;
639
+ } else {
640
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
641
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
642
+ }
195
643
  }
196
644
 
197
- static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
645
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
646
+ const Persistent<Context>& context,
647
+ Local<Value> value) {
648
+ HandleScope scope(isolate);
649
+ return convert_v8_to_ruby(isolate,
650
+ Local<Context>::New(isolate, context),
651
+ value);
652
+ }
653
+
654
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
655
+ const Persistent<Context>& context,
656
+ const Persistent<Value>& value) {
657
+ HandleScope scope(isolate);
658
+ return convert_v8_to_ruby(isolate,
659
+ Local<Context>::New(isolate, context),
660
+ Local<Value>::New(isolate, value));
661
+ }
662
+
663
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
198
664
  EscapableHandleScope scope(isolate);
199
665
 
666
+ Local<Array> array;
667
+ Local<Object> object;
668
+ VALUE hash_as_array;
669
+ VALUE pair;
670
+ int i;
671
+ long length;
672
+ long fixnum;
673
+ VALUE klass;
674
+
200
675
  switch (TYPE(value)) {
201
676
  case T_FIXNUM:
202
- return scope.Escape(Integer::New(isolate, NUM2INT(value)));
677
+ fixnum = NUM2LONG(value);
678
+ if (fixnum > INT_MAX)
679
+ {
680
+ return scope.Escape(Number::New(isolate, (double)fixnum));
681
+ }
682
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
203
683
  case T_FLOAT:
204
684
  return scope.Escape(Number::New(isolate, NUM2DBL(value)));
205
685
  case T_STRING:
@@ -210,144 +690,495 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
210
690
  return scope.Escape(True(isolate));
211
691
  case T_FALSE:
212
692
  return scope.Escape(False(isolate));
693
+ case T_ARRAY:
694
+ length = RARRAY_LEN(value);
695
+ array = Array::New(isolate, (int)length);
696
+ for(i=0; i<length; i++) {
697
+ Maybe<bool> success = array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
698
+ (void)(success);
699
+ }
700
+ return scope.Escape(array);
701
+ case T_HASH:
702
+ object = Object::New(isolate);
703
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
704
+ length = RARRAY_LEN(hash_as_array);
705
+ for(i=0; i<length; i++) {
706
+ pair = rb_ary_entry(hash_as_array, i);
707
+ Maybe<bool> success = object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
708
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
709
+ (void)(success);
710
+ }
711
+ return scope.Escape(object);
712
+ case T_SYMBOL:
713
+ value = rb_funcall(value, rb_intern("to_s"), 0);
714
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
213
715
  case T_DATA:
716
+ klass = rb_funcall(value, rb_intern("class"), 0);
717
+ if (klass == rb_cTime || klass == rb_cDateTime)
718
+ {
719
+ if (klass == rb_cDateTime)
720
+ {
721
+ value = rb_funcall(value, rb_intern("to_time"), 0);
722
+ }
723
+ value = rb_funcall(value, rb_intern("to_f"), 0);
724
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
725
+ }
214
726
  case T_OBJECT:
215
727
  case T_CLASS:
216
728
  case T_ICLASS:
217
729
  case T_MODULE:
218
730
  case T_REGEXP:
219
731
  case T_MATCH:
220
- case T_ARRAY:
221
- case T_HASH:
222
732
  case T_STRUCT:
223
733
  case T_BIGNUM:
224
734
  case T_FILE:
225
- case T_SYMBOL:
226
735
  case T_UNDEF:
227
736
  case T_NODE:
228
737
  default:
229
- // rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value)));
230
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
738
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
739
+ }
231
740
  }
232
-
233
- }
234
741
 
235
742
  static void unblock_eval(void *ptr) {
236
743
  EvalParams* eval = (EvalParams*)ptr;
237
- eval->context_info->interrupted = true;
744
+ eval->context_info->isolate_info->interrupted = true;
238
745
  }
239
746
 
747
+ /*
748
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
749
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
750
+ */
751
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
752
+ const char *utf8_source, const char *name) {
753
+ Context::Scope context_scope(context);
754
+ TryCatch try_catch(isolate);
755
+ Local<String> source_string;
756
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
757
+ return false;
758
+ }
759
+ Local<String> resource_name =
760
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
761
+ ScriptOrigin origin(resource_name);
762
+ ScriptCompiler::Source source(source_string, origin);
763
+ Local<Script> script;
764
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
765
+ return false;
766
+ if (script->Run(context).IsEmpty()) return false;
767
+ return true;
768
+ }
240
769
 
241
- static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
770
+ static StartupData
771
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
772
+ Isolate *isolate = Isolate::Allocate();
773
+
774
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
775
+ SnapshotCreator snapshot_creator(isolate);
776
+ {
777
+ HandleScope scope(isolate);
778
+ Local<v8::Context> context = v8::Context::New(isolate);
779
+ if (embedded_source != nullptr &&
780
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
781
+ return {};
782
+ }
783
+ snapshot_creator.SetDefaultContext(context);
784
+ }
785
+ return snapshot_creator.CreateBlob(
786
+ SnapshotCreator::FunctionCodeHandling::kClear);
787
+ }
242
788
 
243
- EvalParams eval_params;
244
- EvalResult eval_result;
245
- ContextInfo* context_info;
246
- VALUE result;
789
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
790
+ const char *warmup_source) {
791
+ // Use following steps to create a warmed up snapshot blob from a cold one:
792
+ // - Create a new isolate from the cold snapshot.
793
+ // - Create a new context to run the warmup script. This will trigger
794
+ // compilation of executed functions.
795
+ // - Create a new context. This context will be unpolluted.
796
+ // - Serialize the isolate and the second context into a new snapshot blob.
797
+ StartupData result = {nullptr, 0};
798
+
799
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
800
+ warmup_source != NULL) {
801
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
802
+ Isolate *isolate = snapshot_creator.GetIsolate();
803
+ {
804
+ HandleScope scope(isolate);
805
+ Local<Context> context = Context::New(isolate);
806
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
807
+ return result;
808
+ }
809
+ }
810
+ {
811
+ HandleScope handle_scope(isolate);
812
+ isolate->ContextDisposedNotification(false);
813
+ Local<Context> context = Context::New(isolate);
814
+ snapshot_creator.SetDefaultContext(context);
815
+ }
816
+ result = snapshot_creator.CreateBlob(
817
+ SnapshotCreator::FunctionCodeHandling::kKeep);
818
+ }
819
+ return result;
820
+ }
247
821
 
248
- VALUE message = Qnil;
249
- VALUE backtrace = Qnil;
822
+ static VALUE rb_snapshot_size(VALUE self, VALUE str) {
823
+ SnapshotInfo* snapshot_info;
824
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
825
+
826
+ return INT2NUM(snapshot_info->raw_size);
827
+ }
828
+
829
+ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
830
+ SnapshotInfo* snapshot_info;
831
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
832
+
833
+ if(TYPE(str) != T_STRING) {
834
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
835
+ rb_obj_class(str));
836
+ }
837
+
838
+ init_v8();
839
+
840
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
841
+
842
+ if (startup_data.data == NULL && startup_data.raw_size == 0) {
843
+ rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
844
+ }
845
+
846
+ snapshot_info->data = startup_data.data;
847
+ snapshot_info->raw_size = startup_data.raw_size;
250
848
 
849
+ return Qnil;
850
+ }
851
+
852
+ static VALUE rb_snapshot_dump(VALUE self, VALUE str) {
853
+ SnapshotInfo* snapshot_info;
854
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
855
+
856
+ return rb_str_new(snapshot_info->data, snapshot_info->raw_size);
857
+ }
858
+
859
+ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
860
+ SnapshotInfo* snapshot_info;
861
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
862
+
863
+ if(TYPE(str) != T_STRING) {
864
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
865
+ rb_obj_class(str));
866
+ }
867
+
868
+ init_v8();
869
+
870
+ StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
871
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
872
+
873
+ if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
874
+ rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
875
+ } else {
876
+ delete[] snapshot_info->data;
877
+
878
+ snapshot_info->data = warm_startup_data.data;
879
+ snapshot_info->raw_size = warm_startup_data.raw_size;
880
+ }
881
+
882
+ return self;
883
+ }
884
+
885
+ void IsolateInfo::init(SnapshotInfo* snapshot_info) {
886
+ allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
887
+
888
+ Isolate::CreateParams create_params;
889
+ create_params.array_buffer_allocator = allocator;
890
+
891
+ if (snapshot_info) {
892
+ int raw_size = snapshot_info->raw_size;
893
+ char* data = new char[raw_size];
894
+ memcpy(data, snapshot_info->data, raw_size);
895
+
896
+ startup_data = new StartupData;
897
+ startup_data->data = data;
898
+ startup_data->raw_size = raw_size;
899
+
900
+ create_params.snapshot_blob = startup_data;
901
+ }
902
+
903
+ isolate = Isolate::New(create_params);
904
+ }
905
+
906
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
907
+ IsolateInfo* isolate_info;
908
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
909
+
910
+ init_v8();
911
+
912
+ SnapshotInfo* snapshot_info = nullptr;
913
+ if (!NIL_P(snapshot)) {
914
+ Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
915
+ }
916
+
917
+ isolate_info->init(snapshot_info);
918
+ isolate_info->hold();
919
+
920
+ return Qnil;
921
+ }
922
+
923
+ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
924
+ IsolateInfo* isolate_info;
925
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
926
+
927
+ if (current_platform == NULL) return Qfalse;
928
+
929
+ double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
930
+ double now = current_platform->MonotonicallyIncreasingTime();
931
+ return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
932
+ }
933
+
934
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
935
+ IsolateInfo* isolate_info;
936
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
937
+
938
+ if (current_platform == NULL) return Qfalse;
939
+
940
+ isolate_info->isolate->LowMemoryNotification();
941
+ return Qnil;
942
+ }
943
+
944
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
945
+ IsolateInfo* isolate_info;
946
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
947
+
948
+ if (current_platform == NULL) return Qfalse;
949
+
950
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
951
+ return Qtrue;
952
+ } else {
953
+ return Qfalse;
954
+ }
955
+ }
956
+
957
+ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
958
+ ContextInfo* context_info;
251
959
  Data_Get_Struct(self, ContextInfo, context_info);
252
960
 
961
+ init_v8();
962
+
963
+ IsolateInfo* isolate_info;
964
+
965
+ if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
966
+ isolate_info = new IsolateInfo();
967
+
968
+ SnapshotInfo *snapshot_info = nullptr;
969
+ if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
970
+ Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
971
+ }
972
+ isolate_info->init(snapshot_info);
973
+ } else { // given isolate or snapshot
974
+ Data_Get_Struct(isolate, IsolateInfo, isolate_info);
975
+ }
976
+
977
+ context_info->isolate_info = isolate_info;
978
+ isolate_info->hold();
979
+
253
980
  {
254
- Locker lock(context_info->isolate);
255
- Isolate::Scope isolate_scope(context_info->isolate);
256
- HandleScope handle_scope(context_info->isolate);
257
-
258
- Local<String> eval = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(str),
259
- NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
260
-
261
- eval_params.context_info = context_info;
262
- eval_params.eval = &eval;
263
- eval_params.result = &eval_result;
264
- eval_params.timeout = 0;
265
- VALUE timeout = rb_iv_get(self, "@timeout");
266
- if (timeout != Qnil) {
267
- eval_params.timeout = (useconds_t)NUM2LONG(timeout);
268
- }
981
+ // the ruby lock is needed if this isn't a new isolate
982
+ IsolateInfo::Lock ruby_lock(isolate_info->mutex);
983
+ Locker lock(isolate_info->isolate);
984
+ Isolate::Scope isolate_scope(isolate_info->isolate);
985
+ HandleScope handle_scope(isolate_info->isolate);
269
986
 
270
- eval_result.message = NULL;
271
- eval_result.backtrace = NULL;
987
+ Local<Context> context = Context::New(isolate_info->isolate);
272
988
 
273
- rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
989
+ context_info->context = new Persistent<Context>();
990
+ context_info->context->Reset(isolate_info->isolate, context);
991
+ }
274
992
 
275
- if (eval_result.message != NULL) {
276
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.message);
277
- message = convert_v8_to_ruby(context_info->isolate, tmp);
278
- eval_result.message->Reset();
279
- delete eval_result.message;
280
- }
993
+ if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
994
+ {
995
+ rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
996
+ }
281
997
 
282
- if (eval_result.backtrace != NULL) {
283
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.backtrace);
284
- backtrace = convert_v8_to_ruby(context_info->isolate, tmp);
285
- eval_result.backtrace->Reset();
286
- delete eval_result.backtrace;
287
- }
998
+ return Qnil;
999
+ }
1000
+
1001
+ static VALUE convert_result_to_ruby(VALUE self /* context */,
1002
+ EvalResult& result) {
1003
+ ContextInfo *context_info;
1004
+ Data_Get_Struct(self, ContextInfo, context_info);
1005
+
1006
+ Isolate *isolate = context_info->isolate_info->isolate;
1007
+ Persistent<Context> *p_ctx = context_info->context;
1008
+
1009
+ VALUE message = Qnil;
1010
+ VALUE backtrace = Qnil;
1011
+ {
1012
+ Locker lock(isolate);
1013
+ if (result.message) {
1014
+ message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
1015
+ result.message->Reset();
1016
+ delete result.message;
1017
+ result.message = nullptr;
1018
+ }
1019
+
1020
+ if (result.backtrace) {
1021
+ backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
1022
+ result.backtrace->Reset();
1023
+ delete result.backtrace;
1024
+ }
288
1025
  }
289
1026
 
290
1027
  // NOTE: this is very important, we can not do an rb_raise from within
291
1028
  // a v8 scope, if we do the scope is never cleaned up properly and we leak
292
- if (!eval_result.parsed) {
293
- if(TYPE(message) == T_STRING) {
294
- rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
295
- } else {
296
- rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
297
- }
1029
+ if (!result.parsed) {
1030
+ if(TYPE(message) == T_STRING) {
1031
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
1032
+ } else {
1033
+ rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
1034
+ }
298
1035
  }
299
1036
 
300
- if (!eval_result.executed) {
301
-
302
- VALUE ruby_exception = rb_iv_get(self, "@current_exception");
303
- if (ruby_exception == Qnil) {
304
- ruby_exception = eval_result.terminated ? rb_eScriptTerminatedError : rb_eScriptRuntimeError;
305
- // exception report about what happened
306
- if(TYPE(message) == T_STRING && TYPE(backtrace) == T_STRING) {
307
- rb_raise(ruby_exception, "%s/n%s", RSTRING_PTR(message), RSTRING_PTR(backtrace));
308
- } else if(TYPE(message) == T_STRING) {
309
- rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
310
- } else {
311
- rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
312
- }
313
- } else {
1037
+ if (!result.executed) {
1038
+ VALUE ruby_exception = rb_iv_get(self, "@current_exception");
1039
+ if (ruby_exception == Qnil) {
1040
+ bool mem_softlimit_reached = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED);
1041
+ bool marshal_stack_maxdepth_reached = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED);
1042
+ // If we were terminated or have the memory softlimit flag set
1043
+ if (marshal_stack_maxdepth_reached) {
1044
+ ruby_exception = rb_eScriptRuntimeError;
1045
+ std::string msg = std::string("Marshal object depth too deep. Script terminated.");
1046
+ message = rb_enc_str_new(msg.c_str(), msg.length(), rb_enc_find("utf-8"));
1047
+ } else if (result.terminated || mem_softlimit_reached) {
1048
+ ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
1049
+ } else {
1050
+ ruby_exception = rb_eScriptRuntimeError;
1051
+ }
1052
+
1053
+ // exception report about what happened
1054
+ if (TYPE(backtrace) == T_STRING) {
1055
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
1056
+ } else if(TYPE(message) == T_STRING) {
1057
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
1058
+ } else {
1059
+ rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
1060
+ }
1061
+ } else {
314
1062
  VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
315
- rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
316
- }
1063
+ rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
1064
+ }
317
1065
  }
318
1066
 
1067
+ VALUE ret = Qnil;
1068
+
319
1069
  // New scope for return value
320
1070
  {
321
- Locker lock(context_info->isolate);
322
- Isolate::Scope isolate_scope(context_info->isolate);
323
- HandleScope handle_scope(context_info->isolate);
1071
+ Locker lock(isolate);
1072
+ Isolate::Scope isolate_scope(isolate);
1073
+ HandleScope handle_scope(isolate);
1074
+
1075
+ Local<Value> tmp = Local<Value>::New(isolate, *result.value);
1076
+
1077
+ if (result.json) {
1078
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
1079
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
1080
+ ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
1081
+ } else {
1082
+ StackCounter::Reset(isolate);
1083
+ ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
1084
+ }
1085
+
1086
+ result.value->Reset();
1087
+ delete result.value;
1088
+ }
1089
+
1090
+ if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
1091
+ // TODO try to recover stack trace from the conversion error
1092
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
1093
+ }
1094
+
1095
+
1096
+ return ret;
1097
+ }
1098
+
1099
+ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
1100
+
1101
+ EvalParams eval_params;
1102
+ EvalResult eval_result;
1103
+ ContextInfo* context_info;
1104
+
1105
+ Data_Get_Struct(self, ContextInfo, context_info);
1106
+ Isolate* isolate = context_info->isolate_info->isolate;
324
1107
 
325
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.value);
326
- result = convert_v8_to_ruby(context_info->isolate, tmp);
1108
+ if(TYPE(str) != T_STRING) {
1109
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
1110
+ rb_obj_class(str));
1111
+ }
1112
+ if(filename != Qnil && TYPE(filename) != T_STRING) {
1113
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
1114
+ rb_obj_class(filename));
1115
+ }
327
1116
 
328
- eval_result.value->Reset();
329
- delete eval_result.value;
1117
+ {
1118
+ Locker lock(isolate);
1119
+ Isolate::Scope isolate_scope(isolate);
1120
+ HandleScope handle_scope(isolate);
1121
+
1122
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
1123
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
1124
+
1125
+ Local<String> local_filename;
1126
+
1127
+ if (filename != Qnil) {
1128
+ local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
1129
+ NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
1130
+ eval_params.filename = &local_filename;
1131
+ } else {
1132
+ eval_params.filename = NULL;
1133
+ }
1134
+
1135
+ eval_params.context_info = context_info;
1136
+ eval_params.eval = &eval;
1137
+ eval_params.result = &eval_result;
1138
+ eval_params.timeout = 0;
1139
+ eval_params.max_memory = 0;
1140
+ eval_params.marshal_stackdepth = 0;
1141
+ VALUE timeout = rb_iv_get(self, "@timeout");
1142
+ if (timeout != Qnil) {
1143
+ eval_params.timeout = (useconds_t)NUM2LONG(timeout);
1144
+ }
1145
+
1146
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1147
+ if (mem_softlimit != Qnil) {
1148
+ eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
1149
+ }
1150
+
1151
+ VALUE stack_depth = rb_iv_get(self, "@marshal_stack_depth");
1152
+ if (stack_depth != Qnil) {
1153
+ eval_params.marshal_stackdepth = (size_t)NUM2ULONG(stack_depth);
1154
+ }
1155
+
1156
+ eval_result.message = NULL;
1157
+ eval_result.backtrace = NULL;
1158
+
1159
+ rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
330
1160
  }
331
1161
 
332
- return result;
1162
+ return convert_result_to_ruby(self, eval_result);
333
1163
  }
334
1164
 
335
1165
  typedef struct {
336
1166
  VALUE callback;
337
1167
  int length;
338
- VALUE* args;
1168
+ VALUE ruby_args;
339
1169
  bool failed;
340
1170
  } protected_callback_data;
341
1171
 
342
- static
343
- VALUE protected_callback(VALUE rdata) {
1172
+ static VALUE protected_callback(VALUE rdata) {
344
1173
  protected_callback_data* data = (protected_callback_data*)rdata;
345
1174
  VALUE result;
346
1175
 
347
1176
  if (data->length > 0) {
348
- result = rb_funcall2(data->callback, rb_intern("call"), data->length, data->args);
1177
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length,
1178
+ RARRAY_PTR(data->ruby_args));
1179
+ RB_GC_GUARD(data->ruby_args);
349
1180
  } else {
350
- result = rb_funcall(data->callback, rb_intern("call"), 0);
1181
+ result = rb_funcall(data->callback, rb_intern("call"), 0);
351
1182
  }
352
1183
  return result;
353
1184
  }
@@ -363,61 +1194,98 @@ void*
363
1194
  gvl_ruby_callback(void* data) {
364
1195
 
365
1196
  FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
366
- VALUE* ruby_args;
1197
+ VALUE ruby_args = Qnil;
367
1198
  int length = args->Length();
368
1199
  VALUE callback;
369
1200
  VALUE result;
370
1201
  VALUE self;
1202
+ VALUE parent;
1203
+ ContextInfo* context_info;
371
1204
 
372
1205
  {
373
- HandleScope scope(args->GetIsolate());
374
- Handle<External> external = Handle<External>::Cast(args->Data());
375
-
376
- VALUE* self_pointer = (VALUE*)(external->Value());
377
- self = *self_pointer;
378
- callback = rb_iv_get(self, "@callback");
379
-
380
- if (length > 0) {
381
- ruby_args = ALLOC_N(VALUE, length);
382
- }
383
-
384
-
385
- for (int i = 0; i < length; i++) {
386
- Local<Value> value = ((*args)[i]).As<Value>();
387
- ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
388
- }
1206
+ HandleScope scope(args->GetIsolate());
1207
+ Local<External> external = Local<External>::Cast(args->Data());
1208
+
1209
+ self = *(VALUE*)(external->Value());
1210
+ callback = rb_iv_get(self, "@callback");
1211
+
1212
+ parent = rb_iv_get(self, "@parent");
1213
+ if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
1214
+ return NULL;
1215
+ }
1216
+
1217
+ Data_Get_Struct(parent, ContextInfo, context_info);
1218
+
1219
+ if (length > 0) {
1220
+ ruby_args = rb_ary_tmp_new(length);
1221
+ }
1222
+
1223
+ for (int i = 0; i < length; i++) {
1224
+ Local<Value> value = ((*args)[i]).As<Value>();
1225
+ StackCounter::Reset(args->GetIsolate());
1226
+ VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1227
+ *context_info->context, value);
1228
+ rb_ary_push(ruby_args, tmp);
1229
+ }
389
1230
  }
390
1231
 
391
1232
  // may raise exception stay clear of handle scope
392
1233
  protected_callback_data callback_data;
393
1234
  callback_data.length = length;
394
1235
  callback_data.callback = callback;
395
- callback_data.args = ruby_args;
1236
+ callback_data.ruby_args = ruby_args;
396
1237
  callback_data.failed = false;
397
1238
 
398
- result = rb_rescue((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
399
- (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data));
1239
+ if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
1240
+ args->GetIsolate()->ThrowException(
1241
+ String::NewFromUtf8Literal(args->GetIsolate(),
1242
+ "Terminated execution during transition from Ruby to JS"));
1243
+ args->GetIsolate()->TerminateExecution();
1244
+ if (length > 0) {
1245
+ rb_ary_clear(ruby_args);
1246
+ rb_gc_force_recycle(ruby_args);
1247
+ }
1248
+ return NULL;
1249
+ }
1250
+
1251
+ VALUE callback_data_value = (VALUE)&callback_data;
1252
+
1253
+ // TODO: use rb_vrescue2 in Ruby 2.7 and above
1254
+ result = rb_rescue2(RUBY_METHOD_FUNC(protected_callback), callback_data_value,
1255
+ RUBY_METHOD_FUNC(rescue_callback), callback_data_value, rb_eException, (VALUE)0);
400
1256
 
401
1257
  if(callback_data.failed) {
402
- VALUE parent = rb_iv_get(self, "@parent");
403
- rb_iv_set(parent, "@current_exception", result);
404
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1258
+ rb_iv_set(parent, "@current_exception", result);
1259
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
405
1260
  }
406
1261
  else {
407
- HandleScope scope(args->GetIsolate());
408
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
409
- args->GetReturnValue().Set(v8_result);
1262
+ HandleScope scope(args->GetIsolate());
1263
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1264
+ args->GetReturnValue().Set(v8_result);
410
1265
  }
411
1266
 
412
1267
  if (length > 0) {
413
- xfree(ruby_args);
1268
+ rb_ary_clear(ruby_args);
1269
+ rb_gc_force_recycle(ruby_args);
1270
+ }
1271
+
1272
+ if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
1273
+ args->GetIsolate()->TerminateExecution();
414
1274
  }
415
1275
 
416
1276
  return NULL;
417
1277
  }
418
1278
 
419
1279
  static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
420
- rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1280
+ bool has_gvl = IsolateData::Get(args.GetIsolate(), IsolateData::IN_GVL);
1281
+
1282
+ if(has_gvl) {
1283
+ gvl_ruby_callback((void*)&args);
1284
+ } else {
1285
+ IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, true);
1286
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1287
+ IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, false);
1288
+ }
421
1289
  }
422
1290
 
423
1291
 
@@ -427,114 +1295,613 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
427
1295
 
428
1296
  VALUE parent = rb_iv_get(self, "@parent");
429
1297
  VALUE name = rb_iv_get(self, "@name");
1298
+ VALUE parent_object = rb_iv_get(self, "@parent_object");
1299
+ VALUE parent_object_eval = rb_iv_get(self, "@parent_object_eval");
1300
+
1301
+ bool parse_error = false;
1302
+ bool attach_error = false;
430
1303
 
431
1304
  Data_Get_Struct(parent, ContextInfo, context_info);
1305
+ Isolate* isolate = context_info->isolate_info->isolate;
432
1306
 
433
- Locker lock(context_info->isolate);
434
- Isolate::Scope isolate_scope(context_info->isolate);
435
- HandleScope handle_scope(context_info->isolate);
1307
+ {
1308
+ Locker lock(isolate);
1309
+ Isolate::Scope isolate_scope(isolate);
1310
+ HandleScope handle_scope(isolate);
1311
+
1312
+ Local<Context> context = context_info->context->Get(isolate);
1313
+ Context::Scope context_scope(context);
1314
+
1315
+ Local<String> v8_str =
1316
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1317
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1318
+ .ToLocalChecked();
1319
+
1320
+ // copy self so we can access from v8 external
1321
+ VALUE* self_copy;
1322
+ Data_Get_Struct(self, VALUE, self_copy);
1323
+ *self_copy = self;
1324
+
1325
+ Local<Value> external = External::New(isolate, self_copy);
1326
+
1327
+ if (parent_object == Qnil) {
1328
+ Maybe<bool> success = context->Global()->Set(
1329
+ context,
1330
+ v8_str,
1331
+ FunctionTemplate::New(isolate, ruby_callback, external)
1332
+ ->GetFunction(context)
1333
+ .ToLocalChecked());
1334
+ (void)success;
1335
+
1336
+ } else {
1337
+ Local<String> eval =
1338
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1339
+ NewStringType::kNormal,
1340
+ (int)RSTRING_LEN(parent_object_eval))
1341
+ .ToLocalChecked();
1342
+
1343
+ MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1344
+ if (parsed_script.IsEmpty()) {
1345
+ parse_error = true;
1346
+ } else {
1347
+ MaybeLocal<Value> maybe_value =
1348
+ parsed_script.ToLocalChecked()->Run(context);
1349
+ attach_error = true;
1350
+
1351
+ if (!maybe_value.IsEmpty()) {
1352
+ Local<Value> value = maybe_value.ToLocalChecked();
1353
+ if (value->IsObject()) {
1354
+ Maybe<bool> success = value.As<Object>()->Set(
1355
+ context,
1356
+ v8_str,
1357
+ FunctionTemplate::New(isolate, ruby_callback, external)
1358
+ ->GetFunction(context)
1359
+ .ToLocalChecked());
1360
+ (void)success;
1361
+ attach_error = false;
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+ }
436
1367
 
437
- Local<Context> context = context_info->context->Get(context_info->isolate);
438
- Context::Scope context_scope(context);
1368
+ // always raise out of V8 context
1369
+ if (parse_error) {
1370
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
1371
+ }
439
1372
 
440
- Local<String> v8_str = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(name),
441
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
1373
+ if (attach_error) {
1374
+ rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
1375
+ }
442
1376
 
1377
+ return Qnil;
1378
+ }
443
1379
 
444
- // copy self so we can access from v8 external
445
- VALUE* self_copy;
446
- Data_Get_Struct(self, VALUE, self_copy);
447
- *self_copy = self;
1380
+ static VALUE rb_context_isolate_mutex(VALUE self) {
1381
+ ContextInfo* context_info;
1382
+ Data_Get_Struct(self, ContextInfo, context_info);
448
1383
 
449
- Local<Value> external = External::New(context_info->isolate, self_copy);
450
- context->Global()->Set(v8_str, FunctionTemplate::New(context_info->isolate, ruby_callback, external)->GetFunction());
1384
+ if (!context_info->isolate_info) {
1385
+ rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
1386
+ }
451
1387
 
452
- return Qnil;
1388
+ return context_info->isolate_info->mutex;
453
1389
  }
454
1390
 
455
- void deallocate(void * data) {
456
- ContextInfo* context_info = (ContextInfo*)data;
457
- {
458
- Locker lock(context_info->isolate);
1391
+ IsolateInfo::~IsolateInfo() {
1392
+ if (isolate) {
1393
+ if (this->interrupted) {
1394
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1395
+ "it can not be disposed and memory will not be "
1396
+ "reclaimed till the Ruby process exits.\n");
1397
+ } else {
1398
+ if (this->pid != getpid() && !single_threaded) {
1399
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1400
+ "it can not be disposed and "
1401
+ "memory will not be reclaimed "
1402
+ "till the Ruby process exits.\n"
1403
+ "It is VERY likely your process will hang.\n"
1404
+ "If you wish to use v8 in forked environment "
1405
+ "please ensure the platform is initialized with:\n"
1406
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1407
+ );
1408
+ } else {
1409
+ isolate->Dispose();
1410
+ }
1411
+ }
1412
+ isolate = nullptr;
459
1413
  }
460
1414
 
461
- {
462
- context_info->context->Reset();
463
- delete context_info->context;
1415
+ if (startup_data) {
1416
+ delete[] startup_data->data;
1417
+ delete startup_data;
464
1418
  }
465
1419
 
466
- {
467
- if (context_info->interrupted) {
468
- 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.");
469
- } else {
470
- context_info->isolate->Dispose();
471
- }
1420
+ delete allocator;
1421
+ }
1422
+
1423
+ static void free_context_raw(void *arg) {
1424
+ ContextInfo* context_info = (ContextInfo*)arg;
1425
+ IsolateInfo* isolate_info = context_info->isolate_info;
1426
+ Persistent<Context>* context = context_info->context;
1427
+
1428
+ if (context && isolate_info && isolate_info->isolate) {
1429
+ Locker lock(isolate_info->isolate);
1430
+ v8::Isolate::Scope isolate_scope(isolate_info->isolate);
1431
+ context->Reset();
1432
+ delete context;
1433
+ }
1434
+
1435
+ if (isolate_info) {
1436
+ isolate_info->release();
472
1437
  }
473
1438
 
474
- delete context_info->allocator;
475
1439
  xfree(context_info);
476
1440
  }
477
1441
 
478
- void deallocate_external_function(void * data) {
1442
+ static void *free_context_thr(void* arg) {
1443
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1444
+ return NULL;
1445
+ }
1446
+ if (ruby_exiting) {
1447
+ return NULL;
1448
+ }
1449
+
1450
+ free_context_raw(arg);
1451
+
1452
+ pthread_rwlock_unlock(&exit_lock);
1453
+
1454
+ return NULL;
1455
+ }
1456
+
1457
+ // destroys everything except freeing the ContextInfo struct (see deallocate())
1458
+ static void free_context(ContextInfo* context_info) {
1459
+
1460
+ IsolateInfo* isolate_info = context_info->isolate_info;
1461
+
1462
+ ContextInfo* context_info_copy = ALLOC(ContextInfo);
1463
+ context_info_copy->isolate_info = context_info->isolate_info;
1464
+ context_info_copy->context = context_info->context;
1465
+
1466
+ if (isolate_info && isolate_info->refs() > 1) {
1467
+ pthread_t free_context_thread;
1468
+ if (pthread_create(&free_context_thread, thread_attr_p,
1469
+ free_context_thr, (void*)context_info_copy)) {
1470
+ fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1471
+ }
1472
+ } else {
1473
+ free_context_raw(context_info_copy);
1474
+ }
1475
+
1476
+ context_info->context = NULL;
1477
+ context_info->isolate_info = NULL;
1478
+ }
1479
+
1480
+ static void deallocate_isolate(void* data) {
1481
+
1482
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1483
+
1484
+ isolate_info->release();
1485
+ }
1486
+
1487
+ static void mark_isolate(void* data) {
1488
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1489
+ isolate_info->mark();
1490
+ }
1491
+
1492
+ static void deallocate(void* data) {
1493
+ ContextInfo* context_info = (ContextInfo*)data;
1494
+
1495
+ free_context(context_info);
1496
+
1497
+ xfree(data);
1498
+ }
1499
+
1500
+ static void mark_context(void* data) {
1501
+ ContextInfo* context_info = (ContextInfo*)data;
1502
+ if (context_info->isolate_info) {
1503
+ context_info->isolate_info->mark();
1504
+ }
1505
+ }
1506
+
1507
+ static void deallocate_external_function(void * data) {
479
1508
  xfree(data);
480
1509
  }
481
1510
 
482
- VALUE allocate_external_function(VALUE klass) {
1511
+ static void deallocate_snapshot(void * data) {
1512
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1513
+ delete[] snapshot_info->data;
1514
+ xfree(snapshot_info);
1515
+ }
1516
+
1517
+ static VALUE allocate_external_function(VALUE klass) {
483
1518
  VALUE* self = ALLOC(VALUE);
484
1519
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
485
1520
  }
486
1521
 
487
- VALUE allocate(VALUE klass) {
488
- init_v8();
489
-
1522
+ static VALUE allocate(VALUE klass) {
490
1523
  ContextInfo* context_info = ALLOC(ContextInfo);
491
- context_info->allocator = new ArrayBufferAllocator();
492
- context_info->interrupted = false;
493
- Isolate::CreateParams create_params;
494
- create_params.array_buffer_allocator = context_info->allocator;
1524
+ context_info->isolate_info = NULL;
1525
+ context_info->context = NULL;
495
1526
 
496
- context_info->isolate = Isolate::New(create_params);
1527
+ return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1528
+ }
497
1529
 
498
- Locker lock(context_info->isolate);
499
- Isolate::Scope isolate_scope(context_info->isolate);
500
- HandleScope handle_scope(context_info->isolate);
1530
+ static VALUE allocate_snapshot(VALUE klass) {
1531
+ SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1532
+ snapshot_info->data = NULL;
1533
+ snapshot_info->raw_size = 0;
501
1534
 
502
- Local<Context> context = Context::New(context_info->isolate);
1535
+ return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1536
+ }
503
1537
 
504
- context_info->context = new Persistent<Context>();
505
- context_info->context->Reset(context_info->isolate, context);
1538
+ static VALUE allocate_isolate(VALUE klass) {
1539
+ IsolateInfo* isolate_info = new IsolateInfo();
506
1540
 
507
- return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
1541
+ return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
1542
+ }
1543
+
1544
+ static VALUE
1545
+ rb_heap_stats(VALUE self) {
1546
+
1547
+ ContextInfo* context_info;
1548
+ Data_Get_Struct(self, ContextInfo, context_info);
1549
+ Isolate* isolate;
1550
+ v8::HeapStatistics stats;
1551
+
1552
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1553
+
1554
+ VALUE rval = rb_hash_new();
1555
+
1556
+ if (!isolate) {
1557
+
1558
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1559
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1560
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1561
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1562
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
1563
+
1564
+ } else {
1565
+ isolate->GetHeapStatistics(&stats);
1566
+
1567
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1568
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1569
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1570
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1571
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
1572
+ }
1573
+
1574
+ return rval;
1575
+ }
1576
+
1577
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1578
+ class FileOutputStream : public OutputStream {
1579
+ public:
1580
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1581
+
1582
+ virtual int GetChunkSize() {
1583
+ return 65536;
1584
+ }
1585
+
1586
+ virtual void EndOfStream() {}
1587
+
1588
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1589
+ const size_t len = static_cast<size_t>(size);
1590
+ size_t off = 0;
1591
+
1592
+ while (off < len && !feof(stream_) && !ferror(stream_))
1593
+ off += fwrite(data + off, 1, len - off, stream_);
1594
+
1595
+ return off == len ? kContinue : kAbort;
1596
+ }
1597
+
1598
+ private:
1599
+ FILE* stream_;
1600
+ };
1601
+
1602
+
1603
+ static VALUE
1604
+ rb_heap_snapshot(VALUE self, VALUE file) {
1605
+
1606
+ rb_io_t *fptr;
1607
+
1608
+ fptr = RFILE(file)->fptr;
1609
+
1610
+ if (!fptr) return Qfalse;
1611
+
1612
+ FILE* fp;
1613
+ fp = fdopen(fptr->fd, "w");
1614
+ if (fp == NULL) return Qfalse;
1615
+
1616
+
1617
+ ContextInfo* context_info;
1618
+ Data_Get_Struct(self, ContextInfo, context_info);
1619
+ Isolate* isolate;
1620
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1621
+
1622
+ if (!isolate) return Qfalse;
1623
+
1624
+ Locker lock(isolate);
1625
+ Isolate::Scope isolate_scope(isolate);
1626
+ HandleScope handle_scope(isolate);
1627
+
1628
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1629
+
1630
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1631
+
1632
+ FileOutputStream stream(fp);
1633
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1634
+
1635
+ fflush(fp);
1636
+
1637
+ const_cast<HeapSnapshot*>(snap)->Delete();
1638
+
1639
+ return Qtrue;
508
1640
  }
509
1641
 
510
1642
  static VALUE
511
1643
  rb_context_stop(VALUE self) {
1644
+
512
1645
  ContextInfo* context_info;
513
1646
  Data_Get_Struct(self, ContextInfo, context_info);
514
- V8::TerminateExecution(context_info->isolate);
1647
+
1648
+ Isolate* isolate = context_info->isolate_info->isolate;
1649
+
1650
+ // flag for termination
1651
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
1652
+
1653
+ isolate->TerminateExecution();
1654
+ rb_funcall(self, rb_intern("stop_attached"), 0);
1655
+
515
1656
  return Qnil;
516
1657
  }
517
1658
 
518
- extern "C" {
1659
+ static VALUE
1660
+ rb_context_dispose(VALUE self) {
1661
+
1662
+ ContextInfo* context_info;
1663
+ Data_Get_Struct(self, ContextInfo, context_info);
1664
+
1665
+ free_context(context_info);
1666
+
1667
+ return Qnil;
1668
+ }
1669
+
1670
+ static void*
1671
+ nogvl_context_call(void *args) {
1672
+
1673
+ FunctionCall *call = (FunctionCall *) args;
1674
+ if (!call) {
1675
+ return NULL;
1676
+ }
1677
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1678
+ Isolate* isolate = isolate_info->isolate;
1679
+
1680
+ IsolateData::Set(isolate, IsolateData::IN_GVL, false);
1681
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, false);
1682
+
1683
+ if (call->max_memory > 0) {
1684
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, call->max_memory);
1685
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, false);
1686
+ if (!isolate_info->added_gc_cb) {
1687
+ isolate->AddGCEpilogueCallback(gc_callback);
1688
+ isolate_info->added_gc_cb = true;
1689
+ }
1690
+ }
1691
+
1692
+ if (call->marshal_stackdepth > 0) {
1693
+ StackCounter::SetMax(isolate, call->marshal_stackdepth);
1694
+ }
1695
+
1696
+ Isolate::Scope isolate_scope(isolate);
1697
+ EscapableHandleScope handle_scope(isolate);
1698
+ TryCatch trycatch(isolate);
1699
+
1700
+ Local<Context> context = call->context_info->context->Get(isolate);
1701
+ Context::Scope context_scope(context);
1702
+
1703
+ Local<Function> fun = call->fun;
1704
+
1705
+ EvalResult& eval_res = call->result;
1706
+ eval_res.parsed = true;
1707
+
1708
+ MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
1709
+ prepare_result(res, trycatch, isolate, context, eval_res);
1710
+
1711
+ IsolateData::Set(isolate, IsolateData::IN_GVL, true);
1712
+
1713
+ return NULL;
1714
+ }
1715
+
1716
+ static void unblock_function(void *args) {
1717
+ FunctionCall *call = (FunctionCall *) args;
1718
+ call->context_info->isolate_info->interrupted = true;
1719
+ }
1720
+
1721
+ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1722
+ ContextInfo* context_info;
1723
+ FunctionCall call;
1724
+ VALUE *call_argv = NULL;
1725
+
1726
+ Data_Get_Struct(self, ContextInfo, context_info);
1727
+ Isolate* isolate = context_info->isolate_info->isolate;
519
1728
 
520
- void Init_mini_racer_extension ( void )
1729
+ if (argc < 1) {
1730
+ rb_raise(rb_eArgError, "need at least one argument %d", argc);
1731
+ }
1732
+
1733
+ VALUE function_name = argv[0];
1734
+ if (TYPE(function_name) != T_STRING) {
1735
+ rb_raise(rb_eTypeError, "first argument should be a String");
1736
+ }
1737
+
1738
+ char *fname = RSTRING_PTR(function_name);
1739
+ if (!fname) {
1740
+ return Qnil;
1741
+ }
1742
+
1743
+ call.context_info = context_info;
1744
+ call.error = false;
1745
+ call.function_name = fname;
1746
+ call.argc = argc - 1;
1747
+ call.argv = NULL;
1748
+ if (call.argc > 0) {
1749
+ // skip first argument which is the function name
1750
+ call_argv = argv + 1;
1751
+ }
1752
+
1753
+ call.max_memory = 0;
1754
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1755
+ if (mem_softlimit != Qnil) {
1756
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1757
+ call.max_memory = (size_t)sl_int;
1758
+ }
1759
+
1760
+ call.marshal_stackdepth = 0;
1761
+ VALUE marshal_stackdepth = rb_iv_get(self, "@marshal_stack_depth");
1762
+ if (marshal_stackdepth != Qnil) {
1763
+ unsigned long sl_int = NUM2ULONG(marshal_stackdepth);
1764
+ call.marshal_stackdepth = (size_t)sl_int;
1765
+ }
1766
+
1767
+ bool missingFunction = false;
521
1768
  {
522
- VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
523
- VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1769
+ Locker lock(isolate);
1770
+ Isolate::Scope isolate_scope(isolate);
1771
+ HandleScope handle_scope(isolate);
1772
+
1773
+ Local<Context> context = context_info->context->Get(isolate);
1774
+ Context::Scope context_scope(context);
1775
+
1776
+ // examples of such usage can be found in
1777
+ // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1778
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1779
+ MaybeLocal<v8::Value> val;
1780
+ if (!fname.IsEmpty()) {
1781
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1782
+ }
1783
+
1784
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1785
+ missingFunction = true;
1786
+ } else {
1787
+
1788
+ Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1789
+ call.fun = fun;
1790
+ int fun_argc = call.argc;
1791
+
1792
+ if (fun_argc > 0) {
1793
+ call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1794
+ if (!call.argv) {
1795
+ return Qnil;
1796
+ }
1797
+ for(int i=0; i < fun_argc; i++) {
1798
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1799
+ }
1800
+ }
1801
+ rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1802
+ free(call.argv);
1803
+
1804
+ }
1805
+ }
1806
+
1807
+ if (missingFunction) {
1808
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1809
+ }
524
1810
 
525
- VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
526
- rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
527
- rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
528
- rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
529
- rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1811
+ return convert_result_to_ruby(self, call.result);
1812
+ }
1813
+
1814
+ static VALUE rb_context_create_isolate_value(VALUE self) {
1815
+ ContextInfo* context_info;
1816
+ Data_Get_Struct(self, ContextInfo, context_info);
1817
+ IsolateInfo *isolate_info = context_info->isolate_info;
1818
+
1819
+ if (!isolate_info) {
1820
+ return Qnil;
1821
+ }
1822
+
1823
+ isolate_info->hold();
1824
+ return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1825
+ }
1826
+
1827
+ static void set_ruby_exiting(VALUE value) {
1828
+ (void)value;
530
1829
 
531
- VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
532
- rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
533
- rb_define_alloc_func(rb_cContext, allocate);
1830
+ int res = pthread_rwlock_wrlock(&exit_lock);
534
1831
 
535
- rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
536
- rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
537
- rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1832
+ ruby_exiting = true;
1833
+ if (res == 0) {
1834
+ pthread_rwlock_unlock(&exit_lock);
538
1835
  }
1836
+ }
539
1837
 
1838
+ extern "C" {
1839
+
1840
+ __attribute__((visibility("default"))) void Init_mini_racer_extension ( void )
1841
+ {
1842
+ VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1843
+ rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1844
+ rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1845
+ rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1846
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1847
+
1848
+ VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1849
+
1850
+ VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1851
+ rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1852
+ rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1853
+ rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1854
+ rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
1855
+
1856
+ rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1857
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
1858
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
1859
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
1860
+ rb_mJSON = rb_define_module("JSON");
1861
+
1862
+ VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1863
+
1864
+ rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1865
+ rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1866
+ rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1867
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1868
+
1869
+ rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1870
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1871
+ rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1872
+ rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1873
+ rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1874
+
1875
+ rb_define_alloc_func(rb_cContext, allocate);
1876
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1877
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1878
+
1879
+ rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1880
+ rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1881
+
1882
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1883
+ rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
1884
+ rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1885
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1886
+
1887
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1888
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1889
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1890
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1891
+
1892
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1893
+
1894
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1895
+
1896
+ static pthread_attr_t attr;
1897
+ if (pthread_attr_init(&attr) == 0) {
1898
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1899
+ thread_attr_p = &attr;
1900
+ }
1901
+ }
1902
+ auto on_fork_for_child = []() {
1903
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1904
+ };
1905
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1906
+ }
540
1907
  }