sq_mini_racer 0.2.4.sqreen3 → 0.2.5.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da4cace43c9af136e8d30f3918dbd99af25f72fd904d7db9a888a843925000ba
4
- data.tar.gz: 3f234d5dafea154baa80d0a459bcff351cb0c0d01ed2604b293bd22756809303
3
+ metadata.gz: a4deece72e6d02478b4381a59ab9b1988a2fbff6a30e811596183240699fd018
4
+ data.tar.gz: ac83f077c9b56423cd8b8e716d8102bec73050c8a4ae0e525fda931f43ec6762
5
5
  SHA512:
6
- metadata.gz: 9b461007d884a961d3b081ceac7c5d815477d3993ced64f108ae078ede7d54318a0a3aa3b504431b50d6b7167ea4f0ac592f097406e31c35cd184bb25e84ac6b
7
- data.tar.gz: a8e76f1ddd78a6578aaa35424f87ffce467065cf1b981af9673a6a7e01d5c6e0263161eaa68f1263acffd20e400ae0453a9a388cc0735d7d75b539f6d2973b09
6
+ metadata.gz: cad6011c1066ccc51e1c3bfe3064c50e7dbdc6dd04127ce24f855a97f9ba8fd492a5de3b1af54b810b4a112d74f03bcd746c8d95af822a5f9d59e8cd9cdce8a2
7
+ data.tar.gz: f985a398202455acfadd914606306d5469928bfbf69394fd39b5a79b097f81a6994bf4875bb7765d01b4fd2797b00378a5e3d23de4a05b4d3588216e3ac59cc9
data/CHANGELOG CHANGED
@@ -1,3 +1,18 @@
1
+ - 14-05-2019
2
+
3
+ - 0.2.6
4
+
5
+ - FEATURE: add support for write_heap_snapshot which helps you analyze memory
6
+
7
+ - 25-04-2019
8
+
9
+ - 0.2.5
10
+
11
+ - FIX: Compatiblity fixes for V8 7 and above @ignisf
12
+ - FIX: Memory leak in gc_callback @messense
13
+ - IMPROVEMENT: Added example of sourcemap support @ianks
14
+ - URGENT: you will need this release for latest version of libv8 to work
15
+
1
16
  - 02-11-2018
2
17
 
3
18
  - 0.2.4
data/README.md CHANGED
@@ -101,6 +101,27 @@ context.eval('bar()', filename: 'a/bar.js')
101
101
 
102
102
  ```
103
103
 
104
+ ### Fork safety
105
+
106
+ Some Ruby web servers employ forking (for example unicorn or puma in clustered mode). V8 is not fork safe.
107
+ Sadly Ruby does not have support for fork notifications per [#5446](https://bugs.ruby-lang.org/issues/5446).
108
+
109
+ If you want to ensure your application does not leak memory after fork either:
110
+
111
+ 1. Ensure no MiniRacer::Context objects are created in the master process
112
+
113
+ Or
114
+
115
+ 2. Dispose manually of all MiniRacer::Context objects prior to forking
116
+
117
+ ```ruby
118
+ # before fork
119
+
120
+ require 'objspace'
121
+ ObjectSpace.each_object(MiniRacer::Context){|c| c.dispose}
122
+
123
+ # fork here
124
+ ```
104
125
 
105
126
  ### Threadsafe
106
127
 
@@ -350,6 +371,10 @@ Note how the global interpreter lock release leads to 2 threads doing the same w
350
371
 
351
372
  As a rule MiniRacer strives to always support and depend on the latest stable version of libv8.
352
373
 
374
+ ## Source Maps
375
+
376
+ MiniRacer can fully support source maps but must be configured correctly to do so. [Check out this example](./examples/source-map-support/) for a working implementation.
377
+
353
378
  ## Installation
354
379
 
355
380
  Add this line to your application's Gemfile:
data/Rakefile CHANGED
@@ -19,6 +19,22 @@ Rake::ExtensionTask.new('prv_ext_loader', gem)
19
19
 
20
20
  # via http://blog.flavorjon.es/2009/06/easily-valgrind-gdb-your-ruby-c.html
21
21
  namespace :test do
22
+ desc "run test suite with Address Sanitizer"
23
+ task :asan do
24
+ ENV["CONFIGURE_ARGS"] = [ENV["CONFIGURE_ARGS"], '--enable-asan'].compact.join(' ')
25
+ Rake::Task['compile'].invoke
26
+
27
+ asan_path = `ldconfig -N -p |grep libasan | grep -v 32 | sed 's/.* => \\(.*\\)$/\\1/'`.chomp.split("\n")[-1]
28
+
29
+
30
+ cmdline = "env LD_PRELOAD=\"#{asan_path}\" ruby test/test_leak.rb"
31
+ puts cmdline
32
+ system cmdline
33
+
34
+ cmdline = "env LD_PRELOAD=\"#{asan_path}\" rake test"
35
+ puts cmdline
36
+ system cmdline
37
+ end
22
38
  # partial-loads-ok and undef-value-errors necessary to ignore
23
39
  # spurious (and eminently ignorable) warnings from the ruby
24
40
  # interpreter
@@ -55,3 +71,28 @@ namespace :test do
55
71
  end
56
72
  end
57
73
  end
74
+
75
+ desc 'run clang-tidy linter on mini_racer_extension.cc'
76
+ task :lint do
77
+ require 'mkmf'
78
+ require 'libv8'
79
+
80
+ Libv8.configure_makefile
81
+
82
+ conf = RbConfig::CONFIG.merge('hdrdir' => $hdrdir.quote, 'srcdir' => $srcdir.quote,
83
+ 'arch_hdrdir' => $arch_hdrdir.quote,
84
+ 'top_srcdir' => $top_srcdir.quote)
85
+ if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty?
86
+ conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '')
87
+ end
88
+
89
+ checks = %W(bugprone-*
90
+ cert-*
91
+ cppcoreguidelines-*
92
+ clang-analyzer-*
93
+ performance-*
94
+ portability-*
95
+ readability-*).join(',')
96
+
97
+ sh RbConfig::expand("clang-tidy -checks='#{checks}' ext/mini_racer_extension/mini_racer_extension.cc -- #$INCFLAGS #$CPPFLAGS", conf)
98
+ end
@@ -1,24 +1,191 @@
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
+ return "libv8-solaris" if IS_SOLARIS
30
+ return "libv8-alpine" if IS_LINUX_MUSL
31
+
32
+ 'libv8'
33
+ end
34
+
35
+ def libv8_version
36
+ '7.3.492.27.1'
37
+ end
38
+
39
+ def libv8_basename
40
+ "#{libv8_gem_name}-#{libv8_version}-#{ruby_platform}"
41
+ end
42
+
43
+ def libv8_gemspec
44
+ "#{libv8_basename}.gemspec"
45
+ end
46
+
47
+ def libv8_local_path
48
+ puts "looking for #{libv8_gemspec} in installed gems"
49
+ candidates = Gem.path.map { |p| File.join(p, 'specifications', libv8_gemspec) }
50
+ found = candidates.select { |f| File.exist?(f) }.first
51
+
52
+ unless found
53
+ puts "#{libv8_gemspec} not found in installed gems"
54
+ return
55
+ end
56
+
57
+ puts "found in installed specs: #{found}"
58
+
59
+ dir = File.expand_path(File.join(found, '..', '..', 'gems', libv8_basename))
60
+
61
+ unless Dir.exist?(dir)
62
+ puts "not found in installed gems: #{dir}"
63
+ return
64
+ end
65
+
66
+ puts "found in installed gems: #{dir}"
67
+
68
+ dir
69
+ end
70
+
71
+ def vendor_path
72
+ File.join(Dir.pwd, 'vendor')
73
+ end
74
+
75
+ def libv8_vendor_path
76
+ puts "looking for #{libv8_basename} in #{vendor_path}"
77
+ path = Dir.glob("#{vendor_path}/#{libv8_basename}").first
78
+
79
+ unless path
80
+ puts "#{libv8_basename} not found in #{vendor_path}"
81
+ return
82
+ end
83
+
84
+ puts "looking for #{libv8_basename}/lib/libv8.rb in #{vendor_path}"
85
+ unless Dir.glob(File.join(vendor_path, libv8_basename, 'lib', 'libv8.rb')).first
86
+ puts "#{libv8_basename}/lib/libv8.rb not found in #{vendor_path}"
87
+ return
88
+ end
89
+
90
+ path
91
+ end
92
+
93
+ def parse_platform(str)
94
+ Gem::Platform.new(str).tap { |p| p.instance_eval { @os = 'linux-musl' } if str =~ /musl/ }
95
+ end
96
+
97
+ def ruby_platform
98
+ parse_platform(RUBY_PLATFORM)
99
+ end
100
+
101
+ def http_get(uri)
102
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
103
+ res = http.get(uri.path)
104
+
105
+ abort("HTTP error #{res.code}: #{uri}") unless res.code == '200'
106
+
107
+ return res.body
108
+ end
109
+ end
110
+
111
+ def libv8_remote_search
112
+ body = http_get(URI("https://rubygems.org/api/v1/versions/#{libv8_gem_name}.json"))
113
+ json = JSON.parse(body)
114
+
115
+ versions = json.select do |v|
116
+ Gem::Version.new(v['number']) == Gem::Version.new(libv8_version)
117
+ end
118
+ abort(<<-ERROR) if versions.empty?
119
+ ERROR: could not find #{libv8_version}
120
+ ERROR
121
+
122
+ platform_versions = versions.select do |v|
123
+ parse_platform(v['platform']) == ruby_platform
124
+ end
125
+ abort(<<-ERROR) if platform_versions.empty?
126
+ ERROR: found #{libv8_gem_name}-#{libv8_version}, but no binary for #{ruby_platform}
127
+ try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
128
+ ERROR
129
+
130
+ platform_versions.first
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(vendor_path)
143
+ body = http_get(libv8_download_uri(name, version, platform))
144
+ File.open(File.join(vendor_path, libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
145
+ end
146
+
147
+ def libv8_vendor!
148
+ version = libv8_remote_search
149
+
150
+ puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
151
+ libv8_download(libv8_gem_name, version['number'], version['platform'])
152
+
153
+ package = Gem::Package.new(File.join(vendor_path, libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
154
+ package.extract_files(File.join(vendor_path, File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
155
+
156
+ libv8_vendor_path
157
+ end
158
+
159
+ def ensure_libv8_load_path
160
+ puts "detected platform #{RUBY_PLATFORM} => #{ruby_platform}"
161
+
162
+ libv8_path = libv8_local_path || libv8_vendor_path || libv8_vendor!
163
+
164
+ abort(<<-ERROR) unless libv8_path
165
+ ERROR: could not find #{libv8_gem_name}
166
+ ERROR
167
+
168
+ $LOAD_PATH.unshift(File.join(libv8_path, 'ext'))
169
+ $LOAD_PATH.unshift(File.join(libv8_path, 'lib'))
170
+ end
171
+
172
+ ensure_libv8_load_path
173
+
174
+ require 'libv8'
175
+
176
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
6
177
 
7
178
  have_library('pthread')
8
179
  have_library('objc') if IS_DARWIN
9
- $CPPFLAGS.gsub! /-std=[^\s]+/, ''
180
+ cppflags_clear_std!
10
181
  $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
11
182
  $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
12
183
  $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
13
184
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
14
185
  $CPPFLAGS += " -std=c++0x"
15
186
  $CPPFLAGS += " -fpermissive"
16
- $CPPFLAGS += " -fno-omit-frame-pointer"
17
- if enable_config('avx2')
18
- $CPPFLAGS += " -mavx2"
19
- else
20
- $CPPFLAGS += " -mssse3"
21
- end
187
+ cppflags_add_frame_pointer!
188
+ cppflags_add_cpu_extension!
22
189
 
23
190
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
24
191
 
@@ -57,103 +224,15 @@ if CONFIG['warnflags']
57
224
  CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
58
225
  end
59
226
 
60
- if enable_config('debug')
227
+ if enable_config('debug') || enable_config('asan')
61
228
  CONFIG['debugflags'] << ' -ggdb3 -O0'
62
229
  end
63
230
 
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
231
+ Libv8.configure_makefile
151
232
 
152
- if libv8_rb
153
- $:.unshift(File.dirname(libv8_rb) + '/../ext')
154
- $:.unshift File.dirname(libv8_rb)
233
+ if enable_config('asan')
234
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
235
+ $LDFLAGS.insert(0, " -fsanitize=address ")
155
236
  end
156
237
 
157
- require 'libv8'
158
- Libv8.configure_makefile
159
238
  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>
@@ -174,7 +176,7 @@ static VALUE rb_mJSON;
174
176
  static VALUE rb_cFailedV8Conversion;
175
177
  static VALUE rb_cDateTime = Qnil;
176
178
 
177
- static Platform* current_platform = NULL;
179
+ static std::unique_ptr<Platform> current_platform = NULL;
178
180
  static std::mutex platform_lock;
179
181
 
180
182
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
@@ -211,8 +213,8 @@ static void init_v8() {
211
213
 
212
214
  if (current_platform == NULL) {
213
215
  V8::InitializeICU();
214
- current_platform = platform::CreateDefaultPlatform();
215
- V8::InitializePlatform(current_platform);
216
+ current_platform = platform::NewDefaultPlatform();
217
+ V8::InitializePlatform(current_platform.get());
216
218
  V8::Initialize();
217
219
  }
218
220
 
@@ -254,16 +256,16 @@ static void prepare_result(MaybeLocal<Value> v8res,
254
256
  Local<Value> local_value = v8res.ToLocalChecked();
255
257
  if ((local_value->IsObject() || local_value->IsArray()) &&
256
258
  !local_value->IsDate() && !local_value->IsFunction()) {
257
- Local<Object> JSON = context->Global()->Get(
258
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
259
+ Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
260
+ ->ToObject(context).ToLocalChecked();
259
261
 
260
262
  Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
261
263
  .As<Function>();
262
264
 
263
- Local<Object> object = local_value->ToObject();
265
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
264
266
  const unsigned argc = 1;
265
267
  Local<Value> argv[argc] = { object };
266
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
268
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
267
269
 
268
270
  if (json.IsEmpty()) {
269
271
  evalRes.executed = false;
@@ -287,11 +289,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
287
289
  evalRes.message = new Persistent<Value>();
288
290
  Local<Message> message = trycatch.Message();
289
291
  char buf[1000];
290
- int len;
292
+ int len, line, column;
293
+
294
+ if (!message->GetLineNumber(context).To(&line)) {
295
+ line = 0;
296
+ }
297
+
298
+ if (!message->GetStartColumn(context).To(&column)) {
299
+ column = 0;
300
+ }
301
+
291
302
  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());
303
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
304
+ line,
305
+ column);
306
+
295
307
  if ((size_t) len >= sizeof(buf)) {
296
308
  len = sizeof(buf) - 1;
297
309
  buf[len] = '\0';
@@ -307,7 +319,8 @@ static void prepare_result(MaybeLocal<Value> v8res,
307
319
  }
308
320
  if (!trycatch.StackTrace(context).IsEmpty()) {
309
321
  evalRes.backtrace = new Persistent<Value>();
310
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace(context).ToLocalChecked()->ToString());
322
+ evalRes.backtrace->Reset(isolate,
323
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
311
324
  }
312
325
  }
313
326
  }
@@ -391,11 +404,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
391
404
  }
392
405
 
393
406
  if (value->IsInt32()) {
394
- return INT2FIX(value->Int32Value());
407
+ return INT2FIX(value->Int32Value(context).ToChecked());
395
408
  }
396
409
 
397
410
  if (value->IsNumber()) {
398
- return rb_float_new(value->NumberValue());
411
+ return rb_float_new(value->NumberValue(context).ToChecked());
399
412
  }
400
413
 
401
414
  if (value->IsTrue()) {
@@ -436,7 +449,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
436
449
  VALUE rb_hash = rb_hash_new();
437
450
  TryCatch trycatch(isolate);
438
451
 
439
- Local<Object> object = value->ToObject();
452
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
440
453
  auto maybe_props = object->GetOwnPropertyNames(context);
441
454
  if (!maybe_props.IsEmpty()) {
442
455
  Local<Array> props = maybe_props.ToLocalChecked();
@@ -459,8 +472,8 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
459
472
  return rb_hash;
460
473
  }
461
474
 
462
- Local<String> rstr = value->ToString();
463
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
475
+ Local<String> rstr = value->ToString(context).ToLocalChecked();
476
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
464
477
  }
465
478
 
466
479
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -557,7 +570,7 @@ treat_as_latin1:
557
570
  return v8str.ToLocalChecked();
558
571
  }
559
572
 
560
- static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
573
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value)
561
574
  {
562
575
  EscapableHandleScope scope(isolate);
563
576
 
@@ -571,87 +584,86 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
571
584
  VALUE klass;
572
585
 
573
586
  switch (TYPE(value)) {
574
- case T_FIXNUM:
587
+ case T_FIXNUM:
575
588
  {
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));
589
+ fixnum = NUM2LONG(value);
590
+ if (fixnum > INT_MAX)
591
+ {
592
+ return scope.Escape(Number::New(isolate, (double)fixnum));
593
+ }
594
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
582
595
  }
583
- case T_FLOAT:
584
- return scope.Escape(Number::New(isolate, NUM2DBL(value)));
585
- case T_STRING:
596
+ case T_FLOAT:
597
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
598
+ case T_STRING:
586
599
  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:
600
+ case T_NIL:
601
+ return scope.Escape(Null(isolate));
602
+ case T_TRUE:
603
+ return scope.Escape(True(isolate));
604
+ case T_FALSE:
605
+ return scope.Escape(False(isolate));
606
+ case T_ARRAY:
594
607
  {
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);
608
+ length = RARRAY_LEN(value);
609
+ array = Array::New(isolate, (int)length);
610
+ for(i=0; i<length; i++) {
611
+ array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
612
+ }
613
+ return scope.Escape(array);
601
614
  }
602
- case T_HASH:
615
+ case T_HASH:
603
616
  {
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);
617
+ object = Object::New(isolate);
618
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
619
+ length = RARRAY_LEN(hash_as_array);
620
+ for(i=0; i<length; i++) {
621
+ pair = rb_ary_entry(hash_as_array, i);
622
+ object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
623
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
624
+ }
625
+ return scope.Escape(object);
613
626
  }
614
- case T_SYMBOL:
627
+ case T_SYMBOL:
615
628
  {
616
- value = rb_funcall(value, rb_intern("to_s"), 0);
629
+ value = rb_funcall(value, rb_intern("to_s"), 0);
617
630
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
618
631
  }
619
- case T_DATA:
632
+ case T_DATA:
633
+ {
634
+ klass = rb_funcall(value, rb_intern("class"), 0);
635
+ if (klass == rb_cTime || klass == rb_cDateTime)
620
636
  {
621
- klass = rb_funcall(value, rb_intern("class"), 0);
622
- if (klass == rb_cTime || klass == rb_cDateTime)
637
+ if (klass == rb_cDateTime)
623
638
  {
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));
639
+ value = rb_funcall(value, rb_intern("to_time"), 0);
630
640
  }
641
+ value = rb_funcall(value, rb_intern("to_f"), 0);
642
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
643
+ }
631
644
  // break intentionally missing
632
645
  }
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:
646
+ case T_OBJECT:
647
+ case T_CLASS:
648
+ case T_ICLASS:
649
+ case T_MODULE:
650
+ case T_REGEXP:
651
+ case T_MATCH:
652
+ case T_STRUCT:
653
+ case T_BIGNUM:
654
+ case T_FILE:
655
+ case T_UNDEF:
656
+ case T_NODE:
657
+ default:
645
658
  {
646
659
  if (rb_respond_to(value, rb_intern("to_s"))) {
647
660
  // TODO: if this throws we're screwed
648
661
  value = rb_funcall(value, rb_intern("to_s"), 0);
649
662
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
650
663
  }
651
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
652
- }
664
+ return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
665
+ }
653
666
  }
654
-
655
667
  }
656
668
 
657
669
  static void unblock_eval(void *ptr) {
@@ -659,6 +671,91 @@ static void unblock_eval(void *ptr) {
659
671
  eval->context_info->isolate_info->interrupted = true;
660
672
  }
661
673
 
674
+ /*
675
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
676
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
677
+ */
678
+ bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
679
+ const char *utf8_source, const char *name) {
680
+ Context::Scope context_scope(context);
681
+ TryCatch try_catch(isolate);
682
+ Local<String> source_string;
683
+ if (!String::NewFromUtf8(isolate, utf8_source,
684
+ NewStringType::kNormal)
685
+ .ToLocal(&source_string)) {
686
+ return false;
687
+ }
688
+ Local<v8::String> resource_name =
689
+ String::NewFromUtf8(isolate, name, NewStringType::kNormal)
690
+ .ToLocalChecked();
691
+ ScriptOrigin origin(resource_name);
692
+ ScriptCompiler::Source source(source_string, origin);
693
+ Local<Script> script;
694
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
695
+ return false;
696
+ if (script->Run(context).IsEmpty())
697
+ return false;
698
+ // CHECK(!try_catch.HasCaught());
699
+ return true;
700
+ }
701
+
702
+ StartupData
703
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
704
+ // Create a new isolate and a new context from scratch, optionally run
705
+ // a script to embed, and serialize to create a snapshot blob.
706
+ StartupData result = {nullptr, 0};
707
+ {
708
+ SnapshotCreator snapshot_creator;
709
+ Isolate *isolate = snapshot_creator.GetIsolate();
710
+ {
711
+ HandleScope scope(isolate);
712
+ Local<Context> context = Context::New(isolate);
713
+ if (embedded_source != nullptr &&
714
+ !run_extra_code(isolate, context, embedded_source,
715
+ "<embedded>")) {
716
+ return result;
717
+ }
718
+ snapshot_creator.SetDefaultContext(context);
719
+ }
720
+ result = snapshot_creator.CreateBlob(
721
+ SnapshotCreator::FunctionCodeHandling::kClear);
722
+ }
723
+ return result;
724
+ }
725
+
726
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
727
+ const char *warmup_source) {
728
+ // Use following steps to create a warmed up snapshot blob from a cold one:
729
+ // - Create a new isolate from the cold snapshot.
730
+ // - Create a new context to run the warmup script. This will trigger
731
+ // compilation of executed functions.
732
+ // - Create a new context. This context will be unpolluted.
733
+ // - Serialize the isolate and the second context into a new snapshot blob.
734
+ StartupData result = {nullptr, 0};
735
+
736
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
737
+ warmup_source != NULL) {
738
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
739
+ Isolate *isolate = snapshot_creator.GetIsolate();
740
+ {
741
+ HandleScope scope(isolate);
742
+ Local<Context> context = Context::New(isolate);
743
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
744
+ return result;
745
+ }
746
+ }
747
+ {
748
+ HandleScope handle_scope(isolate);
749
+ isolate->ContextDisposedNotification(false);
750
+ Local<Context> context = Context::New(isolate);
751
+ snapshot_creator.SetDefaultContext(context);
752
+ }
753
+ result = snapshot_creator.CreateBlob(
754
+ SnapshotCreator::FunctionCodeHandling::kKeep);
755
+ }
756
+ return result;
757
+ }
758
+
662
759
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
663
760
  SnapshotInfo* snapshot_info;
664
761
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -677,7 +774,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
677
774
 
678
775
  init_v8();
679
776
 
680
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
777
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
681
778
 
682
779
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
683
780
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -708,7 +805,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
708
805
  init_v8();
709
806
 
710
807
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
711
- StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
808
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
712
809
 
713
810
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
714
811
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -887,8 +984,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
887
984
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
888
985
 
889
986
  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"));
987
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
988
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
892
989
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
893
990
  } else {
894
991
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -1009,6 +1106,8 @@ gvl_ruby_callback(void* data) {
1009
1106
  VALUE result;
1010
1107
  VALUE self;
1011
1108
  VALUE parent;
1109
+ ContextInfo* context_info;
1110
+
1012
1111
  {
1013
1112
  HandleScope scope(args->GetIsolate());
1014
1113
  Local<External> external = Local<External>::Cast(args->Data());
@@ -1021,7 +1120,6 @@ gvl_ruby_callback(void* data) {
1021
1120
  return NULL;
1022
1121
  }
1023
1122
 
1024
- ContextInfo* context_info;
1025
1123
  Data_Get_Struct(parent, ContextInfo, context_info);
1026
1124
 
1027
1125
  if (length > 0) {
@@ -1031,7 +1129,7 @@ gvl_ruby_callback(void* data) {
1031
1129
  for (int i = 0; i < length; i++) {
1032
1130
  Local<Value> value = ((*args)[i]).As<Value>();
1033
1131
  VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1034
- *context_info->context, value);
1132
+ *context_info->context, value);
1035
1133
  rb_ary_push(ruby_args, tmp);
1036
1134
  }
1037
1135
  }
@@ -1062,7 +1160,7 @@ gvl_ruby_callback(void* data) {
1062
1160
  }
1063
1161
  else {
1064
1162
  HandleScope scope(args->GetIsolate());
1065
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
1163
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1066
1164
  args->GetReturnValue().Set(v8_result);
1067
1165
  }
1068
1166
 
@@ -1119,8 +1217,10 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1119
1217
  Local<Context> context = context_info->context->Get(isolate);
1120
1218
  Context::Scope context_scope(context);
1121
1219
 
1122
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
1123
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
1220
+ Local<String> v8_str =
1221
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1222
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1223
+ .ToLocalChecked();
1124
1224
 
1125
1225
  // copy self so we can access from v8 external
1126
1226
  VALUE* self_copy;
@@ -1130,23 +1230,34 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1130
1230
  Local<Value> external = External::New(isolate, self_copy);
1131
1231
 
1132
1232
  if (parent_object == Qnil) {
1133
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1134
- } else {
1233
+ context->Global()->Set(
1234
+ v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1235
+ ->GetFunction(context)
1236
+ .ToLocalChecked());
1135
1237
 
1136
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1137
- NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
1238
+ } else {
1239
+ Local<String> eval =
1240
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1241
+ NewStringType::kNormal,
1242
+ (int)RSTRING_LEN(parent_object_eval))
1243
+ .ToLocalChecked();
1138
1244
 
1139
1245
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1140
1246
  if (parsed_script.IsEmpty()) {
1141
1247
  parse_error = true;
1142
1248
  } else {
1143
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1249
+ MaybeLocal<Value> maybe_value =
1250
+ parsed_script.ToLocalChecked()->Run(context);
1144
1251
  attach_error = true;
1145
1252
 
1146
1253
  if (!maybe_value.IsEmpty()) {
1147
1254
  Local<Value> value = maybe_value.ToLocalChecked();
1148
- if (value->IsObject()){
1149
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1255
+ if (value->IsObject()) {
1256
+ value.As<Object>()->Set(
1257
+ v8_str, FunctionTemplate::New(
1258
+ isolate, ruby_callback, external)
1259
+ ->GetFunction(context)
1260
+ .ToLocalChecked());
1150
1261
  attach_error = false;
1151
1262
  }
1152
1263
  }
@@ -1350,6 +1461,69 @@ rb_heap_stats(VALUE self) {
1350
1461
  return rval;
1351
1462
  }
1352
1463
 
1464
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1465
+ class FileOutputStream : public OutputStream {
1466
+ public:
1467
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1468
+
1469
+ virtual int GetChunkSize() {
1470
+ return 65536;
1471
+ }
1472
+
1473
+ virtual void EndOfStream() {}
1474
+
1475
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1476
+ const size_t len = static_cast<size_t>(size);
1477
+ size_t off = 0;
1478
+
1479
+ while (off < len && !feof(stream_) && !ferror(stream_))
1480
+ off += fwrite(data + off, 1, len - off, stream_);
1481
+
1482
+ return off == len ? kContinue : kAbort;
1483
+ }
1484
+
1485
+ private:
1486
+ FILE* stream_;
1487
+ };
1488
+
1489
+
1490
+ static VALUE
1491
+ rb_heap_snapshot(VALUE self, VALUE file) {
1492
+
1493
+ rb_io_t *fptr;
1494
+
1495
+ fptr = RFILE(file)->fptr;
1496
+
1497
+ if (!fptr) return Qfalse;
1498
+
1499
+ FILE* fp;
1500
+ fp = fdopen(fptr->fd, "w");
1501
+ if (fp == NULL) return Qfalse;
1502
+
1503
+
1504
+ ContextInfo* context_info;
1505
+ Data_Get_Struct(self, ContextInfo, context_info);
1506
+ Isolate* isolate;
1507
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1508
+
1509
+ if (!isolate) return Qfalse;
1510
+
1511
+ Locker lock(isolate);
1512
+ Isolate::Scope isolate_scope(isolate);
1513
+ HandleScope handle_scope(isolate);
1514
+
1515
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1516
+
1517
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1518
+
1519
+ FileOutputStream stream(fp);
1520
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1521
+
1522
+ const_cast<HeapSnapshot*>(snap)->Delete();
1523
+
1524
+ return Qtrue;
1525
+ }
1526
+
1353
1527
  static VALUE
1354
1528
  rb_context_stop(VALUE self) {
1355
1529
 
@@ -1494,7 +1668,7 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1494
1668
  return Qnil;
1495
1669
  }
1496
1670
  for(int i=0; i < fun_argc; i++) {
1497
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1671
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1498
1672
  }
1499
1673
  }
1500
1674
  #if RUBY_API_VERSION_MAJOR > 1
@@ -1558,6 +1732,8 @@ extern "C" {
1558
1732
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1559
1733
  rb_define_method(rb_cContext, "low_memory_notification", (VALUE(*)(...))&rb_context_low_memory_notification, 0);
1560
1734
  rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1735
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1736
+
1561
1737
  rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1562
1738
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1563
1739
  rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
@@ -178,6 +178,28 @@ module MiniRacer
178
178
  eval(File.read(filename))
179
179
  end
180
180
 
181
+ def write_heap_snapshot(file_or_io)
182
+ f = nil
183
+ implicit = false
184
+
185
+
186
+ if String === file_or_io
187
+ f = File.open(file_or_io, "w")
188
+ implicit = true
189
+ else
190
+ f = file_or_io
191
+ end
192
+
193
+ if !(File === f)
194
+ raise ArgumentError("file_or_io")
195
+ end
196
+
197
+ write_heap_snapshot_unsafe(f)
198
+
199
+ ensure
200
+ f.close if implicit
201
+ end
202
+
181
203
  def eval(str, options=nil)
182
204
  raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
183
205
 
@@ -2,6 +2,6 @@ module Sqreen
2
2
  module MiniRacer
3
3
  # part before qualifier is the number of the last upstream release
4
4
  # since we synced with it
5
- VERSION = "0.2.4.sqreen3"
5
+ VERSION = "0.2.5.0.1.beta1"
6
6
  end
7
7
  end
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.license = "MIT"
16
16
 
17
17
 
18
- REJECTS = %r{\A((benchmark|test|spec|features)/|bench\.rb|.+\.sh|Jenkinsfile)}
18
+ REJECTS = %r{\A((benchmark|test|spec|features|examples)/|bench\.rb|.+\.sh|Jenkinsfile)}
19
19
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(REJECTS) }
20
20
  spec.bindir = "exe"
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sq_mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4.sqreen3
4
+ version: 0.2.5.0.1.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-19 00:00:00.000000000 Z
11
+ date: 2019-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler