mini_racer 0.17.0.pre8 → 0.17.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
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: