mini_racer 0.19.0 → 0.19.1

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: 8c2110c8c14882bd775ad556afdeb79e1ab42bfabddc24101b82f8390a5be830
4
+ data.tar.gz: 8c70e50ac0edbe545ddd0a17737c045f21ebd6a2aa47f8d730a7f7ced79248b3
5
5
  SHA512:
6
- metadata.gz: 0f8ab9852328121e3d55119fb1a2a8a875806b0a256d2c79ab09846fe2ed7df174beb68949213057fe8186376b73993b21dc21fe22ce82632f26e0a4354c982e
7
- data.tar.gz: 88c0a37a5a26ab746312c76499a202578a0604098a1ec3fcf7b98110aace6facd95490b19bd0f6abb8afba136b9ae94f5f1367d55050d6abdcf62bd628d6b8f2
6
+ metadata.gz: e8b17ae50c4455ed2c3db185f5e03d9554bb30096fc1540989cc7aa0e56f471ade5a7d0f815f1a24b3e492cd9c8c7db80bb5146bcf52dfdcb707caaf7323daaa
7
+ data.tar.gz: f1ef6b7225779091ab4c488f0177da8b7d8dd19e779d80a99ae307e9efbfd49e654c645f76315c4b766090cc50ce7f2ec2568586432a122d7d7aef3f7ba93d0d
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ - 0.19.1 - 20-10-2025
2
+ - JS code can now catch ruby exceptions - Ben Noordhuis
3
+ - Retain string encoding when raising exceptions - Ben Noordhuis
4
+ - Fix object identity bug with Ruby to JS conversion - Benjamin Wood
5
+
1
6
  - 0.19.0 - 24-06-2025
2
7
  - upgrade to node 24.1.0
3
8
 
@@ -13,6 +13,13 @@
13
13
  #include "serde.c"
14
14
  #include "mini_racer_v8.h"
15
15
 
16
+ // for debugging
17
+ #define RB_PUTS(v) \
18
+ do { \
19
+ fflush(stdout); \
20
+ rb_funcall(rb_mKernel, rb_intern("puts"), 1, v); \
21
+ } while (0)
22
+
16
23
  #if RUBY_API_VERSION_CODE < 3*10000+4*100 // 3.4.0
17
24
  static inline void rb_thread_lock_native_thread(void)
18
25
  {
@@ -154,6 +161,7 @@ static VALUE platform_init_error;
154
161
  static VALUE context_disposed_error;
155
162
  static VALUE parse_error;
156
163
  static VALUE memory_error;
164
+ static VALUE script_error;
157
165
  static VALUE runtime_error;
158
166
  static VALUE internal_error;
159
167
  static VALUE snapshot_error;
@@ -482,12 +490,36 @@ static void des_object_ref(void *arg, uint32_t id)
482
490
 
483
491
  static void des_error_begin(void *arg)
484
492
  {
485
- push(arg, rb_class_new_instance(0, NULL, rb_eRuntimeError));
493
+ push(arg, rb_ary_new());
486
494
  }
487
495
 
488
496
  static void des_error_end(void *arg)
489
497
  {
490
- pop(arg);
498
+ VALUE *a, h, message, stack, cause, newline;
499
+ DesCtx *c;
500
+
501
+ c = arg;
502
+ if (*c->err)
503
+ return;
504
+ if (c->tos == c->stack) {
505
+ snprintf(c->err, sizeof(c->err), "stack underflow");
506
+ return;
507
+ }
508
+ a = &c->tos->a;
509
+ h = rb_ary_pop(*a);
510
+ message = rb_hash_aref(h, rb_str_new_cstr("message"));
511
+ stack = rb_hash_aref(h, rb_str_new_cstr("stack"));
512
+ cause = rb_hash_aref(h, rb_str_new_cstr("cause"));
513
+ if (NIL_P(message))
514
+ message = rb_str_new_cstr("JS exception");
515
+ if (!NIL_P(stack)) {
516
+ newline = rb_str_new_cstr("\n");
517
+ message = rb_funcall(message, rb_intern("concat"), 2, newline, stack);
518
+ }
519
+ *a = rb_class_new_instance(1, &message, script_error);
520
+ if (!NIL_P(cause))
521
+ rb_iv_set(*a, "@cause", cause);
522
+ pop(c);
491
523
  }
492
524
 
493
525
  static int collect(VALUE k, VALUE v, VALUE a)
@@ -527,52 +559,58 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
527
559
  return -1;
528
560
  switch (TYPE(v)) {
529
561
  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));
562
+ {
563
+ VALUE obj_id = rb_obj_id(v);
564
+ id = rb_hash_lookup(refs, obj_id);
565
+ if (NIL_P(id)) {
566
+ n = RARRAY_LENINT(v);
567
+ i = rb_hash_size_num(refs);
568
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
569
+ ser_array_begin(s, n);
570
+ for (i = 0; i < n; i++)
571
+ if (serialize1(s, refs, rb_ary_entry(v, i)))
572
+ return -1;
573
+ ser_array_end(s, n);
574
+ } else {
575
+ ser_object_ref(s, FIX2LONG(id));
576
+ }
542
577
  }
543
578
  break;
544
579
  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);
580
+ {
581
+ VALUE obj_id = rb_obj_id(v);
582
+ id = rb_hash_lookup(refs, obj_id);
583
+ if (NIL_P(id)) {
584
+ a = rb_ary_new();
585
+ i = rb_hash_size_num(refs);
586
+ n = rb_hash_size_num(v);
587
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
588
+ rb_hash_foreach(v, collect, a);
564
589
  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;
590
+ t = rb_ary_entry(a, i);
591
+ switch (TYPE(t)) {
592
+ case T_FIXNUM:
593
+ case T_STRING:
594
+ case T_SYMBOL:
595
+ continue;
596
+ }
597
+ break;
598
+ }
599
+ if (i == 2*n) {
600
+ ser_object_begin(s);
601
+ for (i = 0; i < 2*n; i += 2) {
602
+ if (serialize1(s, refs, rb_ary_entry(a, i+0)))
603
+ return -1;
604
+ if (serialize1(s, refs, rb_ary_entry(a, i+1)))
605
+ return -1;
606
+ }
607
+ ser_object_end(s, n);
608
+ } else {
609
+ return bail(&s->err, "TODO serialize as Map");
569
610
  }
570
- ser_object_end(s, n);
571
611
  } else {
572
- return bail(&s->err, "TODO serialize as Map");
612
+ ser_object_ref(s, FIX2LONG(id));
573
613
  }
574
- } else {
575
- ser_object_ref(s, FIX2LONG(id));
576
614
  }
577
615
  break;
578
616
  case T_DATA:
@@ -888,6 +926,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
888
926
  static void *rendezvous_callback(void *arg)
889
927
  {
890
928
  struct rendezvous_nogvl *a;
929
+ const char *err;
891
930
  Context *c;
892
931
  int exc;
893
932
  VALUE r;
@@ -911,7 +950,12 @@ out:
911
950
  buf_move(&s.b, a->req);
912
951
  return NULL;
913
952
  fail:
914
- ser_init1(&s, 'e'); // exception pending
953
+ ser_init0(&s); // ruby exception pending
954
+ w_byte(&s, 'e'); // send ruby error message to v8 thread
955
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
956
+ err = StringValueCStr(r);
957
+ if (err)
958
+ w(&s, err, strlen(err));
915
959
  goto out;
916
960
  }
917
961
 
@@ -969,6 +1013,13 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
969
1013
  int exc;
970
1014
 
971
1015
  rendezvous_no_des(c, req, &res); // takes ownership of |req|
1016
+ r = c->exception;
1017
+ c->exception = Qnil;
1018
+ // if js land didn't handle exception from ruby callback, re-raise it now
1019
+ if (res.len == 1 && *res.buf == 'e') {
1020
+ assert(!NIL_P(r));
1021
+ rb_exc_raise(r);
1022
+ }
972
1023
  r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
973
1024
  buf_reset(&res);
974
1025
  if (exc) {
@@ -976,11 +1027,6 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
976
1027
  rb_set_errinfo(Qnil);
977
1028
  rb_exc_raise(r);
978
1029
  }
979
- if (!NIL_P(c->exception)) {
980
- r = c->exception;
981
- c->exception = Qnil;
982
- rb_exc_raise(r);
983
- }
984
1030
  return r;
985
1031
  }
986
1032
 
@@ -999,8 +1045,8 @@ static void handle_exception(VALUE e)
999
1045
 
1000
1046
  if (NIL_P(e))
1001
1047
  return;
1002
- StringValue(e);
1003
- s = RSTRING_PTR(e);
1048
+ e = StringValue(e);
1049
+ s = StringValueCStr(e);
1004
1050
  switch (*s) {
1005
1051
  case NO_ERROR:
1006
1052
  return;
@@ -1022,7 +1068,7 @@ static void handle_exception(VALUE e)
1022
1068
  default:
1023
1069
  rb_raise(internal_error, "bad error class %02x", *s);
1024
1070
  }
1025
- rb_raise(klass, "%s", s+1);
1071
+ rb_enc_raise(rb_enc_get(e), klass, "%s", s+1);
1026
1072
  }
1027
1073
 
1028
1074
  static VALUE context_alloc(VALUE klass)
@@ -1628,6 +1674,11 @@ static VALUE snapshot_size0(VALUE self)
1628
1674
  return LONG2FIX(RSTRING_LENINT(ss->blob));
1629
1675
  }
1630
1676
 
1677
+ static VALUE script_error_cause(VALUE self)
1678
+ {
1679
+ return rb_iv_get(self, "@cause");
1680
+ }
1681
+
1631
1682
  __attribute__((visibility("default")))
1632
1683
  void Init_mini_racer_extension(void)
1633
1684
  {
@@ -1642,10 +1693,13 @@ void Init_mini_racer_extension(void)
1642
1693
  c = rb_define_class_under(m, "EvalError", c);
1643
1694
  parse_error = rb_define_class_under(m, "ParseError", c);
1644
1695
  memory_error = rb_define_class_under(m, "V8OutOfMemoryError", c);
1696
+ script_error = rb_define_class_under(m, "ScriptError", c);
1645
1697
  runtime_error = rb_define_class_under(m, "RuntimeError", c);
1646
1698
  internal_error = rb_define_class_under(m, "InternalError", c);
1647
1699
  terminated_error = rb_define_class_under(m, "ScriptTerminatedError", c);
1648
1700
 
1701
+ rb_define_method(script_error, "cause", script_error_cause, 0);
1702
+
1649
1703
  c = context_class = rb_define_class_under(m, "Context", rb_cObject);
1650
1704
  rb_define_method(c, "initialize", context_initialize, -1);
1651
1705
  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"
4
+ VERSION = "0.19.1"
5
5
  LIBV8_NODE_VERSION = "~> 24.1.0.0"
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,14 @@
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-24 00:00:00.000000000 Z
11
+ date: 2025-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -137,9 +137,9 @@ licenses:
137
137
  - MIT
138
138
  metadata:
139
139
  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
140
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.19.1/CHANGELOG
141
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.19.1
142
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.19.1
143
143
  post_install_message:
144
144
  rdoc_options: []
145
145
  require_paths: