sq_mini_racer 0.2.4.sqreen2 → 0.3.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,33 +1,232 @@
1
1
  require 'mkmf'
2
+
2
3
  require 'fileutils'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'rubygems'
7
+ require 'rubygems/package'
3
8
 
4
- IS_DARWIN = RUBY_PLATFORM =~ /darwin/
5
9
  IS_SOLARIS = RUBY_PLATFORM =~ /solaris/
10
+ IS_LINUX_MUSL = RUBY_PLATFORM =~ /linux-musl/
11
+
12
+ def cppflags_clear_std!
13
+ $CPPFLAGS.gsub! /-std=[^\s]+/, ''
14
+ end
15
+
16
+ def cppflags_add_frame_pointer!
17
+ $CPPFLAGS += " -fno-omit-frame-pointer"
18
+ end
19
+
20
+ def cppflags_add_cpu_extension!
21
+ if enable_config('avx2')
22
+ $CPPFLAGS += " -mavx2"
23
+ else
24
+ $CPPFLAGS += " -mssse3"
25
+ end
26
+ end
27
+
28
+ def libv8_gem_name
29
+ 'libv8-node'
30
+ end
31
+
32
+ def libv8_version
33
+ '14.14.0.0.beta2'
34
+ end
35
+
36
+ def libv8_basename
37
+ "#{libv8_gem_name}-#{libv8_version}-#{ruby_platform}"
38
+ end
39
+
40
+ def libv8_gemspec_no_libc
41
+ platform_no_libc = ruby_platform.to_s.split('-')[0..1].join('-')
42
+ "#{libv8_gem_name}-#{libv8_version}-#{platform_no_libc}.gemspec"
43
+ end
44
+
45
+ def libv8_gemspec
46
+ "#{libv8_basename}.gemspec"
47
+ end
48
+
49
+ def libv8_local_path(path=Gem.path)
50
+ gemspecs = [libv8_gemspec, libv8_gemspec_no_libc].uniq
51
+ puts "looking for #{gemspecs.join(', ')} in installed gems"
52
+ candidates = path.product(gemspecs)
53
+ .map { |(p, gemspec)| File.join(p, 'specifications', gemspec) }
54
+ p candidates
55
+ found = candidates.select { |f| File.exist?(f) }.first
56
+
57
+ unless found
58
+ puts "#{gemspecs.join(', ')} not found in installed gems"
59
+ return
60
+ end
61
+
62
+ puts "found in installed specs: #{found}"
63
+
64
+ gemdir = File.basename(found, '.gemspec')
65
+ dir = File.expand_path(File.join(found, '..', '..', 'gems', gemdir))
66
+
67
+ unless Dir.exist?(dir)
68
+ puts "not found in installed gems: #{dir}"
69
+ return
70
+ end
71
+
72
+ puts "found in installed gems: #{dir}"
73
+
74
+ dir
75
+ end
76
+
77
+ def vendor_path
78
+ File.join(Dir.pwd, 'vendor')
79
+ end
80
+
81
+ def libv8_vendor_path
82
+ puts "looking for #{libv8_basename} in #{vendor_path}"
83
+ path = Dir.glob("#{vendor_path}/#{libv8_basename}").first
84
+
85
+ unless path
86
+ puts "#{libv8_basename} not found in #{vendor_path}"
87
+ return
88
+ end
89
+
90
+ puts "looking for #{libv8_basename}/lib/libv8-node.rb in #{vendor_path}"
91
+ unless Dir.glob(File.join(vendor_path, libv8_basename, 'lib', 'libv8-node.rb')).first
92
+ puts "#{libv8_basename}/lib/libv8.rb not found in #{vendor_path}"
93
+ return
94
+ end
95
+
96
+ path
97
+ end
98
+
99
+ def parse_platform(str)
100
+ Gem::Platform.new(str).tap do |p|
101
+ p.instance_eval { @cpu = 'x86_64' } if str =~ /universal.*darwin/
102
+ end
103
+ end
104
+
105
+ def ruby_platform
106
+ parse_platform(RUBY_PLATFORM)
107
+ end
108
+
109
+ def http_get(uri)
110
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
111
+ res = http.get(uri.path)
112
+
113
+ abort("HTTP error #{res.code}: #{uri}") unless res.code == '200'
114
+
115
+ return res.body
116
+ end
117
+ end
118
+
119
+ def libv8_remote_search
120
+ body = http_get(URI("https://rubygems.org/api/v1/versions/#{libv8_gem_name}.json"))
121
+ json = JSON.parse(body)
122
+
123
+ versions = json.select do |v|
124
+ Gem::Version.new(v['number']) == Gem::Version.new(libv8_version)
125
+ end
126
+ abort(<<-ERROR) if versions.empty?
127
+ ERROR: could not find #{libv8_gem_name} (version #{libv8_version}) in rubygems.org
128
+ ERROR
129
+
130
+ platform_versions = versions.select do |v|
131
+ parse_platform(v['platform']) == ruby_platform unless v['platform'] =~ /universal.*darwin/
132
+ end
133
+ abort(<<-ERROR) if platform_versions.empty?
134
+ ERROR: found #{libv8_gem_name}-#{libv8_version}, but no binary for #{ruby_platform}
135
+ try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
136
+ ERROR
137
+
138
+ platform_versions.first
139
+ end
140
+
141
+ def libv8_download_uri(name, version, platform)
142
+ URI("https://rubygems.org/downloads/#{name}-#{version}-#{platform}.gem")
143
+ end
144
+
145
+ def libv8_downloaded_gem(name, version, platform)
146
+ "#{name}-#{version}-#{platform}.gem"
147
+ end
148
+
149
+ def libv8_download(name, version, platform)
150
+ FileUtils.mkdir_p(vendor_path)
151
+ body = http_get(libv8_download_uri(name, version, platform))
152
+ File.open(File.join(vendor_path, libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
153
+ end
154
+
155
+ def libv8_install!
156
+ cmd = "gem install #{libv8_gem_name} --version '#{libv8_version}' --install-dir '#{vendor_path}'"
157
+ puts "installing #{libv8_gem_name} using `#{cmd}`"
158
+ rc = system(cmd)
159
+
160
+ abort(<<-ERROR) unless rc
161
+ ERROR: could not install #{libv8_gem_name} #{libv8_version}
162
+ try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
163
+ ERROR
164
+
165
+ libv8_local_path([vendor_path])
166
+ end
167
+
168
+ def libv8_vendor!
169
+ return libv8_install! if Gem::VERSION < '2.0'
170
+
171
+ version = libv8_remote_search
172
+
173
+ puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
174
+ libv8_download(libv8_gem_name, version['number'], version['platform'])
175
+
176
+ package = Gem::Package.new(File.join(vendor_path, libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
177
+ package.extract_files(File.join(vendor_path, File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
178
+
179
+ libv8_vendor_path
180
+ end
181
+
182
+ def ensure_libv8_load_path
183
+ puts "detected platform #{RUBY_PLATFORM} => #{ruby_platform}"
184
+
185
+ libv8_path = libv8_local_path
186
+ unless ENV['ONLY_INSTALLED_LIBV8_GEM']
187
+ libv8_path ||= libv8_vendor_path || libv8_vendor!
188
+ end
189
+
190
+ abort(<<-ERROR) unless libv8_path
191
+ ERROR: could not find #{libv8_gem_name}
192
+ ERROR
193
+
194
+ $LOAD_PATH.unshift(File.join(libv8_path, 'ext'))
195
+ $LOAD_PATH.unshift(File.join(libv8_path, 'lib'))
196
+ end
197
+
198
+ ensure_libv8_load_path
199
+
200
+ require 'libv8-node'
201
+
202
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
6
203
 
7
204
  have_library('pthread')
8
205
  have_library('objc') if IS_DARWIN
9
- $CPPFLAGS.gsub! /-std=[^\s]+/, ''
206
+ cppflags_clear_std!
10
207
  $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
11
208
  $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
12
209
  $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
13
210
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
14
211
  $CPPFLAGS += " -std=c++0x"
15
212
  $CPPFLAGS += " -fpermissive"
16
- $CPPFLAGS += " -fno-omit-frame-pointer"
17
- if enable_config('avx2')
18
- $CPPFLAGS += " -mavx2"
19
- else
20
- $CPPFLAGS += " -mssse3"
21
- end
213
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
214
+ $CPPFLAGS += " -fvisibility=hidden "
215
+ cppflags_add_frame_pointer!
216
+ cppflags_add_cpu_extension!
22
217
 
23
218
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
24
219
 
25
220
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
221
+ $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
26
222
 
27
223
  if ENV['CXX']
28
224
  puts "SETTING CXX"
29
225
  CONFIG['CXX'] = ENV['CXX']
30
226
  end
227
+ # 1.9 has no $CXXFLAGS
228
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
229
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
31
230
 
32
231
  CXX11_TEST = <<EOS
33
232
  #if __cplusplus <= 199711L
@@ -57,94 +256,15 @@ if CONFIG['warnflags']
57
256
  CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
58
257
  end
59
258
 
60
- if enable_config('debug')
259
+ if enable_config('debug') || enable_config('asan')
61
260
  CONFIG['debugflags'] << ' -ggdb3 -O0'
62
261
  end
63
262
 
64
- def fixup_libtinfo
65
- dirs = %w[/lib64 /usr/lib64 /lib /usr/lib]
66
- found_v5 = dirs.map { |d| "#{d}/libtinfo.so.5" }.find &File.method(:file?)
67
- return '' if found_v5
68
- found_v6 = dirs.map { |d| "#{d}/libtinfo.so.6" }.find &File.method(:file?)
69
- return '' unless found_v6
70
- FileUtils.ln_s found_v6, 'gemdir/libtinfo.so.5', :force => true
71
- "LD_LIBRARY_PATH='#{File.expand_path('gemdir')}:#{ENV['LD_LIBRARY_PATH']}'"
72
- end
73
-
74
- def libv8_gem_name
75
- return "libv8-solaris" if IS_SOLARIS
76
-
77
- is_musl = false
78
- begin
79
- is_musl = !!(File.read('/proc/self/maps') =~ /ld-musl-x86_64/)
80
- rescue; end
81
-
82
- is_musl ? 'libv8-alpine' : 'libv8'
83
- end
84
-
85
- # old rubygem versions prefer source gems to binary ones
86
- # ... and their --platform switch is broken too, as it leaves the 'ruby'
87
- # platform in Gem.platforms.
88
- def force_platform_gem
89
- gem_version = `gem --version`
90
- return 'gem' unless $?.success?
91
-
92
- return 'gem' if gem_version.to_f.zero? || gem_version.to_f >= 2.3
93
- return 'gem' if RUBY_PLATFORM != 'x86_64-linux'
94
-
95
- gem_binary = `which gem`
96
- return 'gem' unless $?.success?
97
-
98
- ruby = File.foreach(gem_binary.strip).first.sub(/^#!/, '').strip
99
- unless File.file? ruby
100
- warn "No valid ruby: #{ruby}"
101
- return 'gem'
102
- end
103
-
104
- require 'tempfile'
105
- file = Tempfile.new('sq_mini_racer')
106
- file << <<EOS
107
- require 'rubygems'
108
- Gem.platforms.reject! { |it| it == 'ruby' }
109
- #{IO.read(gem_binary.strip)}
110
- EOS
111
- file.close
112
- "#{ruby} '#{file.path}'"
113
- end
114
-
115
- LIBV8_VERSION = '6.7.288.46.1'
116
- libv8_rb = Dir.glob('**/libv8.rb').first
117
- FileUtils.mkdir_p('gemdir')
118
- unless libv8_rb
119
- gem_name = libv8_gem_name
120
- cmd = "#{fixup_libtinfo} #{force_platform_gem} install --version '= #{LIBV8_VERSION}' --install-dir gemdir #{gem_name}"
121
- puts "Will try downloading #{gem_name} gem: #{cmd}"
122
- `#{cmd}`
123
- unless $?.success?
124
- warn <<EOS
125
-
126
- WARNING: Could not download a private copy of the libv8 gem. Please make
127
- sure that you have internet access and that the `gem` binary is available.
128
-
129
- EOS
130
- end
131
-
132
- libv8_rb = Dir.glob('**/libv8.rb').first
133
- unless libv8_rb
134
- warn <<EOS
135
-
136
- WARNING: Could not find libv8 after the local copy of libv8 having supposedly
137
- been installed.
138
-
139
- EOS
140
- end
141
- end
263
+ Libv8::Node.configure_makefile
142
264
 
143
- if libv8_rb
144
- $:.unshift(File.dirname(libv8_rb) + '/../ext')
145
- $:.unshift File.dirname(libv8_rb)
265
+ if enable_config('asan')
266
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
267
+ $LDFLAGS.insert(0, " -fsanitize=address ")
146
268
  end
147
269
 
148
- require 'libv8'
149
- Libv8.configure_makefile
150
270
  create_makefile 'sq_mini_racer_extension'
@@ -8,12 +8,24 @@
8
8
  # undef HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P
9
9
  # undef HAVE_BUILTIN___BUILTIN_TYPES_COMPATIBLE_P
10
10
 
11
- #include <ruby.h>
12
11
  #include <ruby/version.h>
12
+
13
+ // workaround for Ruby 2.3.0 on macOS
14
+ #if RUBY_API_VERSION_CODE == 20300
15
+ #ifdef _DARWIN_C_SOURCE
16
+ #ifndef __STDC_LIB_EXT1__
17
+ #undef HAVE_MEMSET_S
18
+ #endif
19
+ #endif
20
+ #endif
21
+
22
+ #include <ruby.h>
13
23
  #if RUBY_API_VERSION_MAJOR > 1
14
24
  #include <ruby/thread.h>
15
25
  #endif
26
+ #include <ruby/io.h>
16
27
  #include <v8.h>
28
+ #include <v8-profiler.h>
17
29
  #include <libplatform/libplatform.h>
18
30
  #include <ruby/encoding.h>
19
31
  #include <pthread.h>
@@ -24,6 +36,8 @@
24
36
  #include "compat.hpp"
25
37
  #include "simdutf8check.h"
26
38
 
39
+ #include <time.h>
40
+
27
41
  using namespace v8;
28
42
 
29
43
  typedef struct {
@@ -37,6 +51,7 @@ public:
37
51
  ArrayBuffer::Allocator* allocator;
38
52
  StartupData* startup_data;
39
53
  bool interrupted;
54
+ bool added_gc_cb;
40
55
  pid_t pid;
41
56
  VALUE mutex;
42
57
 
@@ -54,15 +69,12 @@ public:
54
69
 
55
70
 
56
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
57
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
58
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
59
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
60
75
  }
61
76
 
62
- ~IsolateInfo() {
63
- void free_isolate(IsolateInfo*);
64
- free_isolate(this);
65
- }
77
+ ~IsolateInfo();
66
78
 
67
79
  void init(SnapshotInfo* snapshot_info = nullptr);
68
80
 
@@ -139,6 +151,7 @@ typedef struct {
139
151
  Local<Function> fun;
140
152
  Local<Value> *argv;
141
153
  EvalResult result;
154
+ size_t max_memory;
142
155
  } FunctionCall;
143
156
 
144
157
  enum IsolateFlags {
@@ -164,9 +177,14 @@ static VALUE rb_mJSON;
164
177
  static VALUE rb_cFailedV8Conversion;
165
178
  static VALUE rb_cDateTime = Qnil;
166
179
 
167
- static Platform* current_platform = NULL;
180
+ static std::unique_ptr<Platform> current_platform = NULL;
168
181
  static std::mutex platform_lock;
169
182
 
183
+ static pthread_attr_t *thread_attr_p;
184
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
185
+ static bool ruby_exiting = false; // guarded by exit_lock
186
+ static bool single_threaded = false;
187
+
170
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
171
189
  bool platform_already_initialized = false;
172
190
 
@@ -178,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
178
196
  platform_lock.lock();
179
197
 
180
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
181
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
182
203
  } else {
183
204
  platform_already_initialized = true;
@@ -201,8 +222,8 @@ static void init_v8() {
201
222
 
202
223
  if (current_platform == NULL) {
203
224
  V8::InitializeICU();
204
- current_platform = platform::CreateDefaultPlatform();
205
- V8::InitializePlatform(current_platform);
225
+ current_platform = platform::NewDefaultPlatform();
226
+ V8::InitializePlatform(current_platform.get());
206
227
  V8::Initialize();
207
228
  }
208
229
 
@@ -245,15 +266,17 @@ static void prepare_result(MaybeLocal<Value> v8res,
245
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
246
267
  !local_value->IsDate() && !local_value->IsFunction()) {
247
268
  Local<Object> JSON = context->Global()->Get(
248
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
249
271
 
250
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
251
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
252
275
 
253
- Local<Object> object = local_value->ToObject();
276
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
254
277
  const unsigned argc = 1;
255
278
  Local<Value> argv[argc] = { object };
256
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
279
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
257
280
 
258
281
  if (json.IsEmpty()) {
259
282
  evalRes.executed = false;
@@ -277,11 +300,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
277
300
  evalRes.message = new Persistent<Value>();
278
301
  Local<Message> message = trycatch.Message();
279
302
  char buf[1000];
280
- int len;
303
+ int len, line, column;
304
+
305
+ if (!message->GetLineNumber(context).To(&line)) {
306
+ line = 0;
307
+ }
308
+
309
+ if (!message->GetStartColumn(context).To(&column)) {
310
+ column = 0;
311
+ }
312
+
281
313
  len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
282
- *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString()),
283
- message->GetLineNumber(),
284
- message->GetStartColumn());
314
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
315
+ line,
316
+ column);
317
+
285
318
  if ((size_t) len >= sizeof(buf)) {
286
319
  len = sizeof(buf) - 1;
287
320
  buf[len] = '\0';
@@ -292,12 +325,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
292
325
  } else if(trycatch.HasTerminated()) {
293
326
  evalRes.terminated = true;
294
327
  evalRes.message = new Persistent<Value>();
295
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
328
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
296
329
  evalRes.message->Reset(isolate, tmp);
297
330
  }
298
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
299
332
  evalRes.backtrace = new Persistent<Value>();
300
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace(context).ToLocalChecked()->ToString());
333
+ evalRes.backtrace->Reset(isolate,
334
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
301
335
  }
302
336
  }
303
337
  }
@@ -312,7 +346,8 @@ nogvl_context_eval(void* arg) {
312
346
 
313
347
  EvalParams* eval_params = (EvalParams*)arg;
314
348
  EvalResult* result = eval_params->result;
315
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
349
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
350
+ Isolate* isolate = isolate_info->isolate;
316
351
 
317
352
  Isolate::Scope isolate_scope(isolate);
318
353
  HandleScope handle_scope(isolate);
@@ -356,7 +391,10 @@ nogvl_context_eval(void* arg) {
356
391
  // parsing successful
357
392
  if (eval_params->max_memory > 0) {
358
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
359
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
360
398
  }
361
399
 
362
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -369,6 +407,12 @@ nogvl_context_eval(void* arg) {
369
407
  return 0;
370
408
  }
371
409
 
410
+ static VALUE new_empty_failed_conv_obj() {
411
+ // TODO isolate code that translates execption to ruby
412
+ // exception so we can properly return it
413
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
414
+ }
415
+
372
416
  // assumes isolate locking is in place
373
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
374
418
  Local<Value> value) {
@@ -381,11 +425,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
381
425
  }
382
426
 
383
427
  if (value->IsInt32()) {
384
- return INT2FIX(value->Int32Value());
428
+ return INT2FIX(value->Int32Value(context).ToChecked());
385
429
  }
386
430
 
387
431
  if (value->IsNumber()) {
388
- return rb_float_new(value->NumberValue());
432
+ return rb_float_new(value->NumberValue(context).ToChecked());
389
433
  }
390
434
 
391
435
  if (value->IsTrue()) {
@@ -400,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
400
444
  VALUE rb_array = rb_ary_new();
401
445
  Local<Array> arr = Local<Array>::Cast(value);
402
446
  for(uint32_t i=0; i < arr->Length(); i++) {
403
- Local<Value> element = arr->Get(i);
404
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
447
+ MaybeLocal<Value> element = arr->Get(context, i);
448
+ if (element.IsEmpty()) {
449
+ continue;
450
+ }
451
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
405
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
406
453
  return rb_elem;
407
454
  }
@@ -426,31 +473,52 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
426
473
  VALUE rb_hash = rb_hash_new();
427
474
  TryCatch trycatch(isolate);
428
475
 
429
- Local<Object> object = value->ToObject();
476
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
430
477
  auto maybe_props = object->GetOwnPropertyNames(context);
431
478
  if (!maybe_props.IsEmpty()) {
432
479
  Local<Array> props = maybe_props.ToLocalChecked();
433
480
  for(uint32_t i=0; i < props->Length(); i++) {
434
- Local<Value> key = props->Get(i);
435
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
436
- Local<Value> prop_value = object->Get(key);
437
- // this may have failed due to Get raising
481
+ MaybeLocal<Value> key = props->Get(context, i);
482
+ if (key.IsEmpty()) {
483
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
484
+ }
485
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
438
486
 
439
- if (trycatch.HasCaught()) {
440
- // TODO isolate code that translates execption to ruby
441
- // exception so we can properly return it
442
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
487
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
488
+ // this may have failed due to Get raising
489
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
490
+ return new_empty_failed_conv_obj();
443
491
  }
444
492
 
445
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
493
+ VALUE rb_value = convert_v8_to_ruby(
494
+ isolate, context, prop_value.ToLocalChecked());
446
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
447
496
  }
448
497
  }
449
498
  return rb_hash;
450
499
  }
451
500
 
452
- Local<String> rstr = value->ToString();
453
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
501
+ if (value->IsSymbol()) {
502
+ v8::String::Utf8Value symbol_name(isolate,
503
+ Local<Symbol>::Cast(value)->Name());
504
+
505
+ VALUE str_symbol = rb_enc_str_new(
506
+ *symbol_name,
507
+ symbol_name.length(),
508
+ rb_enc_find("utf-8")
509
+ );
510
+
511
+ return ID2SYM(rb_intern_str(str_symbol));
512
+ }
513
+
514
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
515
+
516
+ if (rstr_maybe.IsEmpty()) {
517
+ return Qnil;
518
+ } else {
519
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
520
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
521
+ }
454
522
  }
455
523
 
456
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -547,7 +615,7 @@ treat_as_latin1:
547
615
  return v8str.ToLocalChecked();
548
616
  }
549
617
 
550
- static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
618
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value)
551
619
  {
552
620
  EscapableHandleScope scope(isolate);
553
621
 
@@ -561,87 +629,87 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
561
629
  VALUE klass;
562
630
 
563
631
  switch (TYPE(value)) {
564
- case T_FIXNUM:
632
+ case T_FIXNUM:
565
633
  {
566
- fixnum = NUM2LONG(value);
567
- if (fixnum > INT_MAX)
568
- {
569
- return scope.Escape(Number::New(isolate, (double)fixnum));
570
- }
571
- return scope.Escape(Integer::New(isolate, (int)fixnum));
634
+ fixnum = NUM2LONG(value);
635
+ if (fixnum > INT_MAX)
636
+ {
637
+ return scope.Escape(Number::New(isolate, (double)fixnum));
638
+ }
639
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
572
640
  }
573
- case T_FLOAT:
574
- return scope.Escape(Number::New(isolate, NUM2DBL(value)));
575
- case T_STRING:
641
+ case T_FLOAT:
642
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
643
+ case T_STRING:
576
644
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
577
- case T_NIL:
578
- return scope.Escape(Null(isolate));
579
- case T_TRUE:
580
- return scope.Escape(True(isolate));
581
- case T_FALSE:
582
- return scope.Escape(False(isolate));
583
- case T_ARRAY:
645
+ case T_NIL:
646
+ return scope.Escape(Null(isolate));
647
+ case T_TRUE:
648
+ return scope.Escape(True(isolate));
649
+ case T_FALSE:
650
+ return scope.Escape(False(isolate));
651
+ case T_ARRAY:
584
652
  {
585
- length = RARRAY_LEN(value);
586
- array = Array::New(isolate, (int)length);
587
- for(i=0; i<length; i++) {
588
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
589
- }
590
- return scope.Escape(array);
653
+ length = RARRAY_LEN(value);
654
+ array = Array::New(isolate, (int)length);
655
+ for(i=0; i<length; i++) {
656
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
657
+ }
658
+ return scope.Escape(array);
591
659
  }
592
- case T_HASH:
660
+ case T_HASH:
593
661
  {
594
- object = Object::New(isolate);
595
- hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
596
- length = RARRAY_LEN(hash_as_array);
597
- for(i=0; i<length; i++) {
598
- pair = rb_ary_entry(hash_as_array, i);
599
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
600
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
601
- }
602
- return scope.Escape(object);
662
+ object = Object::New(isolate);
663
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
664
+ length = RARRAY_LEN(hash_as_array);
665
+ for(i=0; i<length; i++) {
666
+ pair = rb_ary_entry(hash_as_array, i);
667
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
668
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
669
+ }
670
+ return scope.Escape(object);
603
671
  }
604
- case T_SYMBOL:
672
+ case T_SYMBOL:
605
673
  {
606
- value = rb_funcall(value, rb_intern("to_s"), 0);
674
+ value = rb_funcall(value, rb_intern("to_s"), 0);
607
675
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
608
676
  }
609
- case T_DATA:
677
+ case T_DATA:
610
678
  {
611
- klass = rb_funcall(value, rb_intern("class"), 0);
612
- if (klass == rb_cTime || klass == rb_cDateTime)
679
+ klass = rb_funcall(value, rb_intern("class"), 0);
680
+ if (klass == rb_cTime || klass == rb_cDateTime)
681
+ {
682
+ if (klass == rb_cDateTime)
613
683
  {
614
- if (klass == rb_cDateTime)
615
- {
616
- value = rb_funcall(value, rb_intern("to_time"), 0);
617
- }
618
- value = rb_funcall(value, rb_intern("to_f"), 0);
619
- return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
684
+ value = rb_funcall(value, rb_intern("to_time"), 0);
620
685
  }
686
+ value = rb_funcall(value, rb_intern("to_f"), 0);
687
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
688
+ }
621
689
  // break intentionally missing
622
690
  }
623
- case T_OBJECT:
624
- case T_CLASS:
625
- case T_ICLASS:
626
- case T_MODULE:
627
- case T_REGEXP:
628
- case T_MATCH:
629
- case T_STRUCT:
630
- case T_BIGNUM:
631
- case T_FILE:
632
- case T_UNDEF:
633
- case T_NODE:
634
- default:
691
+ case T_OBJECT:
692
+ case T_CLASS:
693
+ case T_ICLASS:
694
+ case T_MODULE:
695
+ case T_REGEXP:
696
+ case T_MATCH:
697
+ case T_STRUCT:
698
+ case T_BIGNUM:
699
+ case T_FILE:
700
+ case T_UNDEF:
701
+ case T_NODE:
702
+ default:
635
703
  {
636
704
  if (rb_respond_to(value, rb_intern("to_s"))) {
637
705
  // TODO: if this throws we're screwed
638
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
639
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
640
708
  }
641
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
642
711
  }
643
712
  }
644
-
645
713
  }
646
714
 
647
715
  static void unblock_eval(void *ptr) {
@@ -649,6 +717,81 @@ static void unblock_eval(void *ptr) {
649
717
  eval->context_info->isolate_info->interrupted = true;
650
718
  }
651
719
 
720
+ /*
721
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
722
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
723
+ */
724
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
725
+ const char *utf8_source, const char *name) {
726
+ Context::Scope context_scope(context);
727
+ TryCatch try_catch(isolate);
728
+ Local<String> source_string;
729
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
730
+ return false;
731
+ }
732
+ Local<String> resource_name =
733
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
734
+ ScriptOrigin origin(resource_name);
735
+ ScriptCompiler::Source source(source_string, origin);
736
+ Local<Script> script;
737
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
738
+ return false;
739
+ if (script->Run(context).IsEmpty()) return false;
740
+ return true;
741
+ }
742
+
743
+ static StartupData
744
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
745
+ Isolate *isolate = Isolate::Allocate();
746
+
747
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
748
+ SnapshotCreator snapshot_creator(isolate);
749
+ {
750
+ HandleScope scope(isolate);
751
+ Local<v8::Context> context = v8::Context::New(isolate);
752
+ if (embedded_source != nullptr &&
753
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
754
+ return {};
755
+ }
756
+ snapshot_creator.SetDefaultContext(context);
757
+ }
758
+ return snapshot_creator.CreateBlob(
759
+ SnapshotCreator::FunctionCodeHandling::kClear);
760
+ }
761
+
762
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
763
+ const char *warmup_source) {
764
+ // Use following steps to create a warmed up snapshot blob from a cold one:
765
+ // - Create a new isolate from the cold snapshot.
766
+ // - Create a new context to run the warmup script. This will trigger
767
+ // compilation of executed functions.
768
+ // - Create a new context. This context will be unpolluted.
769
+ // - Serialize the isolate and the second context into a new snapshot blob.
770
+ StartupData result = {nullptr, 0};
771
+
772
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
773
+ warmup_source != NULL) {
774
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
775
+ Isolate *isolate = snapshot_creator.GetIsolate();
776
+ {
777
+ HandleScope scope(isolate);
778
+ Local<Context> context = Context::New(isolate);
779
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
780
+ return result;
781
+ }
782
+ }
783
+ {
784
+ HandleScope handle_scope(isolate);
785
+ isolate->ContextDisposedNotification(false);
786
+ Local<Context> context = Context::New(isolate);
787
+ snapshot_creator.SetDefaultContext(context);
788
+ }
789
+ result = snapshot_creator.CreateBlob(
790
+ SnapshotCreator::FunctionCodeHandling::kKeep);
791
+ }
792
+ return result;
793
+ }
794
+
652
795
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
653
796
  SnapshotInfo* snapshot_info;
654
797
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -667,7 +810,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
667
810
 
668
811
  init_v8();
669
812
 
670
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
813
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
671
814
 
672
815
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
673
816
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -698,7 +841,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
698
841
  init_v8();
699
842
 
700
843
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
701
- StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
844
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
702
845
 
703
846
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
704
847
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -761,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
761
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
762
905
  }
763
906
 
907
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
908
+ IsolateInfo* isolate_info;
909
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
910
+
911
+ if (current_platform == NULL) return Qfalse;
912
+
913
+ isolate_info->isolate->LowMemoryNotification();
914
+ return Qnil;
915
+ }
916
+
917
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
918
+ IsolateInfo* isolate_info;
919
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
920
+
921
+ if (current_platform == NULL) return Qfalse;
922
+
923
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
924
+ return Qtrue;
925
+ } else {
926
+ return Qfalse;
927
+ }
928
+ }
929
+
764
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
765
931
  ContextInfo* context_info;
766
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -877,8 +1043,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
877
1043
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
878
1044
 
879
1045
  if (result.json) {
880
- Local<String> rstr = tmp->ToString();
881
- VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
1046
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
1047
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
882
1048
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
883
1049
  } else {
884
1050
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -999,6 +1165,8 @@ gvl_ruby_callback(void* data) {
999
1165
  VALUE result;
1000
1166
  VALUE self;
1001
1167
  VALUE parent;
1168
+ ContextInfo* context_info;
1169
+
1002
1170
  {
1003
1171
  HandleScope scope(args->GetIsolate());
1004
1172
  Local<External> external = Local<External>::Cast(args->Data());
@@ -1011,7 +1179,6 @@ gvl_ruby_callback(void* data) {
1011
1179
  return NULL;
1012
1180
  }
1013
1181
 
1014
- ContextInfo* context_info;
1015
1182
  Data_Get_Struct(parent, ContextInfo, context_info);
1016
1183
 
1017
1184
  if (length > 0) {
@@ -1021,7 +1188,7 @@ gvl_ruby_callback(void* data) {
1021
1188
  for (int i = 0; i < length; i++) {
1022
1189
  Local<Value> value = ((*args)[i]).As<Value>();
1023
1190
  VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1024
- *context_info->context, value);
1191
+ *context_info->context, value);
1025
1192
  rb_ary_push(ruby_args, tmp);
1026
1193
  }
1027
1194
  }
@@ -1034,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1034
1201
  callback_data.failed = false;
1035
1202
 
1036
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1037
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1204
+ args->GetIsolate()->ThrowException(
1205
+ String::NewFromUtf8Literal(args->GetIsolate(),
1206
+ "Terminated execution during transition from Ruby to JS"));
1038
1207
  args->GetIsolate()->TerminateExecution();
1039
1208
  if (length > 0) {
1040
1209
  rb_ary_clear(ruby_args);
@@ -1048,11 +1217,11 @@ gvl_ruby_callback(void* data) {
1048
1217
 
1049
1218
  if(callback_data.failed) {
1050
1219
  rb_iv_set(parent, "@current_exception", result);
1051
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1052
1221
  }
1053
1222
  else {
1054
1223
  HandleScope scope(args->GetIsolate());
1055
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
1224
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1056
1225
  args->GetReturnValue().Set(v8_result);
1057
1226
  }
1058
1227
 
@@ -1109,8 +1278,10 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1109
1278
  Local<Context> context = context_info->context->Get(isolate);
1110
1279
  Context::Scope context_scope(context);
1111
1280
 
1112
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
1113
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
1281
+ Local<String> v8_str =
1282
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1283
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1284
+ .ToLocalChecked();
1114
1285
 
1115
1286
  // copy self so we can access from v8 external
1116
1287
  VALUE* self_copy;
@@ -1120,24 +1291,38 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1120
1291
  Local<Value> external = External::New(isolate, self_copy);
1121
1292
 
1122
1293
  if (parent_object == Qnil) {
1123
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1124
- } else {
1294
+ context->Global()->Set(
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1298
+ ->GetFunction(context)
1299
+ .ToLocalChecked());
1125
1300
 
1126
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1127
- NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
1301
+ } else {
1302
+ Local<String> eval =
1303
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1304
+ NewStringType::kNormal,
1305
+ (int)RSTRING_LEN(parent_object_eval))
1306
+ .ToLocalChecked();
1128
1307
 
1129
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1130
1309
  if (parsed_script.IsEmpty()) {
1131
- parse_error = true;
1310
+ parse_error = true;
1132
1311
  } else {
1133
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1312
+ MaybeLocal<Value> maybe_value =
1313
+ parsed_script.ToLocalChecked()->Run(context);
1134
1314
  attach_error = true;
1135
1315
 
1136
1316
  if (!maybe_value.IsEmpty()) {
1137
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1138
- if (value->IsObject()){
1139
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1140
- attach_error = false;
1318
+ if (value->IsObject()) {
1319
+ value.As<Object>()->Set(
1320
+ context,
1321
+ v8_str,
1322
+ FunctionTemplate::New(isolate, ruby_callback, external)
1323
+ ->GetFunction(context)
1324
+ .ToLocalChecked());
1325
+ attach_error = false;
1141
1326
  }
1142
1327
  }
1143
1328
  }
@@ -1167,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1167
1352
  return context_info->isolate_info->mutex;
1168
1353
  }
1169
1354
 
1170
- void free_isolate(IsolateInfo* isolate_info) {
1171
-
1172
- if (isolate_info->isolate) {
1173
- Locker lock(isolate_info->isolate);
1174
- }
1175
-
1176
- if (isolate_info->isolate) {
1177
- if (isolate_info->interrupted) {
1178
- fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1355
+ IsolateInfo::~IsolateInfo() {
1356
+ if (isolate) {
1357
+ if (this->interrupted) {
1358
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1359
+ "it can not be disposed and memory will not be "
1360
+ "reclaimed till the Ruby process exits.\n");
1179
1361
  } else {
1180
-
1181
- if (isolate_info->pid != getpid()) {
1182
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1362
+ if (this->pid != getpid() && !single_threaded) {
1363
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1364
+ "it can not be disposed and "
1365
+ "memory will not be reclaimed "
1366
+ "till the Ruby process exits.\n"
1367
+ "It is VERY likely your process will hang.\n"
1368
+ "If you wish to use v8 in forked environment "
1369
+ "please ensure the platform is initialized with:\n"
1370
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1371
+ );
1183
1372
  } else {
1184
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1185
1374
  }
1186
1375
  }
1187
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1188
1377
  }
1189
1378
 
1190
- if (isolate_info->startup_data) {
1191
- delete[] isolate_info->startup_data->data;
1192
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1193
1382
  }
1194
1383
 
1195
- delete isolate_info->allocator;
1384
+ delete allocator;
1196
1385
  }
1197
1386
 
1198
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1199
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1200
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1201
1390
  Persistent<Context>* context = context_info->context;
@@ -1212,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1212
1401
  }
1213
1402
 
1214
1403
  xfree(context_info);
1404
+ }
1405
+
1406
+ static void *free_context_thr(void* arg) {
1407
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1408
+ return NULL;
1409
+ }
1410
+ if (ruby_exiting) {
1411
+ return NULL;
1412
+ }
1413
+
1414
+ free_context_raw(arg);
1415
+
1416
+ pthread_rwlock_unlock(&exit_lock);
1417
+
1215
1418
  return NULL;
1216
1419
  }
1217
1420
 
@@ -1226,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1226
1429
 
1227
1430
  if (isolate_info && isolate_info->refs() > 1) {
1228
1431
  pthread_t free_context_thread;
1229
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1432
+ if (pthread_create(&free_context_thread, thread_attr_p,
1433
+ free_context_thr, (void*)context_info_copy)) {
1230
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1231
1435
  }
1232
-
1233
1436
  } else {
1234
1437
  free_context_raw(context_info_copy);
1235
1438
  }
1236
1439
 
1237
- if (context_info->context && isolate_info && isolate_info->isolate) {
1238
1440
  context_info->context = NULL;
1239
- }
1240
-
1241
- if (isolate_info) {
1242
1441
  context_info->isolate_info = NULL;
1243
1442
  }
1244
- }
1245
1443
 
1246
1444
  static void deallocate_isolate(void* data) {
1247
1445
 
@@ -1255,7 +1453,7 @@ static void mark_isolate(void* data) {
1255
1453
  isolate_info->mark();
1256
1454
  }
1257
1455
 
1258
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1259
1457
  ContextInfo* context_info = (ContextInfo*)data;
1260
1458
 
1261
1459
  free_context(context_info);
@@ -1270,22 +1468,22 @@ static void mark_context(void* data) {
1270
1468
  }
1271
1469
  }
1272
1470
 
1273
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1274
1472
  xfree(data);
1275
1473
  }
1276
1474
 
1277
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1278
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1279
1477
  delete[] snapshot_info->data;
1280
1478
  xfree(snapshot_info);
1281
1479
  }
1282
1480
 
1283
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1284
1482
  VALUE* self = ALLOC(VALUE);
1285
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1286
1484
  }
1287
1485
 
1288
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1289
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1290
1488
  context_info->isolate_info = NULL;
1291
1489
  context_info->context = NULL;
@@ -1293,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1293
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1294
1492
  }
1295
1493
 
1296
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1297
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1298
1496
  snapshot_info->data = NULL;
1299
1497
  snapshot_info->raw_size = 0;
@@ -1301,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1301
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1302
1500
  }
1303
1501
 
1304
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1305
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1306
1504
 
1307
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1340,6 +1538,71 @@ rb_heap_stats(VALUE self) {
1340
1538
  return rval;
1341
1539
  }
1342
1540
 
1541
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1542
+ class FileOutputStream : public OutputStream {
1543
+ public:
1544
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1545
+
1546
+ virtual int GetChunkSize() {
1547
+ return 65536;
1548
+ }
1549
+
1550
+ virtual void EndOfStream() {}
1551
+
1552
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1553
+ const size_t len = static_cast<size_t>(size);
1554
+ size_t off = 0;
1555
+
1556
+ while (off < len && !feof(stream_) && !ferror(stream_))
1557
+ off += fwrite(data + off, 1, len - off, stream_);
1558
+
1559
+ return off == len ? kContinue : kAbort;
1560
+ }
1561
+
1562
+ private:
1563
+ FILE* stream_;
1564
+ };
1565
+
1566
+
1567
+ static VALUE
1568
+ rb_heap_snapshot(VALUE self, VALUE file) {
1569
+
1570
+ rb_io_t *fptr;
1571
+
1572
+ fptr = RFILE(file)->fptr;
1573
+
1574
+ if (!fptr) return Qfalse;
1575
+
1576
+ FILE* fp;
1577
+ fp = fdopen(fptr->fd, "w");
1578
+ if (fp == NULL) return Qfalse;
1579
+
1580
+
1581
+ ContextInfo* context_info;
1582
+ Data_Get_Struct(self, ContextInfo, context_info);
1583
+ Isolate* isolate;
1584
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1585
+
1586
+ if (!isolate) return Qfalse;
1587
+
1588
+ Locker lock(isolate);
1589
+ Isolate::Scope isolate_scope(isolate);
1590
+ HandleScope handle_scope(isolate);
1591
+
1592
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1593
+
1594
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1595
+
1596
+ FileOutputStream stream(fp);
1597
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1598
+
1599
+ fflush(fp);
1600
+
1601
+ const_cast<HeapSnapshot*>(snap)->Delete();
1602
+
1603
+ return Qtrue;
1604
+ }
1605
+
1343
1606
  static VALUE
1344
1607
  rb_context_stop(VALUE self) {
1345
1608
 
@@ -1392,13 +1655,23 @@ nogvl_context_call(void *args) {
1392
1655
  if (!call) {
1393
1656
  return 0;
1394
1657
  }
1395
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1396
1660
 
1397
1661
  // in gvl flag
1398
1662
  isolate->SetData(IN_GVL, (void*)false);
1399
1663
  // terminate ASAP
1400
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1401
1665
 
1666
+ if (call->max_memory > 0) {
1667
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1668
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1669
+ if (!isolate_info->added_gc_cb) {
1670
+ isolate->AddGCEpilogueCallback(gc_callback);
1671
+ isolate_info->added_gc_cb = true;
1672
+ }
1673
+ }
1674
+
1402
1675
  Isolate::Scope isolate_scope(isolate);
1403
1676
  EscapableHandleScope handle_scope(isolate);
1404
1677
  TryCatch trycatch(isolate);
@@ -1456,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1456
1729
  call_argv = argv + 1;
1457
1730
  }
1458
1731
 
1732
+ call.max_memory = 0;
1733
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1734
+ if (mem_softlimit != Qnil) {
1735
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1736
+ call.max_memory = (size_t)sl_int;
1737
+ }
1738
+
1459
1739
  bool missingFunction = false;
1460
1740
  {
1461
1741
  Locker lock(isolate);
@@ -1467,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1467
1747
 
1468
1748
  // examples of such usage can be found in
1469
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1470
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1471
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1750
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1751
+ MaybeLocal<v8::Value> val;
1752
+ if (!fname.IsEmpty()) {
1753
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1754
+ }
1472
1755
 
1473
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1474
1757
  missingFunction = true;
@@ -1484,7 +1767,7 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1484
1767
  return Qnil;
1485
1768
  }
1486
1769
  for(int i=0; i < fun_argc; i++) {
1487
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1770
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1488
1771
  }
1489
1772
  }
1490
1773
  #if RUBY_API_VERSION_MAJOR > 1
@@ -1517,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1517
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1518
1801
  }
1519
1802
 
1803
+ static void set_ruby_exiting(VALUE value) {
1804
+ (void)value;
1805
+
1806
+ int res = pthread_rwlock_wrlock(&exit_lock);
1807
+
1808
+ ruby_exiting = true;
1809
+ if (res == 0) {
1810
+ pthread_rwlock_unlock(&exit_lock);
1811
+ }
1812
+ }
1813
+
1814
+ static VALUE rb_monotime(VALUE self) {
1815
+ struct timespec ts;
1816
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
1817
+ return INT2FIX(-1);
1818
+ }
1819
+
1820
+ return DBL2NUM(
1821
+ (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0);
1822
+ }
1823
+
1520
1824
  extern "C" {
1521
1825
 
1522
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1523
1827
  {
1524
- VALUE rb_mSqreen = rb_define_module("Sqreen");
1828
+ ID sqreen_id = rb_intern("Sqreen");
1829
+ VALUE rb_mSqreen;
1830
+ if (rb_const_defined(rb_cObject, sqreen_id)) {
1831
+ rb_mSqreen = rb_const_get(rb_cObject, sqreen_id);
1832
+ if (TYPE(rb_mSqreen) != T_MODULE) {
1833
+ rb_raise(rb_eTypeError, "Sqreen is not a module");
1834
+ return;
1835
+ }
1836
+ } else {
1837
+ rb_mSqreen = rb_define_module("Sqreen");
1838
+ }
1525
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1526
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1527
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1528
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1548,6 +1863,8 @@ extern "C" {
1548
1863
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1549
1864
  rb_define_method(rb_cContext, "low_memory_notification", (VALUE(*)(...))&rb_context_low_memory_notification, 0);
1550
1865
  rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1866
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1867
+
1551
1868
  rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1552
1869
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1553
1870
  rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
@@ -1567,8 +1884,19 @@ extern "C" {
1567
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1568
1885
 
1569
1886
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1887
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1888
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1570
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1571
1890
 
1572
1891
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1892
+
1893
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1894
+
1895
+ static pthread_attr_t attr;
1896
+ if (pthread_attr_init(&attr) == 0) {
1897
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1898
+ thread_attr_p = &attr;
1899
+ }
1900
+ }
1573
1901
  }
1574
1902
  }