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,13 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_ENGINE == "truffleruby"
4
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
5
+ return
6
+ end
7
+
8
+ extension_name = 'mini_racer_loader'
9
+ dir_config extension_name
10
+
11
+ $CXXFLAGS += " -fvisibility=hidden "
12
+
13
+ create_makefile extension_name
@@ -0,0 +1,123 @@
1
+ #include <ruby.h>
2
+ #include <dlfcn.h>
3
+ #include <string.h>
4
+ #include <stdint.h>
5
+ #include <stdlib.h>
6
+
7
+ // Load a Ruby extension like Ruby does, only with flags that:
8
+ // a) hide symbols from other extensions (RTLD_LOCAL)
9
+ // b) bind symbols tightly (RTLD_DEEPBIND, when available)
10
+
11
+ void Init_mini_racer_loader(void);
12
+
13
+ static void *_dln_load(const char *file);
14
+
15
+ static VALUE _load_shared_lib(VALUE self, volatile VALUE fname)
16
+ {
17
+ (void) self;
18
+
19
+ // check that path is not tainted
20
+ SafeStringValue(fname);
21
+
22
+ FilePathValue(fname);
23
+ VALUE path = rb_str_encode_ospath(fname);
24
+
25
+ char *loc = StringValueCStr(path);
26
+ void *handle = _dln_load(loc);
27
+
28
+ return handle ? Qtrue : Qfalse;
29
+ }
30
+
31
+ // adapted from Ruby's dln.c
32
+ #define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'})
33
+ #define INIT_FUNCNAME(buf, file) do { \
34
+ const char *base = (file); \
35
+ const size_t flen = _init_funcname(&base); \
36
+ const size_t plen = sizeof(INIT_FUNC_PREFIX); \
37
+ char *const tmp = ALLOCA_N(char, plen + flen + 1); \
38
+ memcpy(tmp, INIT_FUNC_PREFIX, plen); \
39
+ memcpy(tmp+plen, base, flen); \
40
+ tmp[plen+flen] = '\0'; \
41
+ *(buf) = tmp; \
42
+ } while(0)
43
+
44
+ // adapted from Ruby's dln.c
45
+ static size_t _init_funcname(const char **file)
46
+ {
47
+ const char *p = *file,
48
+ *base,
49
+ *dot = NULL;
50
+
51
+ for (base = p; *p; p++) { /* Find position of last '/' */
52
+ if (*p == '.' && !dot) {
53
+ dot = p;
54
+ }
55
+ if (*p == '/') {
56
+ base = p + 1;
57
+ dot = NULL;
58
+ }
59
+ }
60
+ *file = base;
61
+ return (uintptr_t) ((dot ? dot : p) - base);
62
+ }
63
+
64
+ // adapted from Ruby's dln.c
65
+ static void *_dln_load(const char *file)
66
+ {
67
+ char *buf;
68
+ const char *error;
69
+ #define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error))
70
+
71
+ void *handle;
72
+ void (*init_fct)(void);
73
+
74
+ INIT_FUNCNAME(&buf, file);
75
+
76
+ #ifndef RTLD_DEEPBIND
77
+ # define RTLD_DEEPBIND 0
78
+ #endif
79
+ /* Load file */
80
+ if ((handle = dlopen(file, RTLD_LAZY|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) {
81
+ DLN_ERROR();
82
+ goto failed;
83
+ }
84
+ #if defined(RUBY_EXPORT)
85
+ {
86
+ static const char incompatible[] = "incompatible library version";
87
+ void *ex = dlsym(handle, "ruby_xmalloc");
88
+ if (ex && ex != (void *) &ruby_xmalloc) {
89
+
90
+ # if defined __APPLE__
91
+ /* dlclose() segfaults */
92
+ rb_fatal("%s - %s", incompatible, file);
93
+ # else
94
+ dlclose(handle);
95
+ error = incompatible;
96
+ goto failed;
97
+ #endif
98
+ }
99
+ }
100
+ # endif
101
+
102
+ init_fct = (void (*)(void)) dlsym(handle, buf);
103
+ if (init_fct == NULL) {
104
+ error = DLN_ERROR();
105
+ dlclose(handle);
106
+ goto failed;
107
+ }
108
+
109
+ /* Call the init code */
110
+ (*init_fct)();
111
+
112
+ return handle;
113
+
114
+ failed:
115
+ rb_raise(rb_eLoadError, "%s", error);
116
+ }
117
+
118
+ __attribute__((visibility("default"))) void Init_mini_racer_loader(void)
119
+ {
120
+ VALUE mMiniRacer = rb_define_module("MiniRacer");
121
+ VALUE mLoader = rb_define_module_under(mMiniRacer, "Loader");
122
+ rb_define_singleton_method(mLoader, "load", _load_shared_lib, 1);
123
+ }
@@ -0,0 +1,395 @@
1
+ # This code used to be shared in lib/mini_racer.rb
2
+ # but was moved to the extension with https://github.com/rubyjs/mini_racer/pull/325.
3
+ # So now this is effectively duplicate logic with C/C++ code.
4
+ # Maybe one day it can be actually shared again between both backends.
5
+
6
+ module MiniRacer
7
+
8
+ MARSHAL_STACKDEPTH_DEFAULT = 2**9-2
9
+ MARSHAL_STACKDEPTH_MAX_VALUE = 2**10-2
10
+
11
+ class FailedV8Conversion
12
+ attr_reader :info
13
+ def initialize(info)
14
+ @info = info
15
+ end
16
+ end
17
+
18
+ # helper class returned when we have a JavaScript function
19
+ class JavaScriptFunction
20
+ def to_s
21
+ "JavaScript Function"
22
+ end
23
+ end
24
+
25
+ class Isolate
26
+ def initialize(snapshot = nil)
27
+ unless snapshot.nil? || snapshot.is_a?(Snapshot)
28
+ raise ArgumentError, "snapshot must be a Snapshot object, passed a #{snapshot.inspect}"
29
+ end
30
+
31
+ # defined in the C class
32
+ init_with_snapshot(snapshot)
33
+ end
34
+ end
35
+
36
+ class Platform
37
+ class << self
38
+ def set_flags!(*args, **kwargs)
39
+ flags_to_strings([args, kwargs]).each do |flag|
40
+ # defined in the C class
41
+ set_flag_as_str!(flag)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def flags_to_strings(flags)
48
+ flags.flatten.map { |flag| flag_to_string(flag) }.flatten
49
+ end
50
+
51
+ # normalize flags to strings, and adds leading dashes if needed
52
+ def flag_to_string(flag)
53
+ if flag.is_a?(Hash)
54
+ flag.map do |key, value|
55
+ "#{flag_to_string(key)} #{value}"
56
+ end
57
+ else
58
+ str = flag.to_s
59
+ str = "--#{str}" unless str.start_with?('--')
60
+ str
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ # eval is defined in the C class
67
+ class Context
68
+
69
+ class ExternalFunction
70
+ def initialize(name, callback, parent)
71
+ unless String === name
72
+ raise ArgumentError, "parent_object must be a String"
73
+ end
74
+ parent_object, _ , @name = name.rpartition(".")
75
+ @callback = callback
76
+ @parent = parent
77
+ @parent_object_eval = nil
78
+ @parent_object = nil
79
+
80
+ unless parent_object.empty?
81
+ @parent_object = parent_object
82
+
83
+ @parent_object_eval = ""
84
+ prev = ""
85
+ first = true
86
+ parent_object.split(".").each do |obj|
87
+ prev << obj
88
+ if first
89
+ @parent_object_eval << "if (typeof #{prev} !== 'object' || typeof #{prev} !== 'function') { #{prev} = {} };\n"
90
+ else
91
+ @parent_object_eval << "#{prev} = #{prev} || {};\n"
92
+ end
93
+ prev << "."
94
+ first = false
95
+ end
96
+ @parent_object_eval << "#{parent_object};"
97
+ end
98
+ notify_v8
99
+ end
100
+ end
101
+
102
+ def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil, marshal_stack_depth: nil, host_namespace: nil)
103
+ options ||= {}
104
+
105
+ check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, marshal_stack_depth: marshal_stack_depth, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout, host_namespace: host_namespace)
106
+
107
+ @functions = {}
108
+ @timeout = nil
109
+ @max_memory = nil
110
+ @current_exception = nil
111
+ @timeout = timeout
112
+ @max_memory = max_memory
113
+ @marshal_stack_depth = marshal_stack_depth
114
+
115
+ # false signals it should be fetched if requested
116
+ @isolate = isolate || false
117
+
118
+ @ensure_gc_after_idle = ensure_gc_after_idle
119
+
120
+ if @ensure_gc_after_idle
121
+ @last_eval = nil
122
+ @ensure_gc_thread = nil
123
+ @ensure_gc_mutex = Mutex.new
124
+ end
125
+
126
+ @disposed = false
127
+
128
+ @callback_mutex = Mutex.new
129
+ @callback_running = false
130
+ @thread_raise_called = false
131
+ @eval_thread = nil
132
+
133
+ # defined in the C class
134
+ init_unsafe(isolate, snapshot)
135
+ end
136
+
137
+ def isolate
138
+ return @isolate if @isolate != false
139
+ # defined in the C class
140
+ @isolate = create_isolate_value
141
+ end
142
+
143
+ def eval(str, options=nil)
144
+ raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
145
+
146
+ filename = options && options[:filename].to_s
147
+
148
+ @eval_thread = Thread.current
149
+ isolate_mutex.synchronize do
150
+ @current_exception = nil
151
+ timeout do
152
+ eval_unsafe(str, filename)
153
+ end
154
+ end
155
+ ensure
156
+ @eval_thread = nil
157
+ ensure_gc_thread if @ensure_gc_after_idle
158
+ end
159
+
160
+ def call(function_name, *arguments)
161
+ raise(ContextDisposedError, 'attempted to call function on a disposed context!') if @disposed
162
+
163
+ @eval_thread = Thread.current
164
+ isolate_mutex.synchronize do
165
+ timeout do
166
+ call_unsafe(function_name, *arguments)
167
+ end
168
+ end
169
+ ensure
170
+ @eval_thread = nil
171
+ ensure_gc_thread if @ensure_gc_after_idle
172
+ end
173
+
174
+ def dispose
175
+ return if @disposed
176
+ isolate_mutex.synchronize do
177
+ return if @disposed
178
+ dispose_unsafe
179
+ @disposed = true
180
+ @isolate = nil # allow it to be garbage collected, if set
181
+ end
182
+ end
183
+
184
+
185
+ def attach(name, callback)
186
+ raise(ContextDisposedError, 'attempted to call function on a disposed context!') if @disposed
187
+
188
+ wrapped = lambda do |*args|
189
+ begin
190
+
191
+ r = nil
192
+
193
+ begin
194
+ @callback_mutex.synchronize{
195
+ @callback_running = true
196
+ }
197
+ r = callback.call(*args)
198
+ ensure
199
+ @callback_mutex.synchronize{
200
+ @callback_running = false
201
+ }
202
+ end
203
+
204
+ # wait up to 2 seconds for this to be interrupted
205
+ # will very rarely be called cause #raise is called
206
+ # in another mutex
207
+ @callback_mutex.synchronize {
208
+ if @thread_raise_called
209
+ sleep 2
210
+ end
211
+ }
212
+
213
+ r
214
+
215
+ ensure
216
+ @callback_mutex.synchronize {
217
+ @thread_raise_called = false
218
+ }
219
+ end
220
+ end
221
+
222
+ isolate_mutex.synchronize do
223
+ external = ExternalFunction.new(name, wrapped, self)
224
+ @functions["#{name}"] = external
225
+ end
226
+ end
227
+
228
+ # reset_realm tears down the JavaScript realm while keeping the warm
229
+ # isolate. It depends on V8's isolate/realm split, which the TruffleRuby
230
+ # (Graal.js) backend does not expose, so it is unavailable here.
231
+ def reset_realm
232
+ raise NotImplementedError, "reset_realm is only available on the V8 backend"
233
+ end
234
+
235
+ private
236
+
237
+ def ensure_gc_thread
238
+ @last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
239
+ @ensure_gc_mutex.synchronize do
240
+ @ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
241
+ return if !Thread.main.alive? # avoid "can't alloc thread" exception
242
+ @ensure_gc_thread ||= Thread.new do
243
+ ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
244
+ done = false
245
+ while !done
246
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
247
+
248
+ if @disposed
249
+ @ensure_gc_thread = nil
250
+ break
251
+ end
252
+
253
+ if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
254
+ @ensure_gc_mutex.synchronize do
255
+ isolate_mutex.synchronize do
256
+ if !@eval_thread
257
+ low_memory_notification if !@disposed
258
+ @ensure_gc_thread = nil
259
+ done = true
260
+ end
261
+ end
262
+ end
263
+ end
264
+ sleep ensure_gc_after_idle_seconds if !done
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ def stop_attached
271
+ @callback_mutex.synchronize{
272
+ if @callback_running
273
+ @eval_thread.raise ScriptTerminatedError, "Terminated during callback"
274
+ @thread_raise_called = true
275
+ end
276
+ }
277
+ end
278
+
279
+ def timeout(&blk)
280
+ return blk.call unless @timeout
281
+
282
+ mutex = Mutex.new
283
+ done = false
284
+
285
+ rp,wp = IO.pipe
286
+
287
+ t = Thread.new do
288
+ begin
289
+ result = rp.wait_readable(@timeout/1000.0)
290
+ if !result
291
+ mutex.synchronize do
292
+ stop unless done
293
+ end
294
+ end
295
+ rescue => e
296
+ STDERR.puts e
297
+ STDERR.puts "FAILED TO TERMINATE DUE TO TIMEOUT"
298
+ end
299
+ end
300
+
301
+ rval = blk.call
302
+ mutex.synchronize do
303
+ done = true
304
+ end
305
+
306
+ wp.close
307
+
308
+ # ensure we do not leak a thread in state
309
+ t.join
310
+ t = nil
311
+
312
+ rval
313
+ ensure
314
+ # exceptions need to be handled
315
+ wp&.close
316
+ t&.join
317
+ rp&.close
318
+ end
319
+
320
+ def check_init_options!(isolate:, snapshot:, max_memory:, marshal_stack_depth:, ensure_gc_after_idle:, timeout:, host_namespace:)
321
+ assert_option_is_nil_or_a('isolate', isolate, Isolate)
322
+ assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
323
+
324
+ assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000, max_value: 2**32-1)
325
+ assert_numeric_or_nil('marshal_stack_depth', marshal_stack_depth, min_value: 1, max_value: MARSHAL_STACKDEPTH_MAX_VALUE)
326
+ assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
327
+ assert_numeric_or_nil('timeout', timeout, min_value: 1)
328
+
329
+ unless host_namespace.nil? || host_namespace == true || host_namespace == false || host_namespace.is_a?(String)
330
+ raise ArgumentError, "host_namespace must be a String, true, false, or nil, passed a #{host_namespace.inspect}"
331
+ end
332
+
333
+ if host_namespace.is_a?(String) && !host_namespace.empty? && !host_namespace.match?(/\A[A-Za-z_$][A-Za-z0-9_$]*\z/)
334
+ raise ArgumentError, "host_namespace must be a valid identifier, passed #{host_namespace.inspect}"
335
+ end
336
+
337
+ if isolate && snapshot
338
+ raise ArgumentError, 'can only pass one of isolate and snapshot options'
339
+ end
340
+ end
341
+
342
+ def assert_numeric_or_nil(option_name, object, min_value:, max_value: nil)
343
+ if max_value && object.is_a?(Numeric) && object > max_value
344
+ raise ArgumentError, "#{option_name} must be less than or equal to #{max_value}"
345
+ end
346
+
347
+ if object.is_a?(Numeric) && object < min_value
348
+ raise ArgumentError, "#{option_name} must be larger than or equal to #{min_value}"
349
+ end
350
+
351
+ if !object.nil? && !object.is_a?(Numeric)
352
+ raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
353
+ end
354
+ end
355
+
356
+ def assert_option_is_nil_or_a(option_name, object, klass)
357
+ unless object.nil? || object.is_a?(klass)
358
+ raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
359
+ end
360
+ end
361
+ end
362
+
363
+ # `size` and `warmup!` public methods are defined in the C class
364
+ class Snapshot
365
+ def initialize(str = '')
366
+ # ensure it first can load
367
+ begin
368
+ ctx = MiniRacer::Context.new
369
+ ctx.eval(str)
370
+ rescue MiniRacer::RuntimeError => e
371
+ raise MiniRacer::SnapshotError, e.message, e.backtrace
372
+ end
373
+
374
+ @source = str
375
+
376
+ # defined in the C class
377
+ load(str)
378
+ end
379
+
380
+ def warmup!(src)
381
+ # we have to do something here
382
+ # we are bloating memory a bit but it is more correct
383
+ # than hitting an exception when attempty to compile invalid source
384
+ begin
385
+ ctx = MiniRacer::Context.new
386
+ ctx.eval(@source)
387
+ ctx.eval(src)
388
+ rescue MiniRacer::RuntimeError => e
389
+ raise MiniRacer::SnapshotError, e.message, e.backtrace
390
+ end
391
+
392
+ warmup_unsafe!(src)
393
+ end
394
+ end
395
+ end