sq_mini_racer 0.2.4.sqreen3 → 0.3.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,33 +1,226 @@
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_requirement
33
+ '~> 14.14.0.0.beta1'
34
+ end
35
+
36
+ def libv8_basename(version)
37
+ "#{libv8_gem_name}-#{version}-#{ruby_platform}"
38
+ end
39
+
40
+ def libv8_gemspec(version)
41
+ "#{libv8_basename(version)}.gemspec"
42
+ end
43
+
44
+ def libv8_local_path(path=Gem.path)
45
+ name_glob = "#{libv8_gem_name}-*-#{ruby_platform}"
46
+
47
+ puts "looking for #{name_glob} in #{path.inspect}"
48
+
49
+ paths = path.map { |p| Dir.glob(File.join(p, 'specifications', name_glob + '.gemspec')) }.flatten
50
+
51
+ if paths.empty?
52
+ puts "#{name_glob} not found in #{path.inspect}"
53
+ return
54
+ end
55
+
56
+ specs = paths.map { |p| [p, eval(File.read(p))] }
57
+ .select { |_, spec| Gem::Requirement.new(libv8_requirement).satisfied_by?(spec.version) }
58
+ found_path, found_spec = specs.sort_by { |_, spec| spec.version }.last
59
+
60
+ unless found_path && found_spec
61
+ puts "not found in specs: no '#{libv8_requirement}' in #{paths.inspect}"
62
+ return
63
+ end
64
+
65
+ puts "found in specs: #{found_path}"
66
+
67
+ gemdir = File.basename(found_path, '.gemspec')
68
+ dir = File.expand_path(File.join(found_path, '..', '..', 'gems', gemdir))
69
+
70
+ unless Dir.exist?(dir)
71
+ puts "not found in gems: #{dir}"
72
+ return
73
+ end
74
+
75
+ puts "found in gems: #{dir}"
76
+
77
+ [dir, found_spec]
78
+ end
79
+
80
+ def vendor_path
81
+ File.join(Dir.pwd, 'vendor')
82
+ end
83
+
84
+ def libv8_vendor_path
85
+ libv8_local_path([vendor_path])
86
+ end
87
+
88
+ def parse_platform(str)
89
+ Gem::Platform.new(str).tap do |p|
90
+ p.instance_eval { @version = 'musl' } if str =~ /-musl/ && p.version.nil?
91
+ p.instance_eval { @cpu = 'x86_64' } if str =~ /universal.*darwin/
92
+ end
93
+ end
94
+
95
+ def ruby_platform
96
+ parse_platform(RUBY_PLATFORM)
97
+ end
98
+
99
+ def http_get(uri)
100
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
101
+ res = http.get(uri.path)
102
+
103
+ abort("HTTP error #{res.code}: #{uri}") unless res.code == '200'
104
+
105
+ return res.body
106
+ end
107
+ end
108
+
109
+ def libv8_remote_search
110
+ body = http_get(URI("https://rubygems.org/api/v1/versions/#{libv8_gem_name}.json"))
111
+ json = JSON.parse(body)
112
+
113
+ versions = json.select do |v|
114
+ Gem::Requirement.new(libv8_requirement).satisfied_by?(Gem::Version.new(v['number']))
115
+ end
116
+ abort(<<-ERROR) if versions.empty?
117
+ ERROR: could not find #{libv8_gem_name} (requirement #{libv8_requirement}) in rubygems.org
118
+ ERROR
119
+
120
+ platform_versions = versions.select do |v|
121
+ parse_platform(v['platform']) == ruby_platform unless v['platform'] =~ /universal.*darwin/
122
+ end
123
+ abort(<<-ERROR) if platform_versions.empty?
124
+ ERROR: found gems matching #{libv8_gem_name}:'#{libv8_requirement}', but no binary for #{ruby_platform}
125
+ try "gem install #{libv8_gem_name}:'#{libv8_requirement}'" to attempt to build #{libv8_gem_name} from source
126
+ ERROR
127
+
128
+ puts "found #{libv8_gem_name} for #{ruby_platform} on rubygems: #{platform_versions.map { |v| v['number'] }.join(', ')}"
129
+
130
+ platform_versions.sort_by { |v| Gem::Version.new(v['number']) }.last
131
+ end
132
+
133
+ def libv8_download_uri(name, version, platform)
134
+ URI("https://rubygems.org/downloads/#{name}-#{version}-#{platform}.gem")
135
+ end
136
+
137
+ def libv8_downloaded_gem(name, version, platform)
138
+ "#{name}-#{version}-#{platform}.gem"
139
+ end
140
+
141
+ def libv8_download(name, version, platform)
142
+ FileUtils.mkdir_p(File.join(vendor_path, 'cache'))
143
+ body = http_get(libv8_download_uri(name, version, platform))
144
+ File.open(File.join(vendor_path, 'cache', libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
145
+ end
146
+
147
+ def libv8_install!
148
+ cmd = "gem install #{libv8_gem_name} --version '#{libv8_requirement}' --install-dir '#{vendor_path}'"
149
+ puts "installing #{libv8_gem_name} using `#{cmd}`"
150
+ rc = system(cmd)
151
+
152
+ abort(<<-ERROR) unless rc
153
+ ERROR: could not install #{libv8_gem_name}:#{libv8_requirement}
154
+ try "gem install #{libv8_gem_name} -v '#{libv8_requirement}'" to attempt to build libv8 from source
155
+ ERROR
156
+
157
+ libv8_local_path([vendor_path])
158
+ end
159
+
160
+ def libv8_vendor!
161
+ return libv8_install! if Gem::VERSION < '2.0'
162
+
163
+ version = libv8_remote_search
164
+
165
+ puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
166
+ libv8_download(libv8_gem_name, version['number'], version['platform'])
167
+
168
+ package = Gem::Package.new(File.join(vendor_path, 'cache', libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
169
+ package.extract_files(File.join(vendor_path, 'gems', File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
170
+ FileUtils.mkdir_p(File.join(vendor_path, 'specifications'))
171
+ File.open(File.join(vendor_path, 'specifications', File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem') + '.gemspec'), 'wb') { |f| f.write(package.spec.to_ruby) }
172
+
173
+ libv8_vendor_path
174
+ end
175
+
176
+ def ensure_libv8_load_path
177
+ puts "platform ruby:#{RUBY_PLATFORM} rubygems:#{Gem::Platform.new(RUBY_PLATFORM)} detected:#{ruby_platform}"
178
+
179
+ libv8_path, spec = libv8_local_path
180
+ if !ENV['ONLY_INSTALLED_LIBV8_GEM'] && !libv8_path
181
+ libv8_path, spec = libv8_vendor_path || libv8_vendor!
182
+ end
183
+
184
+ abort(<<-ERROR) unless libv8_path
185
+ ERROR: could not find #{libv8_gem_name}
186
+ ERROR
187
+
188
+ $LOAD_PATH.unshift(File.join(libv8_path, 'ext'))
189
+ $LOAD_PATH.unshift(File.join(libv8_path, 'lib'))
190
+ end
191
+
192
+ ensure_libv8_load_path
193
+
194
+ require 'libv8-node'
195
+
196
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
6
197
 
7
198
  have_library('pthread')
8
199
  have_library('objc') if IS_DARWIN
9
- $CPPFLAGS.gsub! /-std=[^\s]+/, ''
200
+ cppflags_clear_std!
10
201
  $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
11
202
  $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
12
203
  $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
13
204
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
14
205
  $CPPFLAGS += " -std=c++0x"
15
206
  $CPPFLAGS += " -fpermissive"
16
- $CPPFLAGS += " -fno-omit-frame-pointer"
17
- if enable_config('avx2')
18
- $CPPFLAGS += " -mavx2"
19
- else
20
- $CPPFLAGS += " -mssse3"
21
- end
207
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
208
+ $CPPFLAGS += " -fvisibility=hidden "
209
+ cppflags_add_frame_pointer!
210
+ cppflags_add_cpu_extension!
22
211
 
23
212
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
24
213
 
25
214
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
215
+ $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
26
216
 
27
217
  if ENV['CXX']
28
218
  puts "SETTING CXX"
29
219
  CONFIG['CXX'] = ENV['CXX']
30
220
  end
221
+ # 1.9 has no $CXXFLAGS
222
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
223
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
31
224
 
32
225
  CXX11_TEST = <<EOS
33
226
  #if __cplusplus <= 199711L
@@ -57,103 +250,15 @@ if CONFIG['warnflags']
57
250
  CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
58
251
  end
59
252
 
60
- if enable_config('debug')
253
+ if enable_config('debug') || enable_config('asan')
61
254
  CONFIG['debugflags'] << ' -ggdb3 -O0'
62
255
  end
63
256
 
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
- # 1) 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
- # 2) the ruby binaries distributed with alpine (platform ending in -musl)
89
- # refuse to load binary gems by default
90
- def force_platform_gem
91
- gem_version = `gem --version`
92
- return 'gem' unless $?.success?
93
-
94
- if RUBY_PLATFORM != 'x86_64-linux-musl'
95
- return 'gem' if gem_version.to_f.zero? || gem_version.to_f >= 2.3
96
- return 'gem' if RUBY_PLATFORM != 'x86_64-linux'
97
- end
98
-
99
- gem_binary = `which gem`
100
- return 'gem' unless $?.success?
101
-
102
- ruby = File.foreach(gem_binary.strip).first.sub(/^#!/, '').strip
103
- unless File.file? ruby
104
- warn "No valid ruby: #{ruby}"
105
- return 'gem'
106
- end
107
-
108
- require 'tempfile'
109
- file = Tempfile.new('sq_mini_racer')
110
- file << <<EOS
111
- require 'rubygems'
112
- platforms = Gem.platforms
113
- platforms.reject! { |it| it == 'ruby' }
114
- if platforms.empty?
115
- platforms << Gem::Platform.new('x86_64-linux')
116
- end
117
- Gem.send(:define_method, :platforms) { platforms }
118
- #{IO.read(gem_binary.strip)}
119
- EOS
120
- file.close
121
- "#{ruby} '#{file.path}'"
122
- end
123
-
124
- LIBV8_VERSION = '6.7.288.46.1'
125
- libv8_rb = Dir.glob('**/libv8.rb').first
126
- FileUtils.mkdir_p('gemdir')
127
- unless libv8_rb
128
- gem_name = libv8_gem_name
129
- cmd = "#{fixup_libtinfo} #{force_platform_gem} install --version '= #{LIBV8_VERSION}' --install-dir gemdir #{gem_name}"
130
- puts "Will try downloading #{gem_name} gem: #{cmd}"
131
- `#{cmd}`
132
- unless $?.success?
133
- warn <<EOS
134
-
135
- WARNING: Could not download a private copy of the libv8 gem. Please make
136
- sure that you have internet access and that the `gem` binary is available.
137
-
138
- EOS
139
- end
140
-
141
- libv8_rb = Dir.glob('**/libv8.rb').first
142
- unless libv8_rb
143
- warn <<EOS
144
-
145
- WARNING: Could not find libv8 after the local copy of libv8 having supposedly
146
- been installed.
147
-
148
- EOS
149
- end
150
- end
257
+ Libv8::Node.configure_makefile
151
258
 
152
- if libv8_rb
153
- $:.unshift(File.dirname(libv8_rb) + '/../ext')
154
- $:.unshift File.dirname(libv8_rb)
259
+ if enable_config('asan')
260
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
261
+ $LDFLAGS.insert(0, " -fsanitize=address ")
155
262
  end
156
263
 
157
- require 'libv8'
158
- Libv8.configure_makefile
159
264
  create_makefile 'sq_mini_racer_extension'
@@ -23,7 +23,9 @@
23
23
  #if RUBY_API_VERSION_MAJOR > 1
24
24
  #include <ruby/thread.h>
25
25
  #endif
26
+ #include <ruby/io.h>
26
27
  #include <v8.h>
28
+ #include <v8-profiler.h>
27
29
  #include <libplatform/libplatform.h>
28
30
  #include <ruby/encoding.h>
29
31
  #include <pthread.h>
@@ -34,6 +36,8 @@
34
36
  #include "compat.hpp"
35
37
  #include "simdutf8check.h"
36
38
 
39
+ #include <time.h>
40
+
37
41
  using namespace v8;
38
42
 
39
43
  typedef struct {
@@ -47,6 +51,7 @@ public:
47
51
  ArrayBuffer::Allocator* allocator;
48
52
  StartupData* startup_data;
49
53
  bool interrupted;
54
+ bool added_gc_cb;
50
55
  pid_t pid;
51
56
  VALUE mutex;
52
57
 
@@ -64,15 +69,12 @@ public:
64
69
 
65
70
 
66
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
67
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
68
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
69
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
70
75
  }
71
76
 
72
- ~IsolateInfo() {
73
- void free_isolate(IsolateInfo*);
74
- free_isolate(this);
75
- }
77
+ ~IsolateInfo();
76
78
 
77
79
  void init(SnapshotInfo* snapshot_info = nullptr);
78
80
 
@@ -149,6 +151,7 @@ typedef struct {
149
151
  Local<Function> fun;
150
152
  Local<Value> *argv;
151
153
  EvalResult result;
154
+ size_t max_memory;
152
155
  } FunctionCall;
153
156
 
154
157
  enum IsolateFlags {
@@ -174,9 +177,14 @@ static VALUE rb_mJSON;
174
177
  static VALUE rb_cFailedV8Conversion;
175
178
  static VALUE rb_cDateTime = Qnil;
176
179
 
177
- static Platform* current_platform = NULL;
180
+ static std::unique_ptr<Platform> current_platform = NULL;
178
181
  static std::mutex platform_lock;
179
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
+
180
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
181
189
  bool platform_already_initialized = false;
182
190
 
@@ -188,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
188
196
  platform_lock.lock();
189
197
 
190
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
191
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
192
203
  } else {
193
204
  platform_already_initialized = true;
@@ -211,8 +222,8 @@ static void init_v8() {
211
222
 
212
223
  if (current_platform == NULL) {
213
224
  V8::InitializeICU();
214
- current_platform = platform::CreateDefaultPlatform();
215
- V8::InitializePlatform(current_platform);
225
+ current_platform = platform::NewDefaultPlatform();
226
+ V8::InitializePlatform(current_platform.get());
216
227
  V8::Initialize();
217
228
  }
218
229
 
@@ -255,15 +266,17 @@ static void prepare_result(MaybeLocal<Value> v8res,
255
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
256
267
  !local_value->IsDate() && !local_value->IsFunction()) {
257
268
  Local<Object> JSON = context->Global()->Get(
258
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
259
271
 
260
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
261
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
262
275
 
263
- Local<Object> object = local_value->ToObject();
276
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
264
277
  const unsigned argc = 1;
265
278
  Local<Value> argv[argc] = { object };
266
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
279
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
267
280
 
268
281
  if (json.IsEmpty()) {
269
282
  evalRes.executed = false;
@@ -287,11 +300,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
287
300
  evalRes.message = new Persistent<Value>();
288
301
  Local<Message> message = trycatch.Message();
289
302
  char buf[1000];
290
- 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
+
291
313
  len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
292
- *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString()),
293
- message->GetLineNumber(),
294
- message->GetStartColumn());
314
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
315
+ line,
316
+ column);
317
+
295
318
  if ((size_t) len >= sizeof(buf)) {
296
319
  len = sizeof(buf) - 1;
297
320
  buf[len] = '\0';
@@ -302,12 +325,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
302
325
  } else if(trycatch.HasTerminated()) {
303
326
  evalRes.terminated = true;
304
327
  evalRes.message = new Persistent<Value>();
305
- 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)");
306
329
  evalRes.message->Reset(isolate, tmp);
307
330
  }
308
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
309
332
  evalRes.backtrace = new Persistent<Value>();
310
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace(context).ToLocalChecked()->ToString());
333
+ evalRes.backtrace->Reset(isolate,
334
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
311
335
  }
312
336
  }
313
337
  }
@@ -322,7 +346,8 @@ nogvl_context_eval(void* arg) {
322
346
 
323
347
  EvalParams* eval_params = (EvalParams*)arg;
324
348
  EvalResult* result = eval_params->result;
325
- 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;
326
351
 
327
352
  Isolate::Scope isolate_scope(isolate);
328
353
  HandleScope handle_scope(isolate);
@@ -366,7 +391,10 @@ nogvl_context_eval(void* arg) {
366
391
  // parsing successful
367
392
  if (eval_params->max_memory > 0) {
368
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
369
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
370
398
  }
371
399
 
372
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -379,6 +407,12 @@ nogvl_context_eval(void* arg) {
379
407
  return 0;
380
408
  }
381
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
+
382
416
  // assumes isolate locking is in place
383
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
384
418
  Local<Value> value) {
@@ -391,11 +425,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
391
425
  }
392
426
 
393
427
  if (value->IsInt32()) {
394
- return INT2FIX(value->Int32Value());
428
+ return INT2FIX(value->Int32Value(context).ToChecked());
395
429
  }
396
430
 
397
431
  if (value->IsNumber()) {
398
- return rb_float_new(value->NumberValue());
432
+ return rb_float_new(value->NumberValue(context).ToChecked());
399
433
  }
400
434
 
401
435
  if (value->IsTrue()) {
@@ -410,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
410
444
  VALUE rb_array = rb_ary_new();
411
445
  Local<Array> arr = Local<Array>::Cast(value);
412
446
  for(uint32_t i=0; i < arr->Length(); i++) {
413
- Local<Value> element = arr->Get(i);
414
- 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());
415
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
416
453
  return rb_elem;
417
454
  }
@@ -436,31 +473,52 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
436
473
  VALUE rb_hash = rb_hash_new();
437
474
  TryCatch trycatch(isolate);
438
475
 
439
- Local<Object> object = value->ToObject();
476
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
440
477
  auto maybe_props = object->GetOwnPropertyNames(context);
441
478
  if (!maybe_props.IsEmpty()) {
442
479
  Local<Array> props = maybe_props.ToLocalChecked();
443
480
  for(uint32_t i=0; i < props->Length(); i++) {
444
- Local<Value> key = props->Get(i);
445
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
446
- Local<Value> prop_value = object->Get(key);
447
- // 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());
448
486
 
449
- if (trycatch.HasCaught()) {
450
- // TODO isolate code that translates execption to ruby
451
- // exception so we can properly return it
452
- 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();
453
491
  }
454
492
 
455
- 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());
456
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
457
496
  }
458
497
  }
459
498
  return rb_hash;
460
499
  }
461
500
 
462
- Local<String> rstr = value->ToString();
463
- 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
+ }
464
522
  }
465
523
 
466
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -557,7 +615,7 @@ treat_as_latin1:
557
615
  return v8str.ToLocalChecked();
558
616
  }
559
617
 
560
- 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)
561
619
  {
562
620
  EscapableHandleScope scope(isolate);
563
621
 
@@ -571,87 +629,87 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
571
629
  VALUE klass;
572
630
 
573
631
  switch (TYPE(value)) {
574
- case T_FIXNUM:
632
+ case T_FIXNUM:
575
633
  {
576
- fixnum = NUM2LONG(value);
577
- if (fixnum > INT_MAX)
578
- {
579
- return scope.Escape(Number::New(isolate, (double)fixnum));
580
- }
581
- 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));
582
638
  }
583
- case T_FLOAT:
584
- return scope.Escape(Number::New(isolate, NUM2DBL(value)));
585
- case T_STRING:
639
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
640
+ }
641
+ case T_FLOAT:
642
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
643
+ case T_STRING:
586
644
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
587
- case T_NIL:
588
- return scope.Escape(Null(isolate));
589
- case T_TRUE:
590
- return scope.Escape(True(isolate));
591
- case T_FALSE:
592
- return scope.Escape(False(isolate));
593
- 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:
594
652
  {
595
- length = RARRAY_LEN(value);
596
- array = Array::New(isolate, (int)length);
597
- for(i=0; i<length; i++) {
598
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
599
- }
600
- 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);
601
659
  }
602
- case T_HASH:
660
+ case T_HASH:
603
661
  {
604
- object = Object::New(isolate);
605
- hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
606
- length = RARRAY_LEN(hash_as_array);
607
- for(i=0; i<length; i++) {
608
- pair = rb_ary_entry(hash_as_array, i);
609
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
610
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
611
- }
612
- 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);
613
671
  }
614
- case T_SYMBOL:
672
+ case T_SYMBOL:
615
673
  {
616
- value = rb_funcall(value, rb_intern("to_s"), 0);
674
+ value = rb_funcall(value, rb_intern("to_s"), 0);
617
675
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
618
676
  }
619
- case T_DATA:
677
+ case T_DATA:
620
678
  {
621
- klass = rb_funcall(value, rb_intern("class"), 0);
622
- 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)
623
683
  {
624
- if (klass == rb_cDateTime)
625
- {
626
- value = rb_funcall(value, rb_intern("to_time"), 0);
627
- }
628
- value = rb_funcall(value, rb_intern("to_f"), 0);
629
- return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
684
+ value = rb_funcall(value, rb_intern("to_time"), 0);
630
685
  }
686
+ value = rb_funcall(value, rb_intern("to_f"), 0);
687
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
688
+ }
631
689
  // break intentionally missing
632
690
  }
633
- case T_OBJECT:
634
- case T_CLASS:
635
- case T_ICLASS:
636
- case T_MODULE:
637
- case T_REGEXP:
638
- case T_MATCH:
639
- case T_STRUCT:
640
- case T_BIGNUM:
641
- case T_FILE:
642
- case T_UNDEF:
643
- case T_NODE:
644
- 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:
645
703
  {
646
704
  if (rb_respond_to(value, rb_intern("to_s"))) {
647
705
  // TODO: if this throws we're screwed
648
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
649
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
650
708
  }
651
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
652
711
  }
653
712
  }
654
-
655
713
  }
656
714
 
657
715
  static void unblock_eval(void *ptr) {
@@ -659,6 +717,81 @@ static void unblock_eval(void *ptr) {
659
717
  eval->context_info->isolate_info->interrupted = true;
660
718
  }
661
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
+
662
795
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
663
796
  SnapshotInfo* snapshot_info;
664
797
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -677,7 +810,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
677
810
 
678
811
  init_v8();
679
812
 
680
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
813
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
681
814
 
682
815
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
683
816
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -708,7 +841,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
708
841
  init_v8();
709
842
 
710
843
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
711
- 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));
712
845
 
713
846
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
714
847
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -771,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
771
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
772
905
  }
773
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
+
774
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
775
931
  ContextInfo* context_info;
776
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -887,8 +1043,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
887
1043
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
888
1044
 
889
1045
  if (result.json) {
890
- Local<String> rstr = tmp->ToString();
891
- 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"));
892
1048
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
893
1049
  } else {
894
1050
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -1009,6 +1165,8 @@ gvl_ruby_callback(void* data) {
1009
1165
  VALUE result;
1010
1166
  VALUE self;
1011
1167
  VALUE parent;
1168
+ ContextInfo* context_info;
1169
+
1012
1170
  {
1013
1171
  HandleScope scope(args->GetIsolate());
1014
1172
  Local<External> external = Local<External>::Cast(args->Data());
@@ -1021,7 +1179,6 @@ gvl_ruby_callback(void* data) {
1021
1179
  return NULL;
1022
1180
  }
1023
1181
 
1024
- ContextInfo* context_info;
1025
1182
  Data_Get_Struct(parent, ContextInfo, context_info);
1026
1183
 
1027
1184
  if (length > 0) {
@@ -1031,7 +1188,7 @@ gvl_ruby_callback(void* data) {
1031
1188
  for (int i = 0; i < length; i++) {
1032
1189
  Local<Value> value = ((*args)[i]).As<Value>();
1033
1190
  VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1034
- *context_info->context, value);
1191
+ *context_info->context, value);
1035
1192
  rb_ary_push(ruby_args, tmp);
1036
1193
  }
1037
1194
  }
@@ -1044,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1044
1201
  callback_data.failed = false;
1045
1202
 
1046
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1047
- 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"));
1048
1207
  args->GetIsolate()->TerminateExecution();
1049
1208
  if (length > 0) {
1050
1209
  rb_ary_clear(ruby_args);
@@ -1058,11 +1217,11 @@ gvl_ruby_callback(void* data) {
1058
1217
 
1059
1218
  if(callback_data.failed) {
1060
1219
  rb_iv_set(parent, "@current_exception", result);
1061
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1062
1221
  }
1063
1222
  else {
1064
1223
  HandleScope scope(args->GetIsolate());
1065
- 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);
1066
1225
  args->GetReturnValue().Set(v8_result);
1067
1226
  }
1068
1227
 
@@ -1119,8 +1278,10 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1119
1278
  Local<Context> context = context_info->context->Get(isolate);
1120
1279
  Context::Scope context_scope(context);
1121
1280
 
1122
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
1123
- 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();
1124
1285
 
1125
1286
  // copy self so we can access from v8 external
1126
1287
  VALUE* self_copy;
@@ -1130,24 +1291,38 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1130
1291
  Local<Value> external = External::New(isolate, self_copy);
1131
1292
 
1132
1293
  if (parent_object == Qnil) {
1133
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1134
- } else {
1294
+ context->Global()->Set(
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1298
+ ->GetFunction(context)
1299
+ .ToLocalChecked());
1135
1300
 
1136
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1137
- 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();
1138
1307
 
1139
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1140
1309
  if (parsed_script.IsEmpty()) {
1141
- parse_error = true;
1310
+ parse_error = true;
1142
1311
  } else {
1143
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1312
+ MaybeLocal<Value> maybe_value =
1313
+ parsed_script.ToLocalChecked()->Run(context);
1144
1314
  attach_error = true;
1145
1315
 
1146
1316
  if (!maybe_value.IsEmpty()) {
1147
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1148
- if (value->IsObject()){
1149
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1150
- 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;
1151
1326
  }
1152
1327
  }
1153
1328
  }
@@ -1177,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1177
1352
  return context_info->isolate_info->mutex;
1178
1353
  }
1179
1354
 
1180
- void free_isolate(IsolateInfo* isolate_info) {
1181
-
1182
- if (isolate_info->isolate) {
1183
- Locker lock(isolate_info->isolate);
1184
- }
1185
-
1186
- if (isolate_info->isolate) {
1187
- if (isolate_info->interrupted) {
1188
- 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");
1189
1361
  } else {
1190
-
1191
- if (isolate_info->pid != getpid()) {
1192
- 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
+ );
1193
1372
  } else {
1194
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1195
1374
  }
1196
1375
  }
1197
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1198
1377
  }
1199
1378
 
1200
- if (isolate_info->startup_data) {
1201
- delete[] isolate_info->startup_data->data;
1202
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1203
1382
  }
1204
1383
 
1205
- delete isolate_info->allocator;
1384
+ delete allocator;
1206
1385
  }
1207
1386
 
1208
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1209
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1210
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1211
1390
  Persistent<Context>* context = context_info->context;
@@ -1222,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1222
1401
  }
1223
1402
 
1224
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
+
1225
1418
  return NULL;
1226
1419
  }
1227
1420
 
@@ -1236,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1236
1429
 
1237
1430
  if (isolate_info && isolate_info->refs() > 1) {
1238
1431
  pthread_t free_context_thread;
1239
- 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)) {
1240
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1241
1435
  }
1242
-
1243
1436
  } else {
1244
1437
  free_context_raw(context_info_copy);
1245
1438
  }
1246
1439
 
1247
- if (context_info->context && isolate_info && isolate_info->isolate) {
1248
1440
  context_info->context = NULL;
1249
- }
1250
-
1251
- if (isolate_info) {
1252
1441
  context_info->isolate_info = NULL;
1253
1442
  }
1254
- }
1255
1443
 
1256
1444
  static void deallocate_isolate(void* data) {
1257
1445
 
@@ -1265,7 +1453,7 @@ static void mark_isolate(void* data) {
1265
1453
  isolate_info->mark();
1266
1454
  }
1267
1455
 
1268
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1269
1457
  ContextInfo* context_info = (ContextInfo*)data;
1270
1458
 
1271
1459
  free_context(context_info);
@@ -1280,22 +1468,22 @@ static void mark_context(void* data) {
1280
1468
  }
1281
1469
  }
1282
1470
 
1283
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1284
1472
  xfree(data);
1285
1473
  }
1286
1474
 
1287
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1288
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1289
1477
  delete[] snapshot_info->data;
1290
1478
  xfree(snapshot_info);
1291
1479
  }
1292
1480
 
1293
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1294
1482
  VALUE* self = ALLOC(VALUE);
1295
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1296
1484
  }
1297
1485
 
1298
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1299
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1300
1488
  context_info->isolate_info = NULL;
1301
1489
  context_info->context = NULL;
@@ -1303,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1303
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1304
1492
  }
1305
1493
 
1306
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1307
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1308
1496
  snapshot_info->data = NULL;
1309
1497
  snapshot_info->raw_size = 0;
@@ -1311,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1311
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1312
1500
  }
1313
1501
 
1314
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1315
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1316
1504
 
1317
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1350,6 +1538,71 @@ rb_heap_stats(VALUE self) {
1350
1538
  return rval;
1351
1539
  }
1352
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
+
1353
1606
  static VALUE
1354
1607
  rb_context_stop(VALUE self) {
1355
1608
 
@@ -1402,13 +1655,23 @@ nogvl_context_call(void *args) {
1402
1655
  if (!call) {
1403
1656
  return 0;
1404
1657
  }
1405
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1406
1660
 
1407
1661
  // in gvl flag
1408
1662
  isolate->SetData(IN_GVL, (void*)false);
1409
1663
  // terminate ASAP
1410
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1411
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
+
1412
1675
  Isolate::Scope isolate_scope(isolate);
1413
1676
  EscapableHandleScope handle_scope(isolate);
1414
1677
  TryCatch trycatch(isolate);
@@ -1466,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1466
1729
  call_argv = argv + 1;
1467
1730
  }
1468
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
+
1469
1739
  bool missingFunction = false;
1470
1740
  {
1471
1741
  Locker lock(isolate);
@@ -1477,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1477
1747
 
1478
1748
  // examples of such usage can be found in
1479
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1480
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1481
- 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
+ }
1482
1755
 
1483
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1484
1757
  missingFunction = true;
@@ -1494,7 +1767,7 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1494
1767
  return Qnil;
1495
1768
  }
1496
1769
  for(int i=0; i < fun_argc; i++) {
1497
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1770
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1498
1771
  }
1499
1772
  }
1500
1773
  #if RUBY_API_VERSION_MAJOR > 1
@@ -1527,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1527
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1528
1801
  }
1529
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
+
1530
1824
  extern "C" {
1531
1825
 
1532
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1533
1827
  {
1534
- 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
+ }
1535
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1536
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1537
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1538
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1558,6 +1863,8 @@ extern "C" {
1558
1863
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1559
1864
  rb_define_method(rb_cContext, "low_memory_notification", (VALUE(*)(...))&rb_context_low_memory_notification, 0);
1560
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
+
1561
1868
  rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1562
1869
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1563
1870
  rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
@@ -1577,8 +1884,19 @@ extern "C" {
1577
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1578
1885
 
1579
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);
1580
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1581
1890
 
1582
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
+ }
1583
1901
  }
1584
1902
  }