mini_racer 0.17.0.pre8 → 0.17.0.pre10

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: 6a4649daa0104376800784775a305593ff637dbde023f27ed6d9126b77c54548
4
- data.tar.gz: e46ffb00764e6113cef170508d8ed1ac4b68cab29ace1051fc7c5df4c7d97f07
3
+ metadata.gz: f09948c878aaefbc5e47285a3ee17db34e00de5d72e441cbb4ef45dd15e45f06
4
+ data.tar.gz: 95aa4a36b7612535524ff9ef6ca3116bde6dad9aa474c1c4b1af9427c0204969
5
5
  SHA512:
6
- metadata.gz: 38202ee576c23d016afd7b69ba9bada8ca47a82c3a460c028a42e25c23438f965e4ac2e8d6c4a24bea7211bed6436a9f2cc55bc4c9c0c42fd6b8e5054602c8f5
7
- data.tar.gz: e8da8f6787111b63e698db0964a9ad35ffe62c1acca83db7efa08cdb7db94d8d68ed39b989f81fd77b310fff510790d16454761f083bc5bd1154105f93c20f6f
6
+ metadata.gz: 76a9a93bcb3bdfc0f7073e6a5501d8c96bd993bb85fe321ac54114154f015c0b0f1e2ba21067f79787831cff604f34e82079913b593dab559f4cb74e54781f17
7
+ data.tar.gz: ea1da1f7e66915a147fe614cff9a6de80e2d5a2953eabb0de422c0ad2839db940cc9dbf2c5c642604b03fbd5ab71a6c0fd26e15abb38d4f4652273243a41221f
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ - 0.17.0.pr10 - 20-01-2025
2
+ - Added back support for partially deserialized objects (objects that do not translate across boundaries are returned as Error properties) - Ben Noordhuis
3
+
4
+ - 0.17.0.pre9 - 13-01-2025
5
+ - For backwards compatibility convert v8 return values to UTF-8 (invalidly encoded string still get returned using V8 encoding)
6
+
1
7
  - 0.17.0.pre8 - 11-01-2025
2
8
  - Fix handling of UTF 32 LE and Ascii encoding strings - Ben Noordhuis
3
9
  - Handle rare edge case in V8 serialization - Ben Noordhuis
@@ -164,12 +164,6 @@ static VALUE js_function_class;
164
164
  static pthread_mutex_t flags_mtx = PTHREAD_MUTEX_INITIALIZER;
165
165
  static Buf flags; // protected by |flags_mtx|
166
166
 
167
- struct rendezvous_nogvl
168
- {
169
- Context *context;
170
- Buf *req, *res;
171
- };
172
-
173
167
  // arg == &(struct rendezvous_nogvl){...}
174
168
  static void *rendezvous_callback(void *arg);
175
169
 
@@ -177,23 +171,38 @@ static void *rendezvous_callback(void *arg);
177
171
  typedef struct State
178
172
  {
179
173
  VALUE a, b;
174
+ uint8_t verbatim_keys:1;
180
175
  } State;
181
176
 
182
177
  // note: must be stack-allocated or VALUEs won't be visible to ruby's GC
183
178
  typedef struct DesCtx
184
179
  {
185
180
  State *tos;
186
- VALUE refs; // array
181
+ VALUE refs; // object refs array
182
+ uint8_t transcode_latin1:1;
187
183
  char err[64];
188
184
  State stack[512];
189
185
  } DesCtx;
190
186
 
187
+ struct rendezvous_nogvl
188
+ {
189
+ Context *context;
190
+ Buf *req, *res;
191
+ };
192
+
193
+ struct rendezvous_des
194
+ {
195
+ DesCtx *d;
196
+ Buf *res;
197
+ };
198
+
191
199
  static void DesCtx_init(DesCtx *c)
192
200
  {
193
201
  c->tos = c->stack;
194
202
  c->refs = rb_ary_new();
195
- *c->tos = (State){Qundef, Qundef};
203
+ *c->tos = (State){Qundef, Qundef, /*verbatim_keys*/0};
196
204
  *c->err = '\0';
205
+ c->transcode_latin1 = 1; // convert to utf8
197
206
  }
198
207
 
199
208
  static void put(DesCtx *c, VALUE v)
@@ -212,7 +221,8 @@ static void put(DesCtx *c, VALUE v)
212
221
  if (*b == Qundef) {
213
222
  *b = v;
214
223
  } else {
215
- *b = rb_funcall(*b, rb_intern("to_s"), 0);
224
+ if (!c->tos->verbatim_keys)
225
+ *b = rb_funcall(*b, rb_intern("to_s"), 0);
216
226
  rb_hash_aset(*a, *b, v);
217
227
  *b = Qundef;
218
228
  }
@@ -234,7 +244,7 @@ static void push(DesCtx *c, VALUE v)
234
244
  snprintf(c->err, sizeof(c->err), "stack overflow");
235
245
  return;
236
246
  }
237
- *++c->tos = (State){v, Qundef};
247
+ *++c->tos = (State){v, Qundef, /*verbatim_keys*/0};
238
248
  rb_ary_push(c->refs, v);
239
249
  }
240
250
 
@@ -321,9 +331,22 @@ static void des_string(void *arg, const char *s, size_t n)
321
331
  put(arg, rb_utf8_str_new(s, n));
322
332
  }
323
333
 
334
+ static VALUE str_encode_bang(VALUE v)
335
+ {
336
+ // TODO cache these? this function can get called often
337
+ return rb_funcall(v, rb_intern("encode!"), 1, rb_str_new_cstr("UTF-8"));
338
+ }
339
+
324
340
  static void des_string8(void *arg, const uint8_t *s, size_t n)
325
341
  {
326
- put(arg, rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding()));
342
+ DesCtx *c;
343
+ VALUE v;
344
+
345
+ c = arg;
346
+ v = rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding());
347
+ if (c->transcode_latin1)
348
+ v = str_encode_bang(v); // cannot fail
349
+ put(c, v);
327
350
  }
328
351
 
329
352
  // des_string16: |s| is not word aligned
@@ -331,7 +354,9 @@ static void des_string8(void *arg, const uint8_t *s, size_t n)
331
354
  static void des_string16(void *arg, const void *s, size_t n)
332
355
  {
333
356
  rb_encoding *e;
357
+ VALUE v, r;
334
358
  DesCtx *c;
359
+ int exc;
335
360
 
336
361
  c = arg;
337
362
  if (*c->err)
@@ -344,7 +369,15 @@ static void des_string16(void *arg, const void *s, size_t n)
344
369
  snprintf(c->err, sizeof(c->err), "no UTF16-LE encoding");
345
370
  return;
346
371
  }
347
- put(c, rb_enc_str_new((char *)s, n, e));
372
+ v = rb_enc_str_new((char *)s, n, e);
373
+ // JS strings can contain unmatched or illegal surrogate pairs
374
+ // that Ruby won't decode; return the string as-is in that case
375
+ r = rb_protect(str_encode_bang, v, &exc);
376
+ if (exc) {
377
+ rb_set_errinfo(Qnil);
378
+ r = v;
379
+ }
380
+ put(c, r);
348
381
  }
349
382
 
350
383
  // ruby doesn't really have a concept of a byte array so store it as
@@ -395,6 +428,20 @@ static void des_object_end(void *arg)
395
428
  pop(arg);
396
429
  }
397
430
 
431
+ static void des_map_begin(void *arg)
432
+ {
433
+ DesCtx *c;
434
+
435
+ c = arg;
436
+ push(c, rb_hash_new());
437
+ c->tos->verbatim_keys = 1; // don't stringify or intern keys
438
+ }
439
+
440
+ static void des_map_end(void *arg)
441
+ {
442
+ pop(arg);
443
+ }
444
+
398
445
  static void des_object_ref(void *arg, uint32_t id)
399
446
  {
400
447
  DesCtx *c;
@@ -740,25 +787,25 @@ static void *v8_thread_start(void *arg)
740
787
  return NULL;
741
788
  }
742
789
 
743
- static VALUE deserialize1(const uint8_t *p, size_t n)
790
+ static VALUE deserialize1(DesCtx *d, const uint8_t *p, size_t n)
744
791
  {
745
792
  char err[64];
746
- DesCtx d;
747
793
 
748
- DesCtx_init(&d);
749
- if (des(&err, p, n, &d))
794
+ if (des(&err, p, n, d))
750
795
  rb_raise(runtime_error, "%s", err);
751
- if (d.tos != d.stack) // should not happen
796
+ if (d->tos != d->stack) // should not happen
752
797
  rb_raise(runtime_error, "parse stack not empty");
753
- return d.tos->a;
798
+ return d->tos->a;
754
799
  }
755
800
 
756
801
  static VALUE deserialize(VALUE arg)
757
802
  {
803
+ struct rendezvous_des *a;
758
804
  Buf *b;
759
805
 
760
- b = (void *)arg;
761
- return deserialize1(b->buf, b->len);
806
+ a = (void *)arg;
807
+ b = a->res;
808
+ return deserialize1(a->d, b->buf, b->len);
762
809
  }
763
810
 
764
811
  // called with |rr_mtx| and GVL held; can raise exception
@@ -767,6 +814,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
767
814
  struct rendezvous_nogvl *a;
768
815
  VALUE func, args;
769
816
  Context *c;
817
+ DesCtx d;
770
818
  Buf *b;
771
819
 
772
820
  a = (void *)arg;
@@ -774,7 +822,8 @@ static VALUE rendezvous_callback_do(VALUE arg)
774
822
  c = a->context;
775
823
  assert(b->len > 0);
776
824
  assert(*b->buf == 'c');
777
- args = deserialize1(b->buf+1, b->len-1); // skip 'c' marker
825
+ DesCtx_init(&d);
826
+ args = deserialize1(&d, b->buf+1, b->len-1); // skip 'c' marker
778
827
  func = rb_ary_pop(args); // callback id
779
828
  func = rb_ary_entry(c->procs, FIX2LONG(func));
780
829
  return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
@@ -866,14 +915,14 @@ static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
866
915
 
867
916
  // send request to & receive reply from v8 thread; takes ownership of |req|
868
917
  // can raise exceptions and longjmp away but won't leak |req|
869
- static VALUE rendezvous(Context *c, Buf *req)
918
+ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
870
919
  {
871
920
  VALUE r;
872
921
  Buf res;
873
922
  int exc;
874
923
 
875
924
  rendezvous_no_des(c, req, &res); // takes ownership of |req|
876
- r = rb_protect(deserialize, (VALUE)&res, &exc);
925
+ r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
877
926
  buf_reset(&res);
878
927
  if (exc) {
879
928
  r = rb_errinfo();
@@ -888,6 +937,14 @@ static VALUE rendezvous(Context *c, Buf *req)
888
937
  return r;
889
938
  }
890
939
 
940
+ static VALUE rendezvous(Context *c, Buf *req)
941
+ {
942
+ DesCtx d;
943
+
944
+ DesCtx_init(&d);
945
+ return rendezvous1(c, req, &d);
946
+ }
947
+
891
948
  static void handle_exception(VALUE e)
892
949
  {
893
950
  const char *s;
@@ -1469,6 +1526,7 @@ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
1469
1526
  VALUE a, e, code, cv;
1470
1527
  Snapshot *ss;
1471
1528
  Context *c;
1529
+ DesCtx d;
1472
1530
  Ser s;
1473
1531
 
1474
1532
  TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
@@ -1483,7 +1541,9 @@ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
1483
1541
  ser_init1(&s, 'T');
1484
1542
  add_string(&s, code);
1485
1543
  // response is [arraybuffer, error]
1486
- a = rendezvous(c, &s.b);
1544
+ DesCtx_init(&d);
1545
+ d.transcode_latin1 = 0; // don't mangle snapshot binary data
1546
+ a = rendezvous1(c, &s.b, &d);
1487
1547
  e = rb_ary_pop(a);
1488
1548
  context_dispose(cv);
1489
1549
  if (*RSTRING_PTR(e))
@@ -1497,6 +1557,7 @@ static VALUE snapshot_warmup(VALUE self, VALUE arg)
1497
1557
  VALUE a, e, cv;
1498
1558
  Snapshot *ss;
1499
1559
  Context *c;
1560
+ DesCtx d;
1500
1561
  Ser s;
1501
1562
 
1502
1563
  TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
@@ -1511,7 +1572,9 @@ static VALUE snapshot_warmup(VALUE self, VALUE arg)
1511
1572
  add_string(&s, arg);
1512
1573
  ser_array_end(&s, 2);
1513
1574
  // response is [arraybuffer, error]
1514
- a = rendezvous(c, &s.b);
1575
+ DesCtx_init(&d);
1576
+ d.transcode_latin1 = 0; // don't mangle snapshot binary data
1577
+ a = rendezvous1(c, &s.b, &d);
1515
1578
  e = rb_ary_pop(a);
1516
1579
  context_dispose(cv);
1517
1580
  if (*RSTRING_PTR(e))
@@ -11,6 +11,56 @@
11
11
  #include <cstring>
12
12
  #include <vector>
13
13
 
14
+ // note: the filter function gets called inside the safe context,
15
+ // i.e., the context that has not been tampered with by user JS
16
+ // convention: $-prefixed identifiers signify objects from the
17
+ // user JS context and should be handled with special care
18
+ static const char safe_context_script_source[] = R"js(
19
+ ;(function($globalThis) {
20
+ const {Map: $Map, Set: $Set} = $globalThis
21
+ const sentinel = {}
22
+ return function filter(v) {
23
+ if (typeof v === "function")
24
+ return sentinel
25
+ if (typeof v !== "object" || v === null)
26
+ return v
27
+ if (v instanceof $Map) {
28
+ const m = new Map()
29
+ for (let [k, t] of Map.prototype.entries.call(v)) {
30
+ t = filter(t)
31
+ if (t !== sentinel)
32
+ m.set(k, t)
33
+ }
34
+ return m
35
+ } else if (v instanceof $Set) {
36
+ const s = new Set()
37
+ for (let t of Set.prototype.values.call(v)) {
38
+ t = filter(t)
39
+ if (t !== sentinel)
40
+ s.add(t)
41
+ }
42
+ return s
43
+ } else {
44
+ const o = Array.isArray(v) ? [] : {}
45
+ const pds = Object.getOwnPropertyDescriptors(v)
46
+ for (const [k, d] of Object.entries(pds)) {
47
+ if (!d.enumerable)
48
+ continue
49
+ let t = d.value
50
+ if (d.get) {
51
+ // *not* d.get.call(...), may have been tampered with
52
+ t = Function.prototype.call.call(d.get, v, k)
53
+ }
54
+ t = filter(t)
55
+ if (t !== sentinel)
56
+ Object.defineProperty(o, k, {value: t, enumerable: true})
57
+ }
58
+ return o
59
+ }
60
+ }
61
+ })
62
+ )js";
63
+
14
64
  struct Callback
15
65
  {
16
66
  struct State *st;
@@ -32,8 +82,10 @@ struct State
32
82
  // extra context for when we need access to built-ins like Array
33
83
  // and want to be sure they haven't been tampered with by JS code
34
84
  v8::Local<v8::Context> safe_context;
35
- v8::Persistent<v8::Context> persistent_context; // single-thread mode only
36
- v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
85
+ v8::Local<v8::Function> safe_context_function;
86
+ v8::Persistent<v8::Context> persistent_context; // single-thread mode only
87
+ v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
88
+ v8::Persistent<v8::Function> persistent_safe_context_function; // single-thread mode only
37
89
  Context *ruby_context;
38
90
  int64_t max_memory;
39
91
  int err_reason;
@@ -73,6 +125,23 @@ struct Serialized
73
125
  // throws JS exception on serialization error
74
126
  bool reply(State& st, v8::Local<v8::Value> v)
75
127
  {
128
+ v8::TryCatch try_catch(st.isolate);
129
+ {
130
+ Serialized serialized(st, v);
131
+ if (serialized.data) {
132
+ v8_reply(st.ruby_context, serialized.data, serialized.size);
133
+ return true;
134
+ }
135
+ }
136
+ if (!try_catch.CanContinue()) {
137
+ try_catch.ReThrow();
138
+ return false;
139
+ }
140
+ auto recv = v8::Undefined(st.isolate);
141
+ if (!st.safe_context_function->Call(st.safe_context, recv, 1, &v).ToLocal(&v)) {
142
+ try_catch.ReThrow();
143
+ return false;
144
+ }
76
145
  Serialized serialized(st, v);
77
146
  if (serialized.data)
78
147
  v8_reply(st.ruby_context, serialized.data, serialized.size);
@@ -240,7 +309,29 @@ extern "C" State *v8_thread_init(Context *c, const uint8_t *snapshot_buf,
240
309
  st.safe_context = v8::Context::New(st.isolate);
241
310
  st.context = v8::Context::New(st.isolate);
242
311
  v8::Context::Scope context_scope(st.context);
312
+ {
313
+ v8::Context::Scope context_scope(st.safe_context);
314
+ auto source = v8::String::NewFromUtf8Literal(st.isolate, safe_context_script_source);
315
+ auto filename = v8::String::NewFromUtf8Literal(st.isolate, "safe_context_script.js");
316
+ v8::ScriptOrigin origin(filename);
317
+ auto script =
318
+ v8::Script::Compile(st.safe_context, source, &origin)
319
+ .ToLocalChecked();
320
+ auto function_v = script->Run(st.safe_context).ToLocalChecked();
321
+ auto function = v8::Function::Cast(*function_v);
322
+ auto recv = v8::Undefined(st.isolate);
323
+ v8::Local<v8::Value> arg = st.context->Global();
324
+ // grant the safe context access to the user context's globalThis
325
+ st.safe_context->SetSecurityToken(st.context->GetSecurityToken());
326
+ function_v =
327
+ function->Call(st.safe_context, recv, 1, &arg)
328
+ .ToLocalChecked();
329
+ // revoke access again now that the script did its one-time setup
330
+ st.safe_context->UseDefaultSecurityToken();
331
+ st.safe_context_function = v8::Local<v8::Function>::Cast(function_v);
332
+ }
243
333
  if (single_threaded) {
334
+ st.persistent_safe_context_function.Reset(st.isolate, st.safe_context_function);
244
335
  st.persistent_safe_context.Reset(st.isolate, st.safe_context);
245
336
  st.persistent_context.Reset(st.isolate, st.context);
246
337
  return pst; // intentionally returning early and keeping alive
@@ -792,12 +883,14 @@ extern "C" void v8_single_threaded_enter(State *pst, Context *c, void (*f)(Conte
792
883
  v8::Isolate::Scope isolate_scope(st.isolate);
793
884
  v8::HandleScope handle_scope(st.isolate);
794
885
  {
886
+ st.safe_context_function = v8::Local<v8::Function>::New(st.isolate, st.persistent_safe_context_function);
795
887
  st.safe_context = v8::Local<v8::Context>::New(st.isolate, st.persistent_safe_context);
796
888
  st.context = v8::Local<v8::Context>::New(st.isolate, st.persistent_context);
797
889
  v8::Context::Scope context_scope(st.context);
798
890
  f(c);
799
891
  st.context = v8::Local<v8::Context>();
800
892
  st.safe_context = v8::Local<v8::Context>();
893
+ st.safe_context_function = v8::Local<v8::Function>();
801
894
  }
802
895
  }
803
896
 
@@ -31,6 +31,8 @@ static void des_named_props_begin(void *arg);
31
31
  static void des_named_props_end(void *arg);
32
32
  static void des_object_begin(void *arg);
33
33
  static void des_object_end(void *arg);
34
+ static void des_map_begin(void *arg);
35
+ static void des_map_end(void *arg);
34
36
  static void des_object_ref(void *arg, uint32_t id);
35
37
  // des_error_begin: followed by des_object_begin + des_object_end calls
36
38
  static void des_error_begin(void *arg);
@@ -642,7 +644,7 @@ again:
642
644
  des_object_end(arg);
643
645
  break;
644
646
  case ';': // Map
645
- des_object_begin(arg);
647
+ des_map_begin(arg);
646
648
  for (u = 0; /*empty*/; u++) {
647
649
  if (*p >= pe)
648
650
  goto too_short;
@@ -658,7 +660,7 @@ again:
658
660
  goto bad_varint;
659
661
  if (t != 2*u)
660
662
  return bail(err, "map element count mismatch");
661
- des_object_end(arg);
663
+ des_map_end(arg);
662
664
  break;
663
665
  case '\'': // Set
664
666
  des_array_begin(arg);
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.17.0.pre8"
4
+ VERSION = "0.17.0.pre10"
5
5
  LIBV8_NODE_VERSION = "~> 22.7.0.4"
6
6
  end
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.17.0.pre8
4
+ version: 0.17.0.pre10
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-01-11 00:00:00.000000000 Z
11
+ date: 2025-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -123,9 +123,9 @@ licenses:
123
123
  - MIT
124
124
  metadata:
125
125
  bug_tracker_uri: https://github.com/discourse/mini_racer/issues
126
- changelog_uri: https://github.com/discourse/mini_racer/blob/v0.17.0.pre8/CHANGELOG
127
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.17.0.pre8
128
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.17.0.pre8
126
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.17.0.pre10/CHANGELOG
127
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.17.0.pre10
128
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.17.0.pre10
129
129
  post_install_message:
130
130
  rdoc_options: []
131
131
  require_paths: