mini_racer-csim 0.21.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'shared'
4
+
5
+ module MiniRacer
6
+ # GraalJS has no equivalent of V8's per-script bytecode cache reachable
7
+ # from Polyglot::InnerContext#eval, so the version tag is meaningless
8
+ # here. Define 0 as a sentinel callers can detect to skip cache logic.
9
+ V8_CACHED_DATA_VERSION_TAG = 0
10
+
11
+ class Context
12
+
13
+ class ExternalFunction
14
+ private
15
+
16
+ def notify_v8
17
+ name = @name.encode(::Encoding::UTF_8)
18
+ wrapped = lambda do |*args|
19
+ converted = @parent.send(:convert_js_to_ruby, args)
20
+ begin
21
+ result = @callback.call(*converted)
22
+ rescue Polyglot::ForeignException => e
23
+ e = RuntimeError.new(e.message)
24
+ e.set_backtrace(e.backtrace)
25
+ @parent.instance_variable_set(:@current_exception, e)
26
+ raise e
27
+ rescue => e
28
+ @parent.instance_variable_set(:@current_exception, e)
29
+ raise e
30
+ end
31
+ @parent.send(:convert_ruby_to_js, result)
32
+ end
33
+
34
+ if @parent_object.nil?
35
+ # set global name to proc
36
+ result = @parent.eval_in_context('this')
37
+ result[name] = wrapped
38
+ else
39
+ parent_object_eval = @parent_object_eval.encode(::Encoding::UTF_8)
40
+ begin
41
+ result = @parent.eval_in_context(parent_object_eval)
42
+ rescue Polyglot::ForeignException, StandardError => e
43
+ raise ParseError, "Was expecting #{@parent_object} to be an object", e.backtrace
44
+ end
45
+ result[name] = wrapped
46
+ # set evaluated object results name to proc
47
+ end
48
+ end
49
+ end
50
+
51
+ def heap_stats
52
+ raise ContextDisposedError if @disposed
53
+ {
54
+ total_physical_size: 0,
55
+ total_heap_size_executable: 0,
56
+ total_heap_size: 0,
57
+ used_heap_size: 0,
58
+ heap_size_limit: 0,
59
+ }
60
+ end
61
+
62
+ def stop
63
+ if @entered
64
+ @context.stop
65
+ @stopped = true
66
+ stop_attached
67
+ end
68
+ end
69
+
70
+ def low_memory_notification
71
+ GC.start
72
+ end
73
+
74
+ private
75
+
76
+ @context_initialized = false
77
+ @use_strict = false
78
+
79
+ def init_unsafe(isolate, snapshot)
80
+ unless defined?(Polyglot::InnerContext)
81
+ raise "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version"
82
+ end
83
+
84
+
85
+ if TruffleRuby.native?
86
+ raise "You need the TruffleRuby JVM Standalone for mini_racer because it is not possible to install the js component in the the Native standalone"
87
+ end
88
+
89
+ unless Polyglot.languages.include? "js"
90
+ raise "The language 'js' is not available, you need to install the 'js' component.\nSee https://github.com/oracle/truffleruby/blob/master/doc/user/polyglot.md#installing-other-languages"
91
+ end
92
+
93
+ @context = Polyglot::InnerContext.new(on_cancelled: -> {
94
+ raise ScriptTerminatedError, 'JavaScript was terminated (either by timeout or explicitly)'
95
+ })
96
+ Context.instance_variable_set(:@context_initialized, true)
97
+ @js_object = @context.eval('js', 'Object')
98
+ @isolate_mutex = Mutex.new
99
+ @stopped = false
100
+ @entered = false
101
+ @has_entered = false
102
+ @current_exception = nil
103
+ if isolate && snapshot
104
+ isolate.instance_variable_set(:@snapshot, snapshot)
105
+ end
106
+ if snapshot
107
+ @snapshot = snapshot
108
+ elsif isolate
109
+ @snapshot = isolate.instance_variable_get(:@snapshot)
110
+ else
111
+ @snapshot = nil
112
+ end
113
+
114
+ @is_object_or_array_func = eval_in_context "(x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) }"
115
+ @is_map_func = eval_in_context "(x) => { return x instanceof Map }"
116
+ @is_map_iterator_func = eval_in_context "(x) => { return x[Symbol.toStringTag] === 'Map Iterator' }"
117
+ @is_time_func = eval_in_context "(x) => { return x instanceof Date }"
118
+ @is_symbol_func = eval_in_context "(x) => { return typeof x === 'symbol' }"
119
+ @is_uint8_array_func = eval_in_context "(x) => { return x instanceof Uint8Array }"
120
+
121
+ @js_date_to_time_func = eval_in_context "(x) => { return x.getTime(x) }"
122
+ @js_symbol_to_symbol_func = eval_in_context "(x) => { var r = x.description; return r === undefined ? 'undefined' : r }"
123
+ @js_new_date_func = eval_in_context "(x) => { return new Date(x) }"
124
+ @js_new_array_func = eval_in_context "(x) => { return new Array(x) }"
125
+ @js_new_uint8array_func = eval_in_context "(x) => { return new Uint8Array(x) }"
126
+ end
127
+
128
+ def dispose_unsafe
129
+ @context.close
130
+ end
131
+
132
+ def eval_unsafe(str, filename)
133
+ @entered = true
134
+ if !@has_entered && @snapshot
135
+ snapshot_src = encode(@snapshot.instance_variable_get(:@source))
136
+ begin
137
+ eval_in_context(snapshot_src, filename)
138
+ rescue Polyglot::ForeignException => e
139
+ raise RuntimeError, e.message, e.backtrace
140
+ end
141
+ end
142
+ @has_entered = true
143
+ raise RuntimeError, "TruffleRuby does not support eval after stop" if @stopped
144
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
145
+ raise TypeError, "wrong type argument #{filename.class} (should be a string)" unless filename.nil? || filename.is_a?(String)
146
+
147
+ str = encode(str)
148
+ begin
149
+ translate do
150
+ eval_in_context(str, filename)
151
+ end
152
+ rescue Polyglot::ForeignException => e
153
+ raise RuntimeError, e.message, e.backtrace
154
+ rescue ::RuntimeError => e
155
+ if @current_exception
156
+ e = @current_exception
157
+ @current_exception = nil
158
+ raise e
159
+ else
160
+ raise e, e.message
161
+ end
162
+ end
163
+ ensure
164
+ @entered = false
165
+ end
166
+
167
+ def call_unsafe(function_name, *arguments)
168
+ @entered = true
169
+ if !@has_entered && @snapshot
170
+ src = encode(@snapshot.instance_variable_get(:source))
171
+ begin
172
+ eval_in_context(src)
173
+ rescue Polyglot::ForeignException => e
174
+ raise RuntimeError, e.message, e.backtrace
175
+ end
176
+ end
177
+ @has_entered = true
178
+ raise RuntimeError, "TruffleRuby does not support call after stop" if @stopped
179
+ begin
180
+ translate do
181
+ function = eval_in_context(function_name)
182
+ function.call(*convert_ruby_to_js(arguments))
183
+ end
184
+ rescue Polyglot::ForeignException => e
185
+ raise RuntimeError, e.message, e.backtrace
186
+ end
187
+ ensure
188
+ @entered = false
189
+ end
190
+
191
+ def create_isolate_value
192
+ # Returning a dummy object since TruffleRuby does not have a 1-1 concept with isolate.
193
+ # However, code and ASTs are shared between contexts.
194
+ Isolate.new
195
+ end
196
+
197
+ def isolate_mutex
198
+ @isolate_mutex
199
+ end
200
+
201
+ def translate
202
+ convert_js_to_ruby yield
203
+ rescue Object => e
204
+ message = e.message
205
+ if @current_exception
206
+ raise @current_exception
207
+ elsif e.message && e.message.start_with?('SyntaxError:')
208
+ error_class = MiniRacer::ParseError
209
+ elsif e.is_a?(MiniRacer::ScriptTerminatedError)
210
+ error_class = MiniRacer::ScriptTerminatedError
211
+ else
212
+ error_class = MiniRacer::RuntimeError
213
+ end
214
+
215
+ if error_class == MiniRacer::RuntimeError
216
+ bls = e.backtrace_locations&.select { |bl| bl&.source_location&.language == 'js' }
217
+ if bls && !bls.empty?
218
+ if '(eval)' != bls[0].path
219
+ message = "#{e.message}\n at #{bls[0]}\n" + bls[1..].map(&:to_s).join("\n")
220
+ else
221
+ message = "#{e.message}\n" + bls.map(&:to_s).join("\n")
222
+ end
223
+ end
224
+ raise error_class, message
225
+ else
226
+ raise error_class, message, e.backtrace
227
+ end
228
+ end
229
+
230
+ def convert_js_to_ruby(value)
231
+ case value
232
+ when true, false, Integer, Float
233
+ value
234
+ else
235
+ if value.nil?
236
+ nil
237
+ elsif value.respond_to?(:call)
238
+ MiniRacer::JavaScriptFunction.new
239
+ elsif value.respond_to?(:to_str)
240
+ value.to_str.dup
241
+ elsif value.respond_to?(:to_ary)
242
+ if uint8_array?(value)
243
+ return value.to_a.pack('C*')
244
+ end
245
+
246
+ value.to_ary.map do |e|
247
+ if e.respond_to?(:call)
248
+ nil
249
+ else
250
+ convert_js_to_ruby(e)
251
+ end
252
+ end
253
+ elsif time?(value)
254
+ js_date_to_time(value)
255
+ elsif symbol?(value)
256
+ js_symbol_to_symbol(value)
257
+ elsif map?(value)
258
+ js_map_to_hash(value)
259
+ elsif map_iterator?(value)
260
+ value.map { |e| convert_js_to_ruby(e) }
261
+ elsif Polyglot::ForeignException === value
262
+ exc = MiniRacer::ScriptError.new(value.message)
263
+ exc.set_backtrace(value.backtrace)
264
+ exc
265
+ else
266
+ object = value
267
+ h = {}
268
+ object.instance_variables.each do |member|
269
+ v = object[member]
270
+ unless v.respond_to?(:call)
271
+ h[member.to_s] = convert_js_to_ruby(v)
272
+ end
273
+ end
274
+ h
275
+ end
276
+ end
277
+ end
278
+
279
+ def object_or_array?(val)
280
+ @is_object_or_array_func.call(val)
281
+ end
282
+
283
+ def map?(value)
284
+ @is_map_func.call(value)
285
+ end
286
+
287
+ def map_iterator?(value)
288
+ @is_map_iterator_func.call(value)
289
+ end
290
+
291
+ def time?(value)
292
+ @is_time_func.call(value)
293
+ end
294
+
295
+ def symbol?(value)
296
+ @is_symbol_func.call(value)
297
+ end
298
+
299
+ def uint8_array?(value)
300
+ @is_uint8_array_func.call(value)
301
+ end
302
+
303
+ def js_date_to_time(value)
304
+ millis = @js_date_to_time_func.call(value)
305
+ Time.at(Rational(millis, 1000))
306
+ end
307
+
308
+ def js_symbol_to_symbol(value)
309
+ @js_symbol_to_symbol_func.call(value).to_s.to_sym
310
+ end
311
+
312
+ def js_map_to_hash(map)
313
+ map.to_a.to_h do |key, value|
314
+ [convert_js_to_ruby(key), convert_js_to_ruby(value)]
315
+ end
316
+ end
317
+
318
+ def js_new_date(value)
319
+ @js_new_date_func.call(value)
320
+ end
321
+
322
+ def js_new_array(size)
323
+ @js_new_array_func.call(size)
324
+ end
325
+
326
+ def convert_ruby_to_js(value)
327
+ case value
328
+ when nil, true, false, Integer, Float
329
+ value
330
+ when Array
331
+ ary = js_new_array(value.size)
332
+ value.each_with_index do |v, i|
333
+ ary[i] = convert_ruby_to_js(v)
334
+ end
335
+ ary
336
+ when Hash
337
+ h = @js_object.new
338
+ value.each_pair do |k, v|
339
+ h[convert_ruby_to_js(k.to_s)] = convert_ruby_to_js(v)
340
+ end
341
+ h
342
+ when String, Symbol
343
+ Truffle::Interop.as_truffle_string value
344
+ when Time
345
+ js_new_date(value.to_f * 1000)
346
+ when DateTime
347
+ js_new_date(value.to_time.to_f * 1000)
348
+ when MiniRacer::Binary
349
+ @js_new_uint8array_func.call(value.data.bytes)
350
+ else
351
+ "Undefined Conversion"
352
+ end
353
+ end
354
+
355
+ def encode(string)
356
+ raise ArgumentError unless string
357
+ string.encode(::Encoding::UTF_8)
358
+ end
359
+
360
+ class_eval <<-'RUBY', "(mini_racer)", 1
361
+ def eval_in_context(code, file = nil); code = ('"use strict";' + code) if Context.instance_variable_get(:@use_strict); @context.eval('js', code, file || '(mini_racer)'); end
362
+ RUBY
363
+
364
+ end
365
+
366
+ class Isolate
367
+ def init_with_snapshot(snapshot)
368
+ # TruffleRuby does not have a 1-1 concept with isolate.
369
+ # However, isolate can hold a snapshot, and code and ASTs are shared between contexts.
370
+ @snapshot = snapshot
371
+ end
372
+ end
373
+
374
+ class Platform
375
+ def self.set_flag_as_str!(flag)
376
+ raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
377
+ raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
378
+ Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
379
+ end
380
+ end
381
+
382
+ class Snapshot
383
+ def load(str)
384
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
385
+ # Intentionally noop since TruffleRuby mocks the snapshot API
386
+ end
387
+
388
+ def warmup_unsafe!(src)
389
+ raise TypeError, "wrong type argument #{src.class} (should be a string)" unless src.is_a?(String)
390
+ # Intentionally noop since TruffleRuby mocks the snapshot API
391
+ # by replaying snapshot source before the first eval/call
392
+ self
393
+ end
394
+ end
395
+
396
+ # GraalJS has no per-script bytecode cache reachable from
397
+ # Polyglot::InnerContext#eval, so cached_data: is silently ignored and
398
+ # Script#run replays the source through Context#eval. compile_module +
399
+ # dynamic_import_resolver raise NotImplementedError because GraalJS has
400
+ # its own module-loading mechanism that doesn't map onto this
401
+ # handle-based API. The MiniRacer::Module class itself is stubbed so
402
+ # cross-engine code can reference it for is_a? / rescue without
403
+ # tripping NameError.
404
+ class Context
405
+ def compile(source, filename: nil, cached_data: nil, produce_cache: false)
406
+ raise(ContextDisposedError, 'attempted to call compile on a disposed context!') if @disposed
407
+ raise TypeError, "wrong type argument #{source.class} (should be a string)" unless source.is_a?(String)
408
+ raise TypeError, "wrong type argument #{filename.class} (should be a string)" unless filename.nil? || filename.is_a?(String)
409
+ if cached_data
410
+ raise TypeError, "wrong type argument #{cached_data.class} (should be a string)" unless cached_data.is_a?(String)
411
+ raise EncodingError, "cached_data must be ASCII-8BIT (binary), got #{cached_data.encoding}" if cached_data.encoding != Encoding::ASCII_8BIT
412
+ end
413
+ # produce_cache is accepted for API parity but has no effect — the shim
414
+ # has no per-script bytecode cache to produce.
415
+ Script.send(:new, self, source, filename)
416
+ end
417
+
418
+ def compile_module(*_args, **_opts)
419
+ raise NotImplementedError,
420
+ 'Context#compile_module is not supported on TruffleRuby'
421
+ end
422
+
423
+ def load_module_graph(*_args, **_opts)
424
+ raise NotImplementedError,
425
+ 'Context#load_module_graph is not supported on TruffleRuby'
426
+ end
427
+
428
+ # nil is the documented "disable" value; accept it as a no-op so that
429
+ # `ctx.dynamic_import_resolver ||= ...` style code doesn't crash on
430
+ # TruffleRuby. Any callable raises, mirroring `compile_module`.
431
+ def dynamic_import_resolver=(blk)
432
+ return blk if blk.nil?
433
+ raise NotImplementedError,
434
+ 'Context#dynamic_import_resolver= is not supported on TruffleRuby'
435
+ end
436
+
437
+ def dynamic_import_resolver = nil
438
+ end
439
+
440
+ class Module
441
+ UNSUPPORTED = 'MiniRacer::Module is not supported on TruffleRuby'
442
+
443
+ def initialize(*) = raise(NotImplementedError, UNSUPPORTED)
444
+ def instantiate(*, &_blk) = raise(NotImplementedError, UNSUPPORTED)
445
+ def evaluate = raise(NotImplementedError, UNSUPPORTED)
446
+ def namespace = raise(NotImplementedError, UNSUPPORTED)
447
+ def status = raise(NotImplementedError, UNSUPPORTED)
448
+ def dispose = raise(NotImplementedError, UNSUPPORTED)
449
+ def disposed? = raise(NotImplementedError, UNSUPPORTED)
450
+ end
451
+
452
+ class Script
453
+ private_class_method :new
454
+
455
+ def initialize(ctx, source, filename)
456
+ @ctx = ctx
457
+ @source = source
458
+ @filename = filename
459
+ @disposed = false
460
+ end
461
+
462
+ def run
463
+ raise MiniRacer::RuntimeError, 'disposed script' if @disposed
464
+ @ctx.eval(@source, filename: @filename) # raises ContextDisposedError if @ctx is disposed
465
+ end
466
+
467
+ def cached_data; nil; end
468
+ def cache_rejected?; false; end
469
+
470
+ def dispose
471
+ @disposed = true
472
+ nil
473
+ end
474
+
475
+ def disposed?
476
+ @disposed
477
+ end
478
+ end
479
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniRacer
4
+ # mini_racer-csim fork: upstream version + a fork revision segment.
5
+ # 0.21.1.0 = first fork release on upstream 0.21.1; bump the 4th segment for
6
+ # fork-only changes, reset it when rebasing onto a new upstream version.
7
+ VERSION = "0.21.1.0"
8
+ LIBV8_NODE_VERSION = "~> 24.12.0.1"
9
+ end
@@ -0,0 +1,4 @@
1
+ # This gem is published as `mini_racer-csim`, but its implementation lives in
2
+ # `mini_racer` (it is a drop-in fork). This shim lets `require 'mini_racer-csim'`
3
+ # — e.g. Bundler's autorequire for the gem name — load the library.
4
+ require 'mini_racer'
data/lib/mini_racer.rb ADDED
@@ -0,0 +1,117 @@
1
+ require "mini_racer/version"
2
+ require "pathname"
3
+
4
+ module MiniRacer
5
+ class Binary
6
+ attr_reader :data
7
+
8
+ def initialize(data)
9
+ raise TypeError, "wrong argument type #{data.class} (expected String)" unless data.is_a?(String)
10
+ @data = data
11
+ end
12
+ end
13
+ end
14
+
15
+ if RUBY_ENGINE == "truffleruby"
16
+ require "mini_racer/truffleruby"
17
+ else
18
+ if ENV["LD_PRELOAD"].to_s.include?("malloc")
19
+ require "mini_racer_extension"
20
+ else
21
+ require "mini_racer_loader"
22
+ ext_filename = "mini_racer_extension.#{RbConfig::CONFIG["DLEXT"]}"
23
+ # This is the mini_racer-csim fork; fall back to the upstream name and then
24
+ # to the default require_paths so the extension is found however we're loaded.
25
+ spec = Gem.loaded_specs["mini_racer-csim"] || Gem.loaded_specs["mini_racer"]
26
+ ext_path =
27
+ (spec ? spec.require_paths : %w[lib ext]).map do |p|
28
+ (p = Pathname.new(p)).absolute? ? p : Pathname.new(__dir__).parent + p
29
+ end
30
+ ext_found = ext_path.map { |p| p + ext_filename }.find { |p| p.file? }
31
+
32
+ unless ext_found
33
+ raise LoadError,
34
+ "Could not find #{ext_filename} in #{ext_path.map(&:to_s)}"
35
+ end
36
+ MiniRacer::Loader.load(ext_found.to_s)
37
+ end
38
+ end
39
+
40
+ require "thread"
41
+ require "json"
42
+ require "io/wait"
43
+
44
+ module MiniRacer
45
+ class Error < ::StandardError; end
46
+
47
+ class ContextDisposedError < Error; end
48
+ class PlatformAlreadyInitialized < Error; end
49
+
50
+ class EvalError < Error; end
51
+ class ParseError < EvalError; end
52
+ class ScriptTerminatedError < EvalError; end
53
+ class V8OutOfMemoryError < EvalError; end
54
+
55
+ class RuntimeError < EvalError
56
+ def initialize(message)
57
+ message, *@frames = message.split("\n")
58
+ @frames.map! { "JavaScript #{_1.strip}" }
59
+ super(message)
60
+ end
61
+
62
+ def backtrace
63
+ frames = super
64
+ @frames + frames unless frames.nil?
65
+ end
66
+ end
67
+
68
+ class ScriptError < EvalError
69
+ def initialize(message)
70
+ message, *@frames = message.split("\n")
71
+ @frames.map! { "JavaScript #{_1.strip}" }
72
+ super(message)
73
+ end
74
+
75
+ def backtrace
76
+ frames = super || []
77
+ @frames + frames
78
+ end
79
+ end
80
+
81
+ class SnapshotError < Error
82
+ def initialize(message)
83
+ message, *@frames = message.split("\n")
84
+ @frames.map! { "JavaScript #{_1.strip}" }
85
+ super(message)
86
+ end
87
+
88
+ def backtrace
89
+ frames = super
90
+ @frames + frames unless frames.nil?
91
+ end
92
+ end
93
+
94
+ class Context
95
+ def load(filename)
96
+ eval(File.read(filename))
97
+ end
98
+
99
+ def write_heap_snapshot(file_or_io)
100
+ f = nil
101
+ implicit = false
102
+
103
+ if String === file_or_io
104
+ f = File.open(file_or_io, "w")
105
+ implicit = true
106
+ else
107
+ f = file_or_io
108
+ end
109
+
110
+ raise ArgumentError, "file_or_io" unless File === f
111
+
112
+ f.write(heap_snapshot())
113
+ ensure
114
+ f.close if implicit
115
+ end
116
+ end
117
+ end