mini_racer 0.19.0 → 0.19.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5923ad9b3aa9cdea7785f0f2eb3fa27357217d0feb5ed439dad46b5ea7eaea8f
4
- data.tar.gz: ffac8a1b740c74467f40e0200730a3fe25892d63105022f8775fe8aaffdcdd20
3
+ metadata.gz: 2730baed1d90aa6181def75b81766dca2481c2ce7406f430436ba524e0dc70d6
4
+ data.tar.gz: d1aa0298af002dfe183acee282718a3e2702c9bfb98cb3b80bc6fc93e5a711b3
5
5
  SHA512:
6
- metadata.gz: 0f8ab9852328121e3d55119fb1a2a8a875806b0a256d2c79ab09846fe2ed7df174beb68949213057fe8186376b73993b21dc21fe22ce82632f26e0a4354c982e
7
- data.tar.gz: 88c0a37a5a26ab746312c76499a202578a0604098a1ec3fcf7b98110aace6facd95490b19bd0f6abb8afba136b9ae94f5f1367d55050d6abdcf62bd628d6b8f2
6
+ metadata.gz: 8050de8f1506ed9a85f0e5109089dae8e7413b6eee5902636ab922a7ccd819a3ae3a7aea8b4f0465f3c795afab03e4c3e2c9dccc77325b848304008d6bad4091
7
+ data.tar.gz: 3c1e532d346b607278235f837eb5bc35d88c82d3e2e088fc3b833344293c94f02dc5b0303edd7d59b4ee8aa86ccfb20497ef86a777e10892f02f2bd10bd710a6
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ - 0.19.2 - 24-12-2025
2
+ - upgrade to node 24.12.0
3
+
4
+ - 0.19.1 - 20-10-2025
5
+ - JS code can now catch ruby exceptions - Ben Noordhuis
6
+ - Retain string encoding when raising exceptions - Ben Noordhuis
7
+ - Fix object identity bug with Ruby to JS conversion - Benjamin Wood
8
+
1
9
  - 0.19.0 - 24-06-2025
2
10
  - upgrade to node 24.1.0
3
11
 
@@ -6,6 +6,30 @@
6
6
  #include <pthread.h>
7
7
  #include <math.h>
8
8
 
9
+ #if defined(__linux__) && !defined(__GLIBC__)
10
+ // musl compatibility for glibc-linked libraries (e.g. libv8-node)
11
+ // some versions of libv8-node are accidentally linked against glibc symbols
12
+ // or compiled with a toolchain that emits these C23 compatibility symbols
13
+ unsigned long long __isoc23_strtoull(const char *nptr, char **endptr, int base) {
14
+ return strtoull(nptr, endptr, base);
15
+ }
16
+ unsigned long __isoc23_strtoul(const char *nptr, char **endptr, int base) {
17
+ return strtoul(nptr, endptr, base);
18
+ }
19
+ long long __isoc23_strtoll(const char *nptr, char **endptr, int base) {
20
+ return strtoll(nptr, endptr, base);
21
+ }
22
+ long __isoc23_strtol(const char *nptr, char **endptr, int base) {
23
+ return strtol(nptr, endptr, base);
24
+ }
25
+ double __isoc23_strtod(const char *nptr, char **endptr) {
26
+ return strtod(nptr, endptr);
27
+ }
28
+ float __isoc23_strtof(const char *nptr, char **endptr) {
29
+ return strtof(nptr, endptr);
30
+ }
31
+ #endif
32
+
9
33
  #include "ruby.h"
10
34
  #include "ruby/encoding.h"
11
35
  #include "ruby/version.h"
@@ -13,6 +37,13 @@
13
37
  #include "serde.c"
14
38
  #include "mini_racer_v8.h"
15
39
 
40
+ // for debugging
41
+ #define RB_PUTS(v) \
42
+ do { \
43
+ fflush(stdout); \
44
+ rb_funcall(rb_mKernel, rb_intern("puts"), 1, v); \
45
+ } while (0)
46
+
16
47
  #if RUBY_API_VERSION_CODE < 3*10000+4*100 // 3.4.0
17
48
  static inline void rb_thread_lock_native_thread(void)
18
49
  {
@@ -154,6 +185,7 @@ static VALUE platform_init_error;
154
185
  static VALUE context_disposed_error;
155
186
  static VALUE parse_error;
156
187
  static VALUE memory_error;
188
+ static VALUE script_error;
157
189
  static VALUE runtime_error;
158
190
  static VALUE internal_error;
159
191
  static VALUE snapshot_error;
@@ -482,12 +514,36 @@ static void des_object_ref(void *arg, uint32_t id)
482
514
 
483
515
  static void des_error_begin(void *arg)
484
516
  {
485
- push(arg, rb_class_new_instance(0, NULL, rb_eRuntimeError));
517
+ push(arg, rb_ary_new());
486
518
  }
487
519
 
488
520
  static void des_error_end(void *arg)
489
521
  {
490
- pop(arg);
522
+ VALUE *a, h, message, stack, cause, newline;
523
+ DesCtx *c;
524
+
525
+ c = arg;
526
+ if (*c->err)
527
+ return;
528
+ if (c->tos == c->stack) {
529
+ snprintf(c->err, sizeof(c->err), "stack underflow");
530
+ return;
531
+ }
532
+ a = &c->tos->a;
533
+ h = rb_ary_pop(*a);
534
+ message = rb_hash_aref(h, rb_str_new_cstr("message"));
535
+ stack = rb_hash_aref(h, rb_str_new_cstr("stack"));
536
+ cause = rb_hash_aref(h, rb_str_new_cstr("cause"));
537
+ if (NIL_P(message))
538
+ message = rb_str_new_cstr("JS exception");
539
+ if (!NIL_P(stack)) {
540
+ newline = rb_str_new_cstr("\n");
541
+ message = rb_funcall(message, rb_intern("concat"), 2, newline, stack);
542
+ }
543
+ *a = rb_class_new_instance(1, &message, script_error);
544
+ if (!NIL_P(cause))
545
+ rb_iv_set(*a, "@cause", cause);
546
+ pop(c);
491
547
  }
492
548
 
493
549
  static int collect(VALUE k, VALUE v, VALUE a)
@@ -527,52 +583,58 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
527
583
  return -1;
528
584
  switch (TYPE(v)) {
529
585
  case T_ARRAY:
530
- id = rb_hash_lookup(refs, v);
531
- if (NIL_P(id)) {
532
- n = RARRAY_LENINT(v);
533
- i = rb_hash_size_num(refs);
534
- rb_hash_aset(refs, v, LONG2FIX(i));
535
- ser_array_begin(s, n);
536
- for (i = 0; i < n; i++)
537
- if (serialize1(s, refs, rb_ary_entry(v, i)))
538
- return -1;
539
- ser_array_end(s, n);
540
- } else {
541
- ser_object_ref(s, FIX2LONG(id));
586
+ {
587
+ VALUE obj_id = rb_obj_id(v);
588
+ id = rb_hash_lookup(refs, obj_id);
589
+ if (NIL_P(id)) {
590
+ n = RARRAY_LENINT(v);
591
+ i = rb_hash_size_num(refs);
592
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
593
+ ser_array_begin(s, n);
594
+ for (i = 0; i < n; i++)
595
+ if (serialize1(s, refs, rb_ary_entry(v, i)))
596
+ return -1;
597
+ ser_array_end(s, n);
598
+ } else {
599
+ ser_object_ref(s, FIX2LONG(id));
600
+ }
542
601
  }
543
602
  break;
544
603
  case T_HASH:
545
- id = rb_hash_lookup(refs, v);
546
- if (NIL_P(id)) {
547
- a = rb_ary_new();
548
- i = rb_hash_size_num(refs);
549
- n = rb_hash_size_num(v);
550
- rb_hash_aset(refs, v, LONG2FIX(i));
551
- rb_hash_foreach(v, collect, a);
552
- for (i = 0; i < 2*n; i += 2) {
553
- t = rb_ary_entry(a, i);
554
- switch (TYPE(t)) {
555
- case T_FIXNUM:
556
- case T_STRING:
557
- case T_SYMBOL:
558
- continue;
559
- }
560
- break;
561
- }
562
- if (i == 2*n) {
563
- ser_object_begin(s);
604
+ {
605
+ VALUE obj_id = rb_obj_id(v);
606
+ id = rb_hash_lookup(refs, obj_id);
607
+ if (NIL_P(id)) {
608
+ a = rb_ary_new();
609
+ i = rb_hash_size_num(refs);
610
+ n = rb_hash_size_num(v);
611
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
612
+ rb_hash_foreach(v, collect, a);
564
613
  for (i = 0; i < 2*n; i += 2) {
565
- if (serialize1(s, refs, rb_ary_entry(a, i+0)))
566
- return -1;
567
- if (serialize1(s, refs, rb_ary_entry(a, i+1)))
568
- return -1;
614
+ t = rb_ary_entry(a, i);
615
+ switch (TYPE(t)) {
616
+ case T_FIXNUM:
617
+ case T_STRING:
618
+ case T_SYMBOL:
619
+ continue;
620
+ }
621
+ break;
622
+ }
623
+ if (i == 2*n) {
624
+ ser_object_begin(s);
625
+ for (i = 0; i < 2*n; i += 2) {
626
+ if (serialize1(s, refs, rb_ary_entry(a, i+0)))
627
+ return -1;
628
+ if (serialize1(s, refs, rb_ary_entry(a, i+1)))
629
+ return -1;
630
+ }
631
+ ser_object_end(s, n);
632
+ } else {
633
+ return bail(&s->err, "TODO serialize as Map");
569
634
  }
570
- ser_object_end(s, n);
571
635
  } else {
572
- return bail(&s->err, "TODO serialize as Map");
636
+ ser_object_ref(s, FIX2LONG(id));
573
637
  }
574
- } else {
575
- ser_object_ref(s, FIX2LONG(id));
576
638
  }
577
639
  break;
578
640
  case T_DATA:
@@ -888,6 +950,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
888
950
  static void *rendezvous_callback(void *arg)
889
951
  {
890
952
  struct rendezvous_nogvl *a;
953
+ const char *err;
891
954
  Context *c;
892
955
  int exc;
893
956
  VALUE r;
@@ -911,7 +974,12 @@ out:
911
974
  buf_move(&s.b, a->req);
912
975
  return NULL;
913
976
  fail:
914
- ser_init1(&s, 'e'); // exception pending
977
+ ser_init0(&s); // ruby exception pending
978
+ w_byte(&s, 'e'); // send ruby error message to v8 thread
979
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
980
+ err = StringValueCStr(r);
981
+ if (err)
982
+ w(&s, err, strlen(err));
915
983
  goto out;
916
984
  }
917
985
 
@@ -969,6 +1037,13 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
969
1037
  int exc;
970
1038
 
971
1039
  rendezvous_no_des(c, req, &res); // takes ownership of |req|
1040
+ r = c->exception;
1041
+ c->exception = Qnil;
1042
+ // if js land didn't handle exception from ruby callback, re-raise it now
1043
+ if (res.len == 1 && *res.buf == 'e') {
1044
+ assert(!NIL_P(r));
1045
+ rb_exc_raise(r);
1046
+ }
972
1047
  r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
973
1048
  buf_reset(&res);
974
1049
  if (exc) {
@@ -976,11 +1051,6 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
976
1051
  rb_set_errinfo(Qnil);
977
1052
  rb_exc_raise(r);
978
1053
  }
979
- if (!NIL_P(c->exception)) {
980
- r = c->exception;
981
- c->exception = Qnil;
982
- rb_exc_raise(r);
983
- }
984
1054
  return r;
985
1055
  }
986
1056
 
@@ -999,8 +1069,8 @@ static void handle_exception(VALUE e)
999
1069
 
1000
1070
  if (NIL_P(e))
1001
1071
  return;
1002
- StringValue(e);
1003
- s = RSTRING_PTR(e);
1072
+ e = StringValue(e);
1073
+ s = StringValueCStr(e);
1004
1074
  switch (*s) {
1005
1075
  case NO_ERROR:
1006
1076
  return;
@@ -1022,7 +1092,7 @@ static void handle_exception(VALUE e)
1022
1092
  default:
1023
1093
  rb_raise(internal_error, "bad error class %02x", *s);
1024
1094
  }
1025
- rb_raise(klass, "%s", s+1);
1095
+ rb_enc_raise(rb_enc_get(e), klass, "%s", s+1);
1026
1096
  }
1027
1097
 
1028
1098
  static VALUE context_alloc(VALUE klass)
@@ -1628,6 +1698,11 @@ static VALUE snapshot_size0(VALUE self)
1628
1698
  return LONG2FIX(RSTRING_LENINT(ss->blob));
1629
1699
  }
1630
1700
 
1701
+ static VALUE script_error_cause(VALUE self)
1702
+ {
1703
+ return rb_iv_get(self, "@cause");
1704
+ }
1705
+
1631
1706
  __attribute__((visibility("default")))
1632
1707
  void Init_mini_racer_extension(void)
1633
1708
  {
@@ -1642,10 +1717,13 @@ void Init_mini_racer_extension(void)
1642
1717
  c = rb_define_class_under(m, "EvalError", c);
1643
1718
  parse_error = rb_define_class_under(m, "ParseError", c);
1644
1719
  memory_error = rb_define_class_under(m, "V8OutOfMemoryError", c);
1720
+ script_error = rb_define_class_under(m, "ScriptError", c);
1645
1721
  runtime_error = rb_define_class_under(m, "RuntimeError", c);
1646
1722
  internal_error = rb_define_class_under(m, "InternalError", c);
1647
1723
  terminated_error = rb_define_class_under(m, "ScriptTerminatedError", c);
1648
1724
 
1725
+ rb_define_method(script_error, "cause", script_error_cause, 0);
1726
+
1649
1727
  c = context_class = rb_define_class_under(m, "Context", rb_cObject);
1650
1728
  rb_define_method(c, "initialize", context_initialize, -1);
1651
1729
  rb_define_method(c, "attach", context_attach, 2);
@@ -86,6 +86,7 @@ struct State
86
86
  v8::Persistent<v8::Context> persistent_context; // single-thread mode only
87
87
  v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
88
88
  v8::Persistent<v8::Function> persistent_safe_context_function; // single-thread mode only
89
+ v8::Persistent<v8::Value> ruby_exception;
89
90
  Context *ruby_context;
90
91
  int64_t max_memory;
91
92
  int err_reason;
@@ -122,6 +123,20 @@ struct Serialized
122
123
  }
123
124
  };
124
125
 
126
+ bool bubble_up_ruby_exception(State& st, v8::TryCatch *try_catch)
127
+ {
128
+ auto exception = try_catch->Exception();
129
+ if (exception.IsEmpty()) return false;
130
+ auto ruby_exception = v8::Local<v8::Value>::New(st.isolate, st.ruby_exception);
131
+ if (ruby_exception.IsEmpty()) return false;
132
+ if (!ruby_exception->SameValue(exception)) return false;
133
+ // signal that the ruby thread should reraise the exception
134
+ // that it caught earlier when executing a js->ruby callback
135
+ uint8_t c = 'e';
136
+ v8_reply(st.ruby_context, &c, 1);
137
+ return true;
138
+ }
139
+
125
140
  // throws JS exception on serialization error
126
141
  bool reply(State& st, v8::Local<v8::Value> v)
127
142
  {
@@ -388,8 +403,17 @@ void v8_api_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
388
403
  v8_roundtrip(st.ruby_context, &p, &n);
389
404
  if (*p == 'c') // callback reply
390
405
  break;
391
- if (*p == 'e') // ruby exception pending
392
- return st.isolate->TerminateExecution();
406
+ if (*p == 'e') { // ruby exception pending
407
+ v8::Local<v8::String> message;
408
+ auto type = v8::NewStringType::kNormal;
409
+ if (!v8::String::NewFromOneByte(st.isolate, p+1, type, n-1).ToLocal(&message)) {
410
+ message = v8::String::NewFromUtf8Literal(st.isolate, "Ruby exception");
411
+ }
412
+ auto exception = v8::Exception::Error(message);
413
+ st.ruby_exception.Reset(st.isolate, exception);
414
+ st.isolate->ThrowException(exception);
415
+ return;
416
+ }
393
417
  v8_dispatch(st.ruby_context);
394
418
  }
395
419
  v8::ValueDeserializer des(st.isolate, p+1, n-1);
@@ -523,6 +547,7 @@ fail:
523
547
  cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
524
548
  st.err_reason = NO_ERROR;
525
549
  }
550
+ if (bubble_up_ruby_exception(st, &try_catch)) return;
526
551
  if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
527
552
  if (cause) result = v8::Undefined(st.isolate);
528
553
  auto err = to_error(st, &try_catch, cause);
@@ -571,6 +596,7 @@ fail:
571
596
  cause = st.err_reason ? st.err_reason : TERMINATED_ERROR;
572
597
  st.err_reason = NO_ERROR;
573
598
  }
599
+ if (bubble_up_ruby_exception(st, &try_catch)) return;
574
600
  if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
575
601
  if (cause) result = v8::Undefined(st.isolate);
576
602
  auto err = to_error(st, &try_catch, cause);
@@ -895,6 +921,7 @@ State::~State()
895
921
  v8::Isolate::Scope isolate_scope(isolate);
896
922
  persistent_safe_context.Reset();
897
923
  persistent_context.Reset();
924
+ ruby_exception.Reset();
898
925
  }
899
926
  isolate->Dispose();
900
927
  for (Callback *cb : callbacks)
@@ -194,17 +194,21 @@ static inline int r_zigzag(const uint8_t **p, const uint8_t *pe, int64_t *r)
194
194
  return 0;
195
195
  }
196
196
 
197
- static inline void ser_init(Ser *s)
197
+ static void ser_init0(Ser *s)
198
198
  {
199
199
  memset(s, 0, sizeof(*s));
200
200
  buf_init(&s->b);
201
+ }
202
+
203
+ static inline void ser_init(Ser *s)
204
+ {
205
+ ser_init0(s);
201
206
  w(s, "\xFF\x0F", 2);
202
207
  }
203
208
 
204
209
  static void ser_init1(Ser *s, uint8_t c)
205
210
  {
206
- memset(s, 0, sizeof(*s));
207
- buf_init(&s->b);
211
+ ser_init0(s);
208
212
  w_byte(s, c);
209
213
  w(s, "\xFF\x0F", 2);
210
214
  }
@@ -246,6 +246,10 @@ module MiniRacer
246
246
  js_map_to_hash(value)
247
247
  elsif map_iterator?(value)
248
248
  value.map { |e| convert_js_to_ruby(e) }
249
+ elsif Polyglot::ForeignException === value
250
+ exc = MiniRacer::ScriptError.new(value.message)
251
+ exc.set_backtrace(value.backtrace)
252
+ exc
249
253
  else
250
254
  object = value
251
255
  h = {}
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.19.0"
5
- LIBV8_NODE_VERSION = "~> 24.1.0.0"
4
+ VERSION = "0.19.2"
5
+ LIBV8_NODE_VERSION = "~> 24.12.0.1"
6
6
  end
data/lib/mini_racer.rb CHANGED
@@ -51,6 +51,19 @@ module MiniRacer
51
51
  end
52
52
  end
53
53
 
54
+ class ScriptError < EvalError
55
+ def initialize(message)
56
+ message, *@frames = message.split("\n")
57
+ @frames.map! { "JavaScript #{_1.strip}" }
58
+ super(message)
59
+ end
60
+
61
+ def backtrace
62
+ frames = super || []
63
+ @frames + frames
64
+ end
65
+ end
66
+
54
67
  class SnapshotError < Error
55
68
  def initialize(message)
56
69
  message, *@frames = message.split("\n")
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.19.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-06-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -100,21 +99,21 @@ dependencies:
100
99
  requirements:
101
100
  - - "~>"
102
101
  - !ruby/object:Gem::Version
103
- version: 24.1.0.0
102
+ version: 24.12.0.1
104
103
  type: :runtime
105
104
  prerelease: false
106
105
  version_requirements: !ruby/object:Gem::Requirement
107
106
  requirements:
108
107
  - - "~>"
109
108
  - !ruby/object:Gem::Version
110
- version: 24.1.0.0
109
+ version: 24.12.0.1
111
110
  description: Minimal embedded v8 engine for Ruby
112
111
  email:
113
112
  - sam.saffron@gmail.com
114
113
  executables: []
115
114
  extensions:
116
- - ext/mini_racer_loader/extconf.rb
117
115
  - ext/mini_racer_extension/extconf.rb
116
+ - ext/mini_racer_loader/extconf.rb
118
117
  extra_rdoc_files: []
119
118
  files:
120
119
  - CHANGELOG
@@ -137,10 +136,9 @@ licenses:
137
136
  - MIT
138
137
  metadata:
139
138
  bug_tracker_uri: https://github.com/discourse/mini_racer/issues
140
- changelog_uri: https://github.com/discourse/mini_racer/blob/v0.19.0/CHANGELOG
141
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.19.0
142
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.19.0
143
- post_install_message:
139
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.19.2/CHANGELOG
140
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.19.2
141
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.19.2
144
142
  rdoc_options: []
145
143
  require_paths:
146
144
  - lib
@@ -156,8 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
154
  - !ruby/object:Gem::Version
157
155
  version: '0'
158
156
  requirements: []
159
- rubygems_version: 3.5.22
160
- signing_key:
157
+ rubygems_version: 3.7.2
161
158
  specification_version: 4
162
159
  summary: Minimal embedded v8 for Ruby
163
160
  test_files: []