mini_racer 0.3.0

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