mini_racer 0.3.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.
@@ -0,0 +1,95 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ require "rake/extensiontask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ task :default => [:compile, :test]
12
+
13
+ gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' )
14
+ Rake::ExtensionTask.new( 'mini_racer_extension', gem )
15
+
16
+
17
+ # via http://blog.flavorjon.es/2009/06/easily-valgrind-gdb-your-ruby-c.html
18
+ namespace :test do
19
+ desc "run test suite with Address Sanitizer"
20
+ task :asan do
21
+ ENV["CONFIGURE_ARGS"] = [ENV["CONFIGURE_ARGS"], '--enable-asan'].compact.join(' ')
22
+ Rake::Task['compile'].invoke
23
+
24
+ asan_path = `ldconfig -N -p |grep libasan | grep -v 32 | sed 's/.* => \\(.*\\)$/\\1/'`.chomp.split("\n")[-1]
25
+
26
+
27
+ cmdline = "env LD_PRELOAD=\"#{asan_path}\" ruby test/test_leak.rb"
28
+ puts cmdline
29
+ system cmdline
30
+
31
+ cmdline = "env LD_PRELOAD=\"#{asan_path}\" rake test"
32
+ puts cmdline
33
+ system cmdline
34
+ end
35
+ # partial-loads-ok and undef-value-errors necessary to ignore
36
+ # spurious (and eminently ignorable) warnings from the ruby
37
+ # interpreter
38
+ VALGRIND_BASIC_OPTS = "--num-callers=50 --error-limit=no \
39
+ --partial-loads-ok=yes --undef-value-errors=no"
40
+
41
+ desc "run test suite under valgrind with basic ruby options"
42
+ task :valgrind => :compile do
43
+ cmdline = "valgrind #{VALGRIND_BASIC_OPTS} ruby test/test_leak.rb"
44
+ puts cmdline
45
+ system cmdline
46
+ end
47
+
48
+ desc "run test suite under valgrind with leak-check=full"
49
+ task :valgrind_leak_check => :compile do
50
+ cmdline = "valgrind #{VALGRIND_BASIC_OPTS} --leak-check=full ruby test/test_leak.rb"
51
+ puts cmdline
52
+ require 'open3'
53
+ _, stderr = Open3.capture3(cmdline)
54
+
55
+ section = ""
56
+ stderr.split("\n").each do |line|
57
+
58
+ if line =~ /==.*==\s*$/
59
+ if (section =~ /mini_racer|SUMMARY/)
60
+ puts
61
+ puts section
62
+ puts
63
+ end
64
+ section = ""
65
+ else
66
+ section << line << "\n"
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ desc 'run clang-tidy linter on mini_racer_extension.cc'
73
+ task :lint do
74
+ require 'mkmf'
75
+ require 'libv8'
76
+
77
+ Libv8.configure_makefile
78
+
79
+ conf = RbConfig::CONFIG.merge('hdrdir' => $hdrdir.quote, 'srcdir' => $srcdir.quote,
80
+ 'arch_hdrdir' => $arch_hdrdir.quote,
81
+ 'top_srcdir' => $top_srcdir.quote)
82
+ if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty?
83
+ conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '')
84
+ end
85
+
86
+ checks = %W(bugprone-*
87
+ cert-*
88
+ cppcoreguidelines-*
89
+ clang-analyzer-*
90
+ performance-*
91
+ portability-*
92
+ readability-*).join(',')
93
+
94
+ sh RbConfig::expand("clang-tidy -checks='#{checks}' ext/mini_racer_extension/mini_racer_extension.cc -- #$INCFLAGS #$CPPFLAGS", conf)
95
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "mini_racer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,64 @@
1
+ require 'mkmf'
2
+ require 'libv8'
3
+
4
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
5
+
6
+ have_library('pthread')
7
+ have_library('objc') if IS_DARWIN
8
+ $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
9
+ $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
10
+ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
11
+ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
12
+ $CPPFLAGS += " -std=c++0x"
13
+ $CPPFLAGS += " -fpermissive"
14
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
15
+
16
+ $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
17
+
18
+ $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
19
+
20
+ if ENV['CXX']
21
+ puts "SETTING CXX"
22
+ CONFIG['CXX'] = ENV['CXX']
23
+ end
24
+
25
+ CXX11_TEST = <<EOS
26
+ #if __cplusplus <= 199711L
27
+ # error A compiler that supports at least C++11 is required in order to compile this project.
28
+ #endif
29
+ EOS
30
+
31
+ `echo "#{CXX11_TEST}" | #{CONFIG['CXX']} -std=c++0x -x c++ -E -`
32
+ unless $?.success?
33
+ warn <<EOS
34
+
35
+
36
+ WARNING: C++11 support is required for compiling mini_racer. Please make sure
37
+ you are using a compiler that supports at least C++11. Examples of such
38
+ compilers are GCC 4.7+ and Clang 3.2+.
39
+
40
+ If you are using Travis, consider either migrating your build to Ubuntu Trusty or
41
+ installing GCC 4.8. See mini_racer's README.md for more information.
42
+
43
+
44
+ EOS
45
+ end
46
+
47
+ CONFIG['LDSHARED'] = '$(CXX) -shared' unless IS_DARWIN
48
+ if CONFIG['warnflags']
49
+ CONFIG['warnflags'].gsub!('-Wdeclaration-after-statement', '')
50
+ CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
51
+ end
52
+
53
+ if enable_config('debug') || enable_config('asan')
54
+ CONFIG['debugflags'] << ' -ggdb3 -O0'
55
+ end
56
+
57
+ Libv8.configure_makefile
58
+
59
+ if enable_config('asan')
60
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
61
+ $LDFLAGS.insert(0, " -fsanitize=address ")
62
+ end
63
+
64
+ create_makefile 'mini_racer_extension'
@@ -0,0 +1,1714 @@
1
+ #include <stdio.h>
2
+ #include <ruby.h>
3
+ #include <ruby/thread.h>
4
+ #include <ruby/io.h>
5
+ #include <v8.h>
6
+ #include <v8-profiler.h>
7
+ #include <libplatform/libplatform.h>
8
+ #include <ruby/encoding.h>
9
+ #include <pthread.h>
10
+ #include <unistd.h>
11
+ #include <mutex>
12
+ #include <atomic>
13
+ #include <math.h>
14
+
15
+ using namespace v8;
16
+
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;
94
+ };
95
+
96
+ typedef struct {
97
+ IsolateInfo* isolate_info;
98
+ Persistent<Context>* context;
99
+ } ContextInfo;
100
+
101
+ typedef struct {
102
+ bool parsed;
103
+ bool executed;
104
+ bool terminated;
105
+ bool json;
106
+ Persistent<Value>* value;
107
+ Persistent<Value>* message;
108
+ Persistent<Value>* backtrace;
109
+ } EvalResult;
110
+
111
+ typedef struct {
112
+ ContextInfo* context_info;
113
+ Local<String>* eval;
114
+ Local<String>* filename;
115
+ useconds_t timeout;
116
+ EvalResult* result;
117
+ size_t max_memory;
118
+ } EvalParams;
119
+
120
+ typedef struct {
121
+ ContextInfo *context_info;
122
+ char *function_name;
123
+ int argc;
124
+ bool error;
125
+ Local<Function> fun;
126
+ Local<Value> *argv;
127
+ EvalResult result;
128
+ size_t max_memory;
129
+ } FunctionCall;
130
+
131
+ enum IsolateFlags {
132
+ IN_GVL,
133
+ DO_TERMINATE,
134
+ MEM_SOFTLIMIT_VALUE,
135
+ MEM_SOFTLIMIT_REACHED,
136
+ };
137
+
138
+ static VALUE rb_cContext;
139
+ static VALUE rb_cSnapshot;
140
+ static VALUE rb_cIsolate;
141
+
142
+ static VALUE rb_eScriptTerminatedError;
143
+ static VALUE rb_eV8OutOfMemoryError;
144
+ static VALUE rb_eParseError;
145
+ static VALUE rb_eScriptRuntimeError;
146
+ static VALUE rb_cJavaScriptFunction;
147
+ static VALUE rb_eSnapshotError;
148
+ static VALUE rb_ePlatformAlreadyInitializedError;
149
+ static VALUE rb_mJSON;
150
+
151
+ static VALUE rb_cFailedV8Conversion;
152
+ static VALUE rb_cDateTime = Qnil;
153
+
154
+ static std::unique_ptr<Platform> current_platform = NULL;
155
+ static std::mutex platform_lock;
156
+
157
+ static pthread_attr_t *thread_attr_p;
158
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
159
+ static bool ruby_exiting; // guarded by exit_lock
160
+
161
+ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
162
+ bool platform_already_initialized = false;
163
+
164
+ if(TYPE(flag_as_str) != T_STRING) {
165
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
166
+ rb_obj_class(flag_as_str));
167
+ }
168
+
169
+ platform_lock.lock();
170
+
171
+ if (current_platform == NULL) {
172
+ V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
173
+ } else {
174
+ platform_already_initialized = true;
175
+ }
176
+
177
+ platform_lock.unlock();
178
+
179
+ // important to raise outside of the lock
180
+ if (platform_already_initialized) {
181
+ rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
182
+ }
183
+
184
+ return Qnil;
185
+ }
186
+
187
+ static void init_v8() {
188
+ // no need to wait for the lock if already initialized
189
+ if (current_platform != NULL) return;
190
+
191
+ platform_lock.lock();
192
+
193
+ if (current_platform == NULL) {
194
+ V8::InitializeICU();
195
+ current_platform = platform::NewDefaultPlatform();
196
+ V8::InitializePlatform(current_platform.get());
197
+ V8::Initialize();
198
+ }
199
+
200
+ platform_lock.unlock();
201
+ }
202
+
203
+ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
204
+ if((bool)isolate->GetData(MEM_SOFTLIMIT_REACHED)) return;
205
+
206
+ size_t softlimit = *(size_t*) isolate->GetData(MEM_SOFTLIMIT_VALUE);
207
+
208
+ HeapStatistics stats;
209
+ isolate->GetHeapStatistics(&stats);
210
+ size_t used = stats.used_heap_size();
211
+
212
+ if(used > softlimit) {
213
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
214
+ isolate->TerminateExecution();
215
+ }
216
+ }
217
+
218
+ // to be called with active lock and scope
219
+ static void prepare_result(MaybeLocal<Value> v8res,
220
+ TryCatch& trycatch,
221
+ Isolate* isolate,
222
+ Local<Context> context,
223
+ EvalResult& evalRes /* out */) {
224
+
225
+ // just don't touch .parsed
226
+ evalRes.terminated = false;
227
+ evalRes.json = false;
228
+ evalRes.value = nullptr;
229
+ evalRes.message = nullptr;
230
+ evalRes.backtrace = nullptr;
231
+ evalRes.executed = !v8res.IsEmpty();
232
+
233
+ if (evalRes.executed) {
234
+ // arrays and objects get converted to json
235
+ Local<Value> local_value = v8res.ToLocalChecked();
236
+ if ((local_value->IsObject() || local_value->IsArray()) &&
237
+ !local_value->IsDate() && !local_value->IsFunction()) {
238
+ Local<Object> JSON = context->Global()->Get(
239
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
240
+ .ToLocalChecked().As<Object>();
241
+
242
+ Local<Function> stringify = JSON->Get(
243
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
244
+ .ToLocalChecked().As<Function>();
245
+
246
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
247
+ const unsigned argc = 1;
248
+ Local<Value> argv[argc] = { object };
249
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
250
+
251
+ if (json.IsEmpty()) {
252
+ evalRes.executed = false;
253
+ } else {
254
+ evalRes.json = true;
255
+ Persistent<Value>* persistent = new Persistent<Value>();
256
+ persistent->Reset(isolate, json.ToLocalChecked());
257
+ evalRes.value = persistent;
258
+ }
259
+
260
+ } else {
261
+ Persistent<Value>* persistent = new Persistent<Value>();
262
+ persistent->Reset(isolate, local_value);
263
+ evalRes.value = persistent;
264
+ }
265
+ }
266
+
267
+ if (!evalRes.executed || !evalRes.parsed) {
268
+ if (trycatch.HasCaught()) {
269
+ if (!trycatch.Exception()->IsNull()) {
270
+ evalRes.message = new Persistent<Value>();
271
+ Local<Message> message = trycatch.Message();
272
+ char buf[1000];
273
+ int len, line, column;
274
+
275
+ if (!message->GetLineNumber(context).To(&line)) {
276
+ line = 0;
277
+ }
278
+
279
+ if (!message->GetStartColumn(context).To(&column)) {
280
+ column = 0;
281
+ }
282
+
283
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
284
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
285
+ line,
286
+ column);
287
+
288
+ if ((size_t) len >= sizeof(buf)) {
289
+ len = sizeof(buf) - 1;
290
+ buf[len] = '\0';
291
+ }
292
+
293
+ Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
294
+ evalRes.message->Reset(isolate, v8_message);
295
+ } else if(trycatch.HasTerminated()) {
296
+ evalRes.terminated = true;
297
+ evalRes.message = new Persistent<Value>();
298
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
299
+ evalRes.message->Reset(isolate, tmp);
300
+ }
301
+ if (!trycatch.StackTrace(context).IsEmpty()) {
302
+ evalRes.backtrace = new Persistent<Value>();
303
+ evalRes.backtrace->Reset(isolate,
304
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ void*
311
+ nogvl_context_eval(void* arg) {
312
+
313
+ EvalParams* eval_params = (EvalParams*)arg;
314
+ EvalResult* result = eval_params->result;
315
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
316
+ Isolate* isolate = isolate_info->isolate;
317
+
318
+ Isolate::Scope isolate_scope(isolate);
319
+ HandleScope handle_scope(isolate);
320
+ TryCatch trycatch(isolate);
321
+ Local<Context> context = eval_params->context_info->context->Get(isolate);
322
+ Context::Scope context_scope(context);
323
+ v8::ScriptOrigin *origin = NULL;
324
+
325
+ // in gvl flag
326
+ isolate->SetData(IN_GVL, (void*)false);
327
+ // terminate ASAP
328
+ isolate->SetData(DO_TERMINATE, (void*)false);
329
+ // Memory softlimit
330
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, (void*)false);
331
+ // Memory softlimit hit flag
332
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
333
+
334
+ MaybeLocal<Script> parsed_script;
335
+
336
+ if (eval_params->filename) {
337
+ origin = new v8::ScriptOrigin(*eval_params->filename);
338
+ }
339
+
340
+ parsed_script = Script::Compile(context, *eval_params->eval, origin);
341
+
342
+ if (origin) {
343
+ delete origin;
344
+ }
345
+
346
+ result->parsed = !parsed_script.IsEmpty();
347
+ result->executed = false;
348
+ result->terminated = false;
349
+ result->json = false;
350
+ result->value = NULL;
351
+
352
+ MaybeLocal<Value> maybe_value;
353
+ if (!result->parsed) {
354
+ result->message = new Persistent<Value>();
355
+ result->message->Reset(isolate, trycatch.Exception());
356
+ } else {
357
+ // parsing successful
358
+ if (eval_params->max_memory > 0) {
359
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
360
+ if (!isolate_info->added_gc_cb) {
361
+ isolate->AddGCEpilogueCallback(gc_callback);
362
+ isolate_info->added_gc_cb = true;
363
+ }
364
+ }
365
+
366
+ maybe_value = parsed_script.ToLocalChecked()->Run(context);
367
+ }
368
+
369
+ prepare_result(maybe_value, trycatch, isolate, context, *result);
370
+
371
+ isolate->SetData(IN_GVL, (void*)true);
372
+
373
+ return NULL;
374
+ }
375
+
376
+ static VALUE new_empty_failed_conv_obj() {
377
+ // TODO isolate code that translates execption to ruby
378
+ // exception so we can properly return it
379
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
380
+ }
381
+
382
+ // assumes isolate locking is in place
383
+ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
384
+ Local<Value> value) {
385
+
386
+ Isolate::Scope isolate_scope(isolate);
387
+ HandleScope scope(isolate);
388
+
389
+ if (value->IsNull() || value->IsUndefined()){
390
+ return Qnil;
391
+ }
392
+
393
+ if (value->IsInt32()) {
394
+ return INT2FIX(value->Int32Value(context).ToChecked());
395
+ }
396
+
397
+ if (value->IsNumber()) {
398
+ return rb_float_new(value->NumberValue(context).ToChecked());
399
+ }
400
+
401
+ if (value->IsTrue()) {
402
+ return Qtrue;
403
+ }
404
+
405
+ if (value->IsFalse()) {
406
+ return Qfalse;
407
+ }
408
+
409
+ if (value->IsArray()) {
410
+ VALUE rb_array = rb_ary_new();
411
+ Local<Array> arr = Local<Array>::Cast(value);
412
+ for(uint32_t i=0; i < arr->Length(); i++) {
413
+ MaybeLocal<Value> element = arr->Get(context, i);
414
+ if (element.IsEmpty()) {
415
+ continue;
416
+ }
417
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
418
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
419
+ return rb_elem;
420
+ }
421
+ rb_ary_push(rb_array, rb_elem);
422
+ }
423
+ return rb_array;
424
+ }
425
+
426
+ if (value->IsFunction()){
427
+ return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
428
+ }
429
+
430
+ if (value->IsDate()){
431
+ double ts = Local<Date>::Cast(value)->ValueOf();
432
+ double secs = ts/1000;
433
+ long nanos = round((secs - floor(secs)) * 1000000);
434
+
435
+ return rb_time_new(secs, nanos);
436
+ }
437
+
438
+ if (value->IsObject()) {
439
+ VALUE rb_hash = rb_hash_new();
440
+ TryCatch trycatch(isolate);
441
+
442
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
443
+ auto maybe_props = object->GetOwnPropertyNames(context);
444
+ if (!maybe_props.IsEmpty()) {
445
+ Local<Array> props = maybe_props.ToLocalChecked();
446
+ for(uint32_t i=0; i < props->Length(); i++) {
447
+ MaybeLocal<Value> key = props->Get(context, i);
448
+ if (key.IsEmpty()) {
449
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
450
+ }
451
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
452
+
453
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
454
+ // this may have failed due to Get raising
455
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
456
+ return new_empty_failed_conv_obj();
457
+ }
458
+
459
+ VALUE rb_value = convert_v8_to_ruby(
460
+ isolate, context, prop_value.ToLocalChecked());
461
+ rb_hash_aset(rb_hash, rb_key, rb_value);
462
+ }
463
+ }
464
+ return rb_hash;
465
+ }
466
+
467
+ if (value->IsSymbol()) {
468
+ v8::String::Utf8Value symbol_name(isolate,
469
+ Local<Symbol>::Cast(value)->Name());
470
+
471
+ VALUE str_symbol = rb_enc_str_new(
472
+ *symbol_name,
473
+ symbol_name.length(),
474
+ rb_enc_find("utf-8")
475
+ );
476
+
477
+ return ID2SYM(rb_intern_str(str_symbol));
478
+ }
479
+
480
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
481
+
482
+ if (rstr_maybe.IsEmpty()) {
483
+ return Qnil;
484
+ } else {
485
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
486
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
487
+ }
488
+ }
489
+
490
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
491
+ const Persistent<Context>& context,
492
+ Local<Value> value) {
493
+ HandleScope scope(isolate);
494
+ return convert_v8_to_ruby(isolate,
495
+ Local<Context>::New(isolate, context),
496
+ value);
497
+ }
498
+
499
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
500
+ const Persistent<Context>& context,
501
+ const Persistent<Value>& value) {
502
+ HandleScope scope(isolate);
503
+ return convert_v8_to_ruby(isolate,
504
+ Local<Context>::New(isolate, context),
505
+ Local<Value>::New(isolate, value));
506
+ }
507
+
508
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
509
+ EscapableHandleScope scope(isolate);
510
+
511
+ Local<Array> array;
512
+ Local<Object> object;
513
+ VALUE hash_as_array;
514
+ VALUE pair;
515
+ int i;
516
+ long length;
517
+ long fixnum;
518
+ VALUE klass;
519
+
520
+ switch (TYPE(value)) {
521
+ case T_FIXNUM:
522
+ fixnum = NUM2LONG(value);
523
+ if (fixnum > INT_MAX)
524
+ {
525
+ return scope.Escape(Number::New(isolate, (double)fixnum));
526
+ }
527
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
528
+ case T_FLOAT:
529
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
530
+ case T_STRING:
531
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
532
+ case T_NIL:
533
+ return scope.Escape(Null(isolate));
534
+ case T_TRUE:
535
+ return scope.Escape(True(isolate));
536
+ case T_FALSE:
537
+ return scope.Escape(False(isolate));
538
+ case T_ARRAY:
539
+ length = RARRAY_LEN(value);
540
+ array = Array::New(isolate, (int)length);
541
+ for(i=0; i<length; i++) {
542
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
543
+ }
544
+ return scope.Escape(array);
545
+ case T_HASH:
546
+ object = Object::New(isolate);
547
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
548
+ length = RARRAY_LEN(hash_as_array);
549
+ for(i=0; i<length; i++) {
550
+ pair = rb_ary_entry(hash_as_array, i);
551
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
552
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
553
+ }
554
+ return scope.Escape(object);
555
+ case T_SYMBOL:
556
+ value = rb_funcall(value, rb_intern("to_s"), 0);
557
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
558
+ case T_DATA:
559
+ klass = rb_funcall(value, rb_intern("class"), 0);
560
+ if (klass == rb_cTime || klass == rb_cDateTime)
561
+ {
562
+ if (klass == rb_cDateTime)
563
+ {
564
+ value = rb_funcall(value, rb_intern("to_time"), 0);
565
+ }
566
+ value = rb_funcall(value, rb_intern("to_f"), 0);
567
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
568
+ }
569
+ case T_OBJECT:
570
+ case T_CLASS:
571
+ case T_ICLASS:
572
+ case T_MODULE:
573
+ case T_REGEXP:
574
+ case T_MATCH:
575
+ case T_STRUCT:
576
+ case T_BIGNUM:
577
+ case T_FILE:
578
+ case T_UNDEF:
579
+ case T_NODE:
580
+ default:
581
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
582
+ }
583
+ }
584
+
585
+ static void unblock_eval(void *ptr) {
586
+ EvalParams* eval = (EvalParams*)ptr;
587
+ eval->context_info->isolate_info->interrupted = true;
588
+ }
589
+
590
+ /*
591
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
592
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
593
+ */
594
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
595
+ const char *utf8_source, const char *name) {
596
+ Context::Scope context_scope(context);
597
+ TryCatch try_catch(isolate);
598
+ Local<String> source_string;
599
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
600
+ return false;
601
+ }
602
+ Local<String> resource_name =
603
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
604
+ ScriptOrigin origin(resource_name);
605
+ ScriptCompiler::Source source(source_string, origin);
606
+ Local<Script> script;
607
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
608
+ return false;
609
+ if (script->Run(context).IsEmpty()) return false;
610
+ return true;
611
+ }
612
+
613
+ static StartupData
614
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
615
+ Isolate *isolate = Isolate::Allocate();
616
+
617
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
618
+ SnapshotCreator snapshot_creator(isolate);
619
+ {
620
+ HandleScope scope(isolate);
621
+ Local<v8::Context> context = v8::Context::New(isolate);
622
+ if (embedded_source != nullptr &&
623
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
624
+ return {};
625
+ }
626
+ snapshot_creator.SetDefaultContext(context);
627
+ }
628
+ return snapshot_creator.CreateBlob(
629
+ SnapshotCreator::FunctionCodeHandling::kClear);
630
+ }
631
+
632
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
633
+ const char *warmup_source) {
634
+ // Use following steps to create a warmed up snapshot blob from a cold one:
635
+ // - Create a new isolate from the cold snapshot.
636
+ // - Create a new context to run the warmup script. This will trigger
637
+ // compilation of executed functions.
638
+ // - Create a new context. This context will be unpolluted.
639
+ // - Serialize the isolate and the second context into a new snapshot blob.
640
+ StartupData result = {nullptr, 0};
641
+
642
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
643
+ warmup_source != NULL) {
644
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
645
+ Isolate *isolate = snapshot_creator.GetIsolate();
646
+ {
647
+ HandleScope scope(isolate);
648
+ Local<Context> context = Context::New(isolate);
649
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
650
+ return result;
651
+ }
652
+ }
653
+ {
654
+ HandleScope handle_scope(isolate);
655
+ isolate->ContextDisposedNotification(false);
656
+ Local<Context> context = Context::New(isolate);
657
+ snapshot_creator.SetDefaultContext(context);
658
+ }
659
+ result = snapshot_creator.CreateBlob(
660
+ SnapshotCreator::FunctionCodeHandling::kKeep);
661
+ }
662
+ return result;
663
+ }
664
+
665
+ static VALUE rb_snapshot_size(VALUE self, VALUE str) {
666
+ SnapshotInfo* snapshot_info;
667
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
668
+
669
+ return INT2NUM(snapshot_info->raw_size);
670
+ }
671
+
672
+ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
673
+ SnapshotInfo* snapshot_info;
674
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
675
+
676
+ if(TYPE(str) != T_STRING) {
677
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
678
+ rb_obj_class(str));
679
+ }
680
+
681
+ init_v8();
682
+
683
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
684
+
685
+ if (startup_data.data == NULL && startup_data.raw_size == 0) {
686
+ rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
687
+ }
688
+
689
+ snapshot_info->data = startup_data.data;
690
+ snapshot_info->raw_size = startup_data.raw_size;
691
+
692
+ return Qnil;
693
+ }
694
+
695
+ static VALUE rb_snapshot_dump(VALUE self, VALUE str) {
696
+ SnapshotInfo* snapshot_info;
697
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
698
+
699
+ return rb_str_new(snapshot_info->data, snapshot_info->raw_size);
700
+ }
701
+
702
+ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
703
+ SnapshotInfo* snapshot_info;
704
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
705
+
706
+ if(TYPE(str) != T_STRING) {
707
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
708
+ rb_obj_class(str));
709
+ }
710
+
711
+ init_v8();
712
+
713
+ StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
714
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
715
+
716
+ if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
717
+ rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
718
+ } else {
719
+ delete[] snapshot_info->data;
720
+
721
+ snapshot_info->data = warm_startup_data.data;
722
+ snapshot_info->raw_size = warm_startup_data.raw_size;
723
+ }
724
+
725
+ return self;
726
+ }
727
+
728
+ void IsolateInfo::init(SnapshotInfo* snapshot_info) {
729
+ allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
730
+
731
+ Isolate::CreateParams create_params;
732
+ create_params.array_buffer_allocator = allocator;
733
+
734
+ if (snapshot_info) {
735
+ int raw_size = snapshot_info->raw_size;
736
+ char* data = new char[raw_size];
737
+ memcpy(data, snapshot_info->data, raw_size);
738
+
739
+ startup_data = new StartupData;
740
+ startup_data->data = data;
741
+ startup_data->raw_size = raw_size;
742
+
743
+ create_params.snapshot_blob = startup_data;
744
+ }
745
+
746
+ isolate = Isolate::New(create_params);
747
+ }
748
+
749
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
750
+ IsolateInfo* isolate_info;
751
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
752
+
753
+ init_v8();
754
+
755
+ SnapshotInfo* snapshot_info = nullptr;
756
+ if (!NIL_P(snapshot)) {
757
+ Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
758
+ }
759
+
760
+ isolate_info->init(snapshot_info);
761
+ isolate_info->hold();
762
+
763
+ return Qnil;
764
+ }
765
+
766
+ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
767
+ IsolateInfo* isolate_info;
768
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
769
+
770
+ if (current_platform == NULL) return Qfalse;
771
+
772
+ double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
773
+ double now = current_platform->MonotonicallyIncreasingTime();
774
+ return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
775
+ }
776
+
777
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
778
+ IsolateInfo* isolate_info;
779
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
780
+
781
+ if (current_platform == NULL) return Qfalse;
782
+
783
+ isolate_info->isolate->LowMemoryNotification();
784
+ return Qnil;
785
+ }
786
+
787
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
788
+ IsolateInfo* isolate_info;
789
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
790
+
791
+ if (current_platform == NULL) return Qfalse;
792
+
793
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
794
+ return Qtrue;
795
+ } else {
796
+ return Qfalse;
797
+ }
798
+ }
799
+
800
+ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
801
+ ContextInfo* context_info;
802
+ Data_Get_Struct(self, ContextInfo, context_info);
803
+
804
+ init_v8();
805
+
806
+ IsolateInfo* isolate_info;
807
+
808
+ if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
809
+ isolate_info = new IsolateInfo();
810
+
811
+ SnapshotInfo *snapshot_info = nullptr;
812
+ if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
813
+ Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
814
+ }
815
+ isolate_info->init(snapshot_info);
816
+ } else { // given isolate or snapshot
817
+ Data_Get_Struct(isolate, IsolateInfo, isolate_info);
818
+ }
819
+
820
+ context_info->isolate_info = isolate_info;
821
+ isolate_info->hold();
822
+
823
+ {
824
+ // the ruby lock is needed if this isn't a new isolate
825
+ IsolateInfo::Lock ruby_lock(isolate_info->mutex);
826
+ Locker lock(isolate_info->isolate);
827
+ Isolate::Scope isolate_scope(isolate_info->isolate);
828
+ HandleScope handle_scope(isolate_info->isolate);
829
+
830
+ Local<Context> context = Context::New(isolate_info->isolate);
831
+
832
+ context_info->context = new Persistent<Context>();
833
+ context_info->context->Reset(isolate_info->isolate, context);
834
+ }
835
+
836
+ if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
837
+ {
838
+ rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
839
+ }
840
+
841
+ return Qnil;
842
+ }
843
+
844
+ static VALUE convert_result_to_ruby(VALUE self /* context */,
845
+ EvalResult& result) {
846
+ ContextInfo *context_info;
847
+ Data_Get_Struct(self, ContextInfo, context_info);
848
+
849
+ Isolate *isolate = context_info->isolate_info->isolate;
850
+ Persistent<Context> *p_ctx = context_info->context;
851
+
852
+ VALUE message = Qnil;
853
+ VALUE backtrace = Qnil;
854
+ {
855
+ Locker lock(isolate);
856
+ if (result.message) {
857
+ message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
858
+ result.message->Reset();
859
+ delete result.message;
860
+ result.message = nullptr;
861
+ }
862
+
863
+ if (result.backtrace) {
864
+ backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
865
+ result.backtrace->Reset();
866
+ delete result.backtrace;
867
+ }
868
+ }
869
+
870
+ // NOTE: this is very important, we can not do an rb_raise from within
871
+ // a v8 scope, if we do the scope is never cleaned up properly and we leak
872
+ if (!result.parsed) {
873
+ if(TYPE(message) == T_STRING) {
874
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
875
+ } else {
876
+ rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
877
+ }
878
+ }
879
+
880
+ if (!result.executed) {
881
+ VALUE ruby_exception = rb_iv_get(self, "@current_exception");
882
+ if (ruby_exception == Qnil) {
883
+ bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
884
+ // If we were terminated or have the memory softlimit flag set
885
+ if (result.terminated || mem_softlimit_reached) {
886
+ ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
887
+ } else {
888
+ ruby_exception = rb_eScriptRuntimeError;
889
+ }
890
+
891
+ // exception report about what happened
892
+ if (TYPE(backtrace) == T_STRING) {
893
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
894
+ } else if(TYPE(message) == T_STRING) {
895
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
896
+ } else {
897
+ rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
898
+ }
899
+ } else {
900
+ VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
901
+ rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
902
+ }
903
+ }
904
+
905
+ VALUE ret = Qnil;
906
+
907
+ // New scope for return value
908
+ {
909
+ Locker lock(isolate);
910
+ Isolate::Scope isolate_scope(isolate);
911
+ HandleScope handle_scope(isolate);
912
+
913
+ Local<Value> tmp = Local<Value>::New(isolate, *result.value);
914
+
915
+ if (result.json) {
916
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
917
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
918
+ ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
919
+ } else {
920
+ ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
921
+ }
922
+
923
+ result.value->Reset();
924
+ delete result.value;
925
+ }
926
+
927
+ if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
928
+ // TODO try to recover stack trace from the conversion error
929
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
930
+ }
931
+
932
+
933
+ return ret;
934
+ }
935
+
936
+ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
937
+
938
+ EvalParams eval_params;
939
+ EvalResult eval_result;
940
+ ContextInfo* context_info;
941
+
942
+ Data_Get_Struct(self, ContextInfo, context_info);
943
+ Isolate* isolate = context_info->isolate_info->isolate;
944
+
945
+ if(TYPE(str) != T_STRING) {
946
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
947
+ rb_obj_class(str));
948
+ }
949
+ if(filename != Qnil && TYPE(filename) != T_STRING) {
950
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
951
+ rb_obj_class(filename));
952
+ }
953
+
954
+ {
955
+ Locker lock(isolate);
956
+ Isolate::Scope isolate_scope(isolate);
957
+ HandleScope handle_scope(isolate);
958
+
959
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
960
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
961
+
962
+ Local<String> local_filename;
963
+
964
+ if (filename != Qnil) {
965
+ local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
966
+ NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
967
+ eval_params.filename = &local_filename;
968
+ } else {
969
+ eval_params.filename = NULL;
970
+ }
971
+
972
+ eval_params.context_info = context_info;
973
+ eval_params.eval = &eval;
974
+ eval_params.result = &eval_result;
975
+ eval_params.timeout = 0;
976
+ eval_params.max_memory = 0;
977
+ VALUE timeout = rb_iv_get(self, "@timeout");
978
+ if (timeout != Qnil) {
979
+ eval_params.timeout = (useconds_t)NUM2LONG(timeout);
980
+ }
981
+
982
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
983
+ if (mem_softlimit != Qnil) {
984
+ eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
985
+ }
986
+
987
+ eval_result.message = NULL;
988
+ eval_result.backtrace = NULL;
989
+
990
+ rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
991
+ }
992
+
993
+ return convert_result_to_ruby(self, eval_result);
994
+ }
995
+
996
+ typedef struct {
997
+ VALUE callback;
998
+ int length;
999
+ VALUE ruby_args;
1000
+ bool failed;
1001
+ } protected_callback_data;
1002
+
1003
+ static VALUE protected_callback(VALUE rdata) {
1004
+ protected_callback_data* data = (protected_callback_data*)rdata;
1005
+ VALUE result;
1006
+
1007
+ if (data->length > 0) {
1008
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length,
1009
+ RARRAY_PTR(data->ruby_args));
1010
+ RB_GC_GUARD(data->ruby_args);
1011
+ } else {
1012
+ result = rb_funcall(data->callback, rb_intern("call"), 0);
1013
+ }
1014
+ return result;
1015
+ }
1016
+
1017
+ static
1018
+ VALUE rescue_callback(VALUE rdata, VALUE exception) {
1019
+ protected_callback_data* data = (protected_callback_data*)rdata;
1020
+ data->failed = true;
1021
+ return exception;
1022
+ }
1023
+
1024
+ void*
1025
+ gvl_ruby_callback(void* data) {
1026
+
1027
+ FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
1028
+ VALUE ruby_args = Qnil;
1029
+ int length = args->Length();
1030
+ VALUE callback;
1031
+ VALUE result;
1032
+ VALUE self;
1033
+ VALUE parent;
1034
+ ContextInfo* context_info;
1035
+
1036
+ {
1037
+ HandleScope scope(args->GetIsolate());
1038
+ Local<External> external = Local<External>::Cast(args->Data());
1039
+
1040
+ self = *(VALUE*)(external->Value());
1041
+ callback = rb_iv_get(self, "@callback");
1042
+
1043
+ parent = rb_iv_get(self, "@parent");
1044
+ if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
1045
+ return NULL;
1046
+ }
1047
+
1048
+ Data_Get_Struct(parent, ContextInfo, context_info);
1049
+
1050
+ if (length > 0) {
1051
+ ruby_args = rb_ary_tmp_new(length);
1052
+ }
1053
+
1054
+ for (int i = 0; i < length; i++) {
1055
+ Local<Value> value = ((*args)[i]).As<Value>();
1056
+ VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1057
+ *context_info->context, value);
1058
+ rb_ary_push(ruby_args, tmp);
1059
+ }
1060
+ }
1061
+
1062
+ // may raise exception stay clear of handle scope
1063
+ protected_callback_data callback_data;
1064
+ callback_data.length = length;
1065
+ callback_data.callback = callback;
1066
+ callback_data.ruby_args = ruby_args;
1067
+ callback_data.failed = false;
1068
+
1069
+ if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1070
+ args->GetIsolate()->ThrowException(
1071
+ String::NewFromUtf8Literal(args->GetIsolate(),
1072
+ "Terminated execution during transition from Ruby to JS"));
1073
+ args->GetIsolate()->TerminateExecution();
1074
+ if (length > 0) {
1075
+ rb_ary_clear(ruby_args);
1076
+ rb_gc_force_recycle(ruby_args);
1077
+ }
1078
+ return NULL;
1079
+ }
1080
+
1081
+ result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
1082
+ (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
1083
+
1084
+ if(callback_data.failed) {
1085
+ rb_iv_set(parent, "@current_exception", result);
1086
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1087
+ }
1088
+ else {
1089
+ HandleScope scope(args->GetIsolate());
1090
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1091
+ args->GetReturnValue().Set(v8_result);
1092
+ }
1093
+
1094
+ if (length > 0) {
1095
+ rb_ary_clear(ruby_args);
1096
+ rb_gc_force_recycle(ruby_args);
1097
+ }
1098
+
1099
+ if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1100
+ Isolate* isolate = args->GetIsolate();
1101
+ isolate->TerminateExecution();
1102
+ }
1103
+
1104
+ return NULL;
1105
+ }
1106
+
1107
+ static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
1108
+ bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
1109
+
1110
+ if(has_gvl) {
1111
+ gvl_ruby_callback((void*)&args);
1112
+ } else {
1113
+ args.GetIsolate()->SetData(IN_GVL, (void*)true);
1114
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1115
+ args.GetIsolate()->SetData(IN_GVL, (void*)false);
1116
+ }
1117
+ }
1118
+
1119
+
1120
+ static VALUE rb_external_function_notify_v8(VALUE self) {
1121
+
1122
+ ContextInfo* context_info;
1123
+
1124
+ VALUE parent = rb_iv_get(self, "@parent");
1125
+ VALUE name = rb_iv_get(self, "@name");
1126
+ VALUE parent_object = rb_iv_get(self, "@parent_object");
1127
+ VALUE parent_object_eval = rb_iv_get(self, "@parent_object_eval");
1128
+
1129
+ bool parse_error = false;
1130
+ bool attach_error = false;
1131
+
1132
+ Data_Get_Struct(parent, ContextInfo, context_info);
1133
+ Isolate* isolate = context_info->isolate_info->isolate;
1134
+
1135
+ {
1136
+ Locker lock(isolate);
1137
+ Isolate::Scope isolate_scope(isolate);
1138
+ HandleScope handle_scope(isolate);
1139
+
1140
+ Local<Context> context = context_info->context->Get(isolate);
1141
+ Context::Scope context_scope(context);
1142
+
1143
+ Local<String> v8_str =
1144
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1145
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1146
+ .ToLocalChecked();
1147
+
1148
+ // copy self so we can access from v8 external
1149
+ VALUE* self_copy;
1150
+ Data_Get_Struct(self, VALUE, self_copy);
1151
+ *self_copy = self;
1152
+
1153
+ Local<Value> external = External::New(isolate, self_copy);
1154
+
1155
+ if (parent_object == Qnil) {
1156
+ context->Global()->Set(
1157
+ context,
1158
+ v8_str,
1159
+ FunctionTemplate::New(isolate, ruby_callback, external)
1160
+ ->GetFunction(context)
1161
+ .ToLocalChecked());
1162
+
1163
+ } else {
1164
+ Local<String> eval =
1165
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1166
+ NewStringType::kNormal,
1167
+ (int)RSTRING_LEN(parent_object_eval))
1168
+ .ToLocalChecked();
1169
+
1170
+ MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1171
+ if (parsed_script.IsEmpty()) {
1172
+ parse_error = true;
1173
+ } else {
1174
+ MaybeLocal<Value> maybe_value =
1175
+ parsed_script.ToLocalChecked()->Run(context);
1176
+ attach_error = true;
1177
+
1178
+ if (!maybe_value.IsEmpty()) {
1179
+ Local<Value> value = maybe_value.ToLocalChecked();
1180
+ if (value->IsObject()) {
1181
+ value.As<Object>()->Set(
1182
+ context,
1183
+ v8_str,
1184
+ FunctionTemplate::New(isolate, ruby_callback, external)
1185
+ ->GetFunction(context)
1186
+ .ToLocalChecked());
1187
+ attach_error = false;
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ // always raise out of V8 context
1195
+ if (parse_error) {
1196
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
1197
+ }
1198
+
1199
+ if (attach_error) {
1200
+ rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
1201
+ }
1202
+
1203
+ return Qnil;
1204
+ }
1205
+
1206
+ static VALUE rb_context_isolate_mutex(VALUE self) {
1207
+ ContextInfo* context_info;
1208
+ Data_Get_Struct(self, ContextInfo, context_info);
1209
+
1210
+ if (!context_info->isolate_info) {
1211
+ rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
1212
+ }
1213
+
1214
+ return context_info->isolate_info->mutex;
1215
+ }
1216
+
1217
+ IsolateInfo::~IsolateInfo() {
1218
+ if (isolate) {
1219
+ if (this->interrupted) {
1220
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1221
+ "it can not be disposed and memory will not be "
1222
+ "reclaimed till the Ruby process exits.\n");
1223
+ } else {
1224
+ if (this->pid != getpid()) {
1225
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1226
+ "it can not be disposed and "
1227
+ "memory will not be reclaimed "
1228
+ "till the Ruby process exits.\n");
1229
+ } else {
1230
+ isolate->Dispose();
1231
+ }
1232
+ }
1233
+ isolate = nullptr;
1234
+ }
1235
+
1236
+ if (startup_data) {
1237
+ delete[] startup_data->data;
1238
+ delete startup_data;
1239
+ }
1240
+
1241
+ delete allocator;
1242
+ }
1243
+
1244
+ static void free_context_raw(void *arg) {
1245
+ ContextInfo* context_info = (ContextInfo*)arg;
1246
+ IsolateInfo* isolate_info = context_info->isolate_info;
1247
+ Persistent<Context>* context = context_info->context;
1248
+
1249
+ if (context && isolate_info && isolate_info->isolate) {
1250
+ Locker lock(isolate_info->isolate);
1251
+ v8::Isolate::Scope isolate_scope(isolate_info->isolate);
1252
+ context->Reset();
1253
+ delete context;
1254
+ }
1255
+
1256
+ if (isolate_info) {
1257
+ isolate_info->release();
1258
+ }
1259
+
1260
+ xfree(context_info);
1261
+ }
1262
+
1263
+ static void *free_context_thr(void* arg) {
1264
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1265
+ return NULL;
1266
+ }
1267
+ if (ruby_exiting) {
1268
+ return NULL;
1269
+ }
1270
+
1271
+ free_context_raw(arg);
1272
+
1273
+ pthread_rwlock_unlock(&exit_lock);
1274
+
1275
+ return NULL;
1276
+ }
1277
+
1278
+ // destroys everything except freeing the ContextInfo struct (see deallocate())
1279
+ static void free_context(ContextInfo* context_info) {
1280
+
1281
+ IsolateInfo* isolate_info = context_info->isolate_info;
1282
+
1283
+ ContextInfo* context_info_copy = ALLOC(ContextInfo);
1284
+ context_info_copy->isolate_info = context_info->isolate_info;
1285
+ context_info_copy->context = context_info->context;
1286
+
1287
+ if (isolate_info && isolate_info->refs() > 1) {
1288
+ pthread_t free_context_thread;
1289
+ if (pthread_create(&free_context_thread, thread_attr_p,
1290
+ free_context_thr, (void*)context_info_copy)) {
1291
+ fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1292
+ }
1293
+ } else {
1294
+ free_context_raw(context_info_copy);
1295
+ }
1296
+
1297
+ context_info->context = NULL;
1298
+ context_info->isolate_info = NULL;
1299
+ }
1300
+
1301
+ static void deallocate_isolate(void* data) {
1302
+
1303
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1304
+
1305
+ isolate_info->release();
1306
+ }
1307
+
1308
+ static void mark_isolate(void* data) {
1309
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1310
+ isolate_info->mark();
1311
+ }
1312
+
1313
+ static void deallocate(void* data) {
1314
+ ContextInfo* context_info = (ContextInfo*)data;
1315
+
1316
+ free_context(context_info);
1317
+
1318
+ xfree(data);
1319
+ }
1320
+
1321
+ static void mark_context(void* data) {
1322
+ ContextInfo* context_info = (ContextInfo*)data;
1323
+ if (context_info->isolate_info) {
1324
+ context_info->isolate_info->mark();
1325
+ }
1326
+ }
1327
+
1328
+ static void deallocate_external_function(void * data) {
1329
+ xfree(data);
1330
+ }
1331
+
1332
+ static void deallocate_snapshot(void * data) {
1333
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1334
+ delete[] snapshot_info->data;
1335
+ xfree(snapshot_info);
1336
+ }
1337
+
1338
+ static VALUE allocate_external_function(VALUE klass) {
1339
+ VALUE* self = ALLOC(VALUE);
1340
+ return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1341
+ }
1342
+
1343
+ static VALUE allocate(VALUE klass) {
1344
+ ContextInfo* context_info = ALLOC(ContextInfo);
1345
+ context_info->isolate_info = NULL;
1346
+ context_info->context = NULL;
1347
+
1348
+ return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1349
+ }
1350
+
1351
+ static VALUE allocate_snapshot(VALUE klass) {
1352
+ SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1353
+ snapshot_info->data = NULL;
1354
+ snapshot_info->raw_size = 0;
1355
+
1356
+ return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1357
+ }
1358
+
1359
+ static VALUE allocate_isolate(VALUE klass) {
1360
+ IsolateInfo* isolate_info = new IsolateInfo();
1361
+
1362
+ return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
1363
+ }
1364
+
1365
+ static VALUE
1366
+ rb_heap_stats(VALUE self) {
1367
+
1368
+ ContextInfo* context_info;
1369
+ Data_Get_Struct(self, ContextInfo, context_info);
1370
+ Isolate* isolate;
1371
+ v8::HeapStatistics stats;
1372
+
1373
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1374
+
1375
+ VALUE rval = rb_hash_new();
1376
+
1377
+ if (!isolate) {
1378
+
1379
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1380
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1381
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1382
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1383
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
1384
+
1385
+ } else {
1386
+ isolate->GetHeapStatistics(&stats);
1387
+
1388
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1389
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1390
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1391
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1392
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
1393
+ }
1394
+
1395
+ return rval;
1396
+ }
1397
+
1398
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1399
+ class FileOutputStream : public OutputStream {
1400
+ public:
1401
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1402
+
1403
+ virtual int GetChunkSize() {
1404
+ return 65536;
1405
+ }
1406
+
1407
+ virtual void EndOfStream() {}
1408
+
1409
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1410
+ const size_t len = static_cast<size_t>(size);
1411
+ size_t off = 0;
1412
+
1413
+ while (off < len && !feof(stream_) && !ferror(stream_))
1414
+ off += fwrite(data + off, 1, len - off, stream_);
1415
+
1416
+ return off == len ? kContinue : kAbort;
1417
+ }
1418
+
1419
+ private:
1420
+ FILE* stream_;
1421
+ };
1422
+
1423
+
1424
+ static VALUE
1425
+ rb_heap_snapshot(VALUE self, VALUE file) {
1426
+
1427
+ rb_io_t *fptr;
1428
+
1429
+ fptr = RFILE(file)->fptr;
1430
+
1431
+ if (!fptr) return Qfalse;
1432
+
1433
+ FILE* fp;
1434
+ fp = fdopen(fptr->fd, "w");
1435
+ if (fp == NULL) return Qfalse;
1436
+
1437
+
1438
+ ContextInfo* context_info;
1439
+ Data_Get_Struct(self, ContextInfo, context_info);
1440
+ Isolate* isolate;
1441
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1442
+
1443
+ if (!isolate) return Qfalse;
1444
+
1445
+ Locker lock(isolate);
1446
+ Isolate::Scope isolate_scope(isolate);
1447
+ HandleScope handle_scope(isolate);
1448
+
1449
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1450
+
1451
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1452
+
1453
+ FileOutputStream stream(fp);
1454
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1455
+
1456
+ fflush(fp);
1457
+
1458
+ const_cast<HeapSnapshot*>(snap)->Delete();
1459
+
1460
+ return Qtrue;
1461
+ }
1462
+
1463
+ static VALUE
1464
+ rb_context_stop(VALUE self) {
1465
+
1466
+ ContextInfo* context_info;
1467
+ Data_Get_Struct(self, ContextInfo, context_info);
1468
+
1469
+ Isolate* isolate = context_info->isolate_info->isolate;
1470
+
1471
+ // flag for termination
1472
+ isolate->SetData(DO_TERMINATE, (void*)true);
1473
+
1474
+ isolate->TerminateExecution();
1475
+ rb_funcall(self, rb_intern("stop_attached"), 0);
1476
+
1477
+ return Qnil;
1478
+ }
1479
+
1480
+ static VALUE
1481
+ rb_context_dispose(VALUE self) {
1482
+
1483
+ ContextInfo* context_info;
1484
+ Data_Get_Struct(self, ContextInfo, context_info);
1485
+
1486
+ free_context(context_info);
1487
+
1488
+ return Qnil;
1489
+ }
1490
+
1491
+ static void*
1492
+ nogvl_context_call(void *args) {
1493
+
1494
+ FunctionCall *call = (FunctionCall *) args;
1495
+ if (!call) {
1496
+ return NULL;
1497
+ }
1498
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1499
+ Isolate* isolate = isolate_info->isolate;
1500
+
1501
+ // in gvl flag
1502
+ isolate->SetData(IN_GVL, (void*)false);
1503
+ // terminate ASAP
1504
+ isolate->SetData(DO_TERMINATE, (void*)false);
1505
+
1506
+ if (call->max_memory > 0) {
1507
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1508
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1509
+ if (!isolate_info->added_gc_cb) {
1510
+ isolate->AddGCEpilogueCallback(gc_callback);
1511
+ isolate_info->added_gc_cb = true;
1512
+ }
1513
+ }
1514
+
1515
+ Isolate::Scope isolate_scope(isolate);
1516
+ EscapableHandleScope handle_scope(isolate);
1517
+ TryCatch trycatch(isolate);
1518
+
1519
+ Local<Context> context = call->context_info->context->Get(isolate);
1520
+ Context::Scope context_scope(context);
1521
+
1522
+ Local<Function> fun = call->fun;
1523
+
1524
+ EvalResult& eval_res = call->result;
1525
+ eval_res.parsed = true;
1526
+
1527
+ MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
1528
+ prepare_result(res, trycatch, isolate, context, eval_res);
1529
+
1530
+ isolate->SetData(IN_GVL, (void*)true);
1531
+
1532
+ return NULL;
1533
+ }
1534
+
1535
+ static void unblock_function(void *args) {
1536
+ FunctionCall *call = (FunctionCall *) args;
1537
+ call->context_info->isolate_info->interrupted = true;
1538
+ }
1539
+
1540
+ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1541
+ ContextInfo* context_info;
1542
+ FunctionCall call;
1543
+ VALUE *call_argv = NULL;
1544
+
1545
+ Data_Get_Struct(self, ContextInfo, context_info);
1546
+ Isolate* isolate = context_info->isolate_info->isolate;
1547
+
1548
+ if (argc < 1) {
1549
+ rb_raise(rb_eArgError, "need at least one argument %d", argc);
1550
+ }
1551
+
1552
+ VALUE function_name = argv[0];
1553
+ if (TYPE(function_name) != T_STRING) {
1554
+ rb_raise(rb_eTypeError, "first argument should be a String");
1555
+ }
1556
+
1557
+ char *fname = RSTRING_PTR(function_name);
1558
+ if (!fname) {
1559
+ return Qnil;
1560
+ }
1561
+
1562
+ call.context_info = context_info;
1563
+ call.error = false;
1564
+ call.function_name = fname;
1565
+ call.argc = argc - 1;
1566
+ call.argv = NULL;
1567
+ if (call.argc > 0) {
1568
+ // skip first argument which is the function name
1569
+ call_argv = argv + 1;
1570
+ }
1571
+
1572
+ call.max_memory = 0;
1573
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1574
+ if (mem_softlimit != Qnil) {
1575
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1576
+ call.max_memory = (size_t)sl_int;
1577
+ }
1578
+
1579
+ bool missingFunction = false;
1580
+ {
1581
+ Locker lock(isolate);
1582
+ Isolate::Scope isolate_scope(isolate);
1583
+ HandleScope handle_scope(isolate);
1584
+
1585
+ Local<Context> context = context_info->context->Get(isolate);
1586
+ Context::Scope context_scope(context);
1587
+
1588
+ // examples of such usage can be found in
1589
+ // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1590
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1591
+ MaybeLocal<v8::Value> val;
1592
+ if (!fname.IsEmpty()) {
1593
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1594
+ }
1595
+
1596
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1597
+ missingFunction = true;
1598
+ } else {
1599
+
1600
+ Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1601
+ call.fun = fun;
1602
+ int fun_argc = call.argc;
1603
+
1604
+ if (fun_argc > 0) {
1605
+ call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1606
+ if (!call.argv) {
1607
+ return Qnil;
1608
+ }
1609
+ for(int i=0; i < fun_argc; i++) {
1610
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1611
+ }
1612
+ }
1613
+ rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1614
+ free(call.argv);
1615
+
1616
+ }
1617
+ }
1618
+
1619
+ if (missingFunction) {
1620
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1621
+ }
1622
+
1623
+ return convert_result_to_ruby(self, call.result);
1624
+ }
1625
+
1626
+ static VALUE rb_context_create_isolate_value(VALUE self) {
1627
+ ContextInfo* context_info;
1628
+ Data_Get_Struct(self, ContextInfo, context_info);
1629
+ IsolateInfo *isolate_info = context_info->isolate_info;
1630
+
1631
+ if (!isolate_info) {
1632
+ return Qnil;
1633
+ }
1634
+
1635
+ isolate_info->hold();
1636
+ return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1637
+ }
1638
+
1639
+ static void set_ruby_exiting(VALUE value) {
1640
+ (void)value;
1641
+
1642
+ int res = pthread_rwlock_wrlock(&exit_lock);
1643
+ ruby_exiting = true;
1644
+ if (res == 0) {
1645
+ pthread_rwlock_unlock(&exit_lock);
1646
+ }
1647
+ }
1648
+
1649
+ extern "C" {
1650
+
1651
+ void Init_mini_racer_extension ( void )
1652
+ {
1653
+ VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1654
+ rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1655
+ rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1656
+ rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1657
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1658
+
1659
+ VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1660
+
1661
+ VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1662
+ rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1663
+ rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1664
+ rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1665
+ rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
1666
+
1667
+ rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1668
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
1669
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
1670
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
1671
+ rb_mJSON = rb_define_module("JSON");
1672
+
1673
+ VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1674
+
1675
+ rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1676
+ rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1677
+ rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1678
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1679
+
1680
+ rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1681
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1682
+ rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1683
+ rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1684
+ rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1685
+
1686
+ rb_define_alloc_func(rb_cContext, allocate);
1687
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1688
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1689
+
1690
+ rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1691
+ rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1692
+
1693
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1694
+ rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
1695
+ rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1696
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1697
+
1698
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1699
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1700
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1701
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1702
+
1703
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1704
+
1705
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1706
+
1707
+ static pthread_attr_t attr;
1708
+ if (pthread_attr_init(&attr) == 0) {
1709
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1710
+ thread_attr_p = &attr;
1711
+ }
1712
+ }
1713
+ }
1714
+ }