mini_racer 0.1.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }