mini_racer 0.22.0 → 0.22.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 +4 -4
- data/CHANGELOG +17 -0
- data/README.md +30 -1
- data/ext/mini_racer_extension/mini_racer_extension.c +157 -41
- data/ext/mini_racer_extension/mini_racer_v8.cc +326 -13
- data/ext/mini_racer_extension/mini_racer_v8.h +4 -0
- data/ext/mini_racer_extension/serde.c +17 -10
- data/lib/mini_racer/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 700f0533786f28a992f2b46a0790ed557c36255d8b7357aecfd3fa1bfd4a01a9
|
|
4
|
+
data.tar.gz: f45170bb718eeff6e2b5d6f83af6039d178f36541983ab20c31e1dbfab83f7cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae2e7f4e68826ce1115c933cc17835cc1d32155de483f00a3269b0669fb25198d648aa87fc3ac49b7496c23015b080ea2e9c8e57b061bd4c115716b0180ad6a7
|
|
7
|
+
data.tar.gz: 4d5a790f98965015cfc97138fe0e8a9e5bbfd11c0f0f67499f8780d0cdee575105f4c7deaf310b6d5937c6fe56f05b9f7548f0d998ea0658c98970fb72b4dc14
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
- 0.22.2 - 23-10-2026
|
|
2
|
+
- Fix nested Symbols, functions, and Map keys aborting the process during `eval`/`call` serialization (#447)
|
|
3
|
+
- Fix attached callback result conversion raising inside the V8 reply path, which could hang the context; conversion errors now propagate normally
|
|
4
|
+
- Fix callback exceptions whose `to_s` raises or returns a non-string, including reentrant `to_s`, hanging the callback roundtrip
|
|
5
|
+
- Bound Ruby to JavaScript serialization depth and fix an off-by-one in the JavaScript to Ruby deserializer stack
|
|
6
|
+
- Fix termination state leaking from error formatting or serialization callbacks into subsequent evaluations
|
|
7
|
+
- Fix `Snapshot#warmup!` aborting for an empty loaded snapshot (#449)
|
|
8
|
+
- Fix `Snapshot#warmup!` aborting when warmup creates Intl objects (#450); snapshot creation now uses the same handle scoping as V8's warmup implementation
|
|
9
|
+
- Reject runtime Intl access in `Snapshot.new` scripts with `MiniRacer::SnapshotError` instead of aborting the process
|
|
10
|
+
- Validate loaded snapshot versions, headers, section offsets, and checksums, raising `MiniRacer::SnapshotError` for malformed or incompatible blobs instead of terminating the process (#448)
|
|
11
|
+
- Make `Snapshot#dump` return a copy so mutating it cannot corrupt the stored snapshot
|
|
12
|
+
|
|
13
|
+
- 0.22.1 - 27-08-2026
|
|
14
|
+
- Fix building with Clang 22+ and big-endian bigint serialization by making bigint serialization byte-oriented instead of relying on native `uint64_t`/`unsigned long` representations
|
|
15
|
+
- Fix Ruby integers at or above 512 bits being silently truncated when passed to JavaScript, and large JavaScript bigints producing an invalid internal value when returned to Ruby
|
|
16
|
+
- Support Ruby and JavaScript bigints up to a 16 MiB magnitude, using allocation-free conversion for common sizes and bounded dynamic storage for larger values
|
|
17
|
+
|
|
1
18
|
- 0.22.0 - 12-08-2026
|
|
2
19
|
- Add `Context#call_await` and `Context#eval_await`: like `call`/`eval` but block until a returned Promise settles and return the settled value; rejections raise `MiniRacer::RuntimeError`
|
|
3
20
|
- Fix a `call` or `eval` made from a Ruby callback taking the timeout or `stop` meant for the evaluation around it, which then kept running
|
data/README.md
CHANGED
|
@@ -60,6 +60,10 @@ puts context.eval("array_and_hash()")
|
|
|
60
60
|
# => {"a" => 1, "b" => [1, {"a" => 1}]}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
Ruby `Integer` and JavaScript `BigInt` values are converted exactly up to a
|
|
64
|
+
16 MiB magnitude (about 134 million bits). Larger individual values are
|
|
65
|
+
rejected with a serialization error rather than truncated.
|
|
66
|
+
|
|
63
67
|
### Return binary data from Ruby to JavaScript
|
|
64
68
|
|
|
65
69
|
Attached Ruby functions can return binary data as `Uint8Array` using `MiniRacer::Binary`:
|
|
@@ -206,6 +210,12 @@ Snapshots can come in handy for example if you want your contexts to be pre-load
|
|
|
206
210
|
> are fixed once the snapshot has been captured. They are no longer really random
|
|
207
211
|
> nor reflect the current time.
|
|
208
212
|
|
|
213
|
+
Snapshot scripts passed to `Snapshot.new` that read or write `Intl` are
|
|
214
|
+
rejected with `MiniRacer::SnapshotError`: V8 cannot serialize Intl's foreign
|
|
215
|
+
handles, and allowing them to reach snapshot creation aborts the process.
|
|
216
|
+
`Snapshot#warmup!` may use Intl because its temporary context is discarded
|
|
217
|
+
before the snapshot is created.
|
|
218
|
+
|
|
209
219
|
Also note that snapshots can be warmed up, using the `warmup!` method, which allows you to call functions which are otherwise lazily compiled to get them to compile right away; any side effect of your warm up code being then dismissed. [More details on warming up here](https://github.com/electron/electron/issues/169#issuecomment-76783481), and a small example:
|
|
210
220
|
|
|
211
221
|
```ruby
|
|
@@ -238,7 +248,7 @@ context.eval("foo")
|
|
|
238
248
|
|
|
239
249
|
Note that snapshots are architecture and V8-version specific. A snapshot created on one platform (e.g., ARM64 macOS) cannot be loaded on a different platform (e.g., x86_64 Linux). Snapshots are best used for same-machine caching or homogeneous deployment environments.
|
|
240
250
|
|
|
241
|
-
**Security note:** Only load snapshots from trusted sources. V8 snapshots are not designed to be safely loaded from untrusted input—malformed or malicious snapshot data may cause crashes or memory corruption.
|
|
251
|
+
**Security note:** Only load snapshots from trusted sources. V8 snapshots are not designed to be safely loaded from untrusted input—malformed or malicious snapshot data may cause crashes or memory corruption. `Snapshot.load` performs basic version, header, section-offset, and checksum validation, but this is not a sandbox.
|
|
242
252
|
|
|
243
253
|
### Garbage collection
|
|
244
254
|
|
|
@@ -518,6 +528,25 @@ gcc >= 12.2 and Xcode >= 13 are, at the time of writing, known to work.
|
|
|
518
528
|
* Concurrent cause .... JRuby
|
|
519
529
|
* Supports execjs
|
|
520
530
|
|
|
531
|
+
## Community
|
|
532
|
+
|
|
533
|
+
Libraries and integrations built with MiniRacer:
|
|
534
|
+
|
|
535
|
+
- [Humid](https://github.com/thoughtbot/humid) — JavaScript server-side rendering helpers for Rails.
|
|
536
|
+
- [ExecJS](https://github.com/rails/execjs) — A common interface for executing JavaScript from Ruby, with a MiniRacer runtime.
|
|
537
|
+
- [Opal](https://github.com/opal/opal) — A Ruby-to-JavaScript compiler with a MiniRacer CLI runner.
|
|
538
|
+
- [Handlebars::Engine](https://github.com/gi/handlebars-ruby) — A complete Ruby interface to Handlebars.js.
|
|
539
|
+
- [Minibars](https://github.com/combinaut/minibars) — A lightweight Handlebars.rb replacement built on MiniRacer.
|
|
540
|
+
- [Rtlcss](https://github.com/discourse/rtlcss) — Runs rtlcss from Ruby to convert stylesheets between LTR and RTL.
|
|
541
|
+
- [MessageFormat](https://github.com/discourse/messageformat) — Compiles MessageFormat messages from Ruby using `@messageformat/core`.
|
|
542
|
+
- [feelin](https://github.com/ekzo-dev/ruby-feelin) — Evaluates DMN FEEL expressions from Ruby.
|
|
543
|
+
- [parse-css](https://github.com/camertron/parse-css) — Parses CSS, including nested CSS, through the JavaScript parse-css library.
|
|
544
|
+
|
|
545
|
+
### Notable users
|
|
546
|
+
|
|
547
|
+
- [Discourse](https://github.com/discourse/discourse) — A large Rails application using MiniRacer for JavaScript compilation, processing, and sandboxed evaluation.
|
|
548
|
+
- [rack-mini-profiler](https://github.com/MiniProfiler/rack-mini-profiler) — Uses MiniRacer in its JavaScript build tooling.
|
|
549
|
+
|
|
521
550
|
## Contributing
|
|
522
551
|
|
|
523
552
|
Bug reports and pull requests are welcome on GitHub at https://github.com/rubyjs/mini_racer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
@@ -62,6 +62,9 @@ static inline void rb_thread_lock_native_thread(void)
|
|
|
62
62
|
|
|
63
63
|
#define countof(x) (sizeof(x) / sizeof(*(x)))
|
|
64
64
|
#define endof(x) ((x) + countof(x))
|
|
65
|
+
#define BIGINT_STACK_WORDS 64
|
|
66
|
+
#define BIGINT_MAX_BYTES (16 * 1024 * 1024)
|
|
67
|
+
#define SERIALIZE_MAX_DEPTH 512
|
|
65
68
|
|
|
66
69
|
// mostly RO: assigned once by platform_set_flag1 while holding |flags_mtx|,
|
|
67
70
|
// from then on read-only and accessible without holding locks
|
|
@@ -136,6 +139,7 @@ typedef struct Context
|
|
|
136
139
|
struct State *pst; // used by v8 thread
|
|
137
140
|
VALUE procs; // array of js -> ruby callbacks
|
|
138
141
|
VALUE exception; // pending exception or Qnil
|
|
142
|
+
int pending_jump_tag; // non-exception jump to replay after the roundtrip
|
|
139
143
|
Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
|
|
140
144
|
Buf v8_req; // stable v8-side copy of a request returned by v8_roundtrip
|
|
141
145
|
int res_ready; // protected by |mtx|; response may be filled before ready
|
|
@@ -287,7 +291,7 @@ static void push(DesCtx *c, VALUE v)
|
|
|
287
291
|
{
|
|
288
292
|
if (*c->err)
|
|
289
293
|
return;
|
|
290
|
-
if (c->tos == endof(c->stack)) {
|
|
294
|
+
if (c->tos == endof(c->stack) - 1) {
|
|
291
295
|
snprintf(c->err, sizeof(c->err), "stack overflow");
|
|
292
296
|
return;
|
|
293
297
|
}
|
|
@@ -353,33 +357,32 @@ static void des_date(void *arg, double v)
|
|
|
353
357
|
put(arg, rb_time_new(sec, usec));
|
|
354
358
|
}
|
|
355
359
|
|
|
356
|
-
// note: v8 stores bigints
|
|
357
|
-
// so we have to take additional steps to ensure correct conversion
|
|
360
|
+
// note: v8 stores bigints as a sign plus little-endian 64-bit magnitude words
|
|
358
361
|
static void des_bigint(void *arg, const void *p, size_t n, int sign)
|
|
359
362
|
{
|
|
360
363
|
VALUE v;
|
|
361
|
-
size_t i;
|
|
362
364
|
DesCtx *c;
|
|
363
|
-
|
|
365
|
+
int flags;
|
|
364
366
|
|
|
365
367
|
c = arg;
|
|
366
368
|
if (*c->err)
|
|
367
369
|
return;
|
|
368
|
-
if (n
|
|
370
|
+
if (n % sizeof(uint64_t)) {
|
|
371
|
+
snprintf(c->err, sizeof(c->err), "bad bigint");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (n > BIGINT_MAX_BYTES) {
|
|
369
375
|
snprintf(c->err, sizeof(c->err), "bigint too big");
|
|
370
376
|
return;
|
|
371
377
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
v = rb_big_unpack(limbs, a-limbs);
|
|
381
|
-
if (sign < 0)
|
|
382
|
-
v = rb_funcall(v, rb_intern("-@"), 0);
|
|
378
|
+
if (n == 0) {
|
|
379
|
+
v = INT2FIX(0);
|
|
380
|
+
} else {
|
|
381
|
+
flags = INTEGER_PACK_LITTLE_ENDIAN;
|
|
382
|
+
if (sign < 0)
|
|
383
|
+
flags |= INTEGER_PACK_NEGATIVE;
|
|
384
|
+
v = rb_integer_unpack(p, n/sizeof(uint64_t), sizeof(uint64_t), 0, flags);
|
|
385
|
+
}
|
|
383
386
|
put(c, v);
|
|
384
387
|
}
|
|
385
388
|
|
|
@@ -580,15 +583,47 @@ static void add_string(Ser *s, VALUE v)
|
|
|
580
583
|
return ser_string(s, p, n);
|
|
581
584
|
}
|
|
582
585
|
|
|
583
|
-
|
|
586
|
+
// Keep small values allocation-free while allowing large values up to a
|
|
587
|
+
// deliberate per-value limit that bounds temporary conversion storage.
|
|
588
|
+
static int serialize_bigint(Ser *s, VALUE v)
|
|
589
|
+
{
|
|
590
|
+
uint64_t stack_words[BIGINT_STACK_WORDS];
|
|
591
|
+
uint64_t *words;
|
|
592
|
+
size_t nwords, nbytes;
|
|
593
|
+
int packed;
|
|
594
|
+
|
|
595
|
+
nwords = rb_absint_numwords(v, 64, NULL);
|
|
596
|
+
if (nwords == (size_t)-1 || nwords > BIGINT_MAX_BYTES/sizeof(*words))
|
|
597
|
+
return bail(&s->err, "bigint too big");
|
|
598
|
+
nbytes = nwords * sizeof(*words);
|
|
599
|
+
words = stack_words;
|
|
600
|
+
if (nwords > countof(stack_words)) {
|
|
601
|
+
words = malloc(nbytes);
|
|
602
|
+
if (!words)
|
|
603
|
+
return bail(&s->err, "out of memory");
|
|
604
|
+
}
|
|
605
|
+
packed = rb_integer_pack(v, words, nwords, sizeof(*words), 0,
|
|
606
|
+
INTEGER_PACK_LITTLE_ENDIAN);
|
|
607
|
+
if (packed < -1 || packed > 1) {
|
|
608
|
+
if (words != stack_words)
|
|
609
|
+
free(words);
|
|
610
|
+
return bail(&s->err, "bigint too big");
|
|
611
|
+
}
|
|
612
|
+
ser_bigint(s, words, nbytes, packed < 0 ? -1 : 1);
|
|
613
|
+
if (words != stack_words)
|
|
614
|
+
free(words);
|
|
615
|
+
return *s->err ? -1 : 0;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
static int serialize1(Ser *s, VALUE refs, VALUE v, int depth)
|
|
584
619
|
{
|
|
585
|
-
unsigned long limbs[64];
|
|
586
620
|
VALUE a, t, id;
|
|
587
621
|
size_t i, n;
|
|
588
|
-
int sign;
|
|
589
622
|
|
|
590
623
|
if (*s->err)
|
|
591
624
|
return -1;
|
|
625
|
+
if (depth > SERIALIZE_MAX_DEPTH)
|
|
626
|
+
return bail(&s->err, "max depth exceeded");
|
|
592
627
|
switch (TYPE(v)) {
|
|
593
628
|
case T_ARRAY:
|
|
594
629
|
{
|
|
@@ -600,7 +635,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
600
635
|
rb_hash_aset(refs, obj_id, LONG2FIX(i));
|
|
601
636
|
ser_array_begin(s, n);
|
|
602
637
|
for (i = 0; i < n; i++)
|
|
603
|
-
if (serialize1(s, refs, rb_ary_entry(v, i)))
|
|
638
|
+
if (serialize1(s, refs, rb_ary_entry(v, i), depth + 1))
|
|
604
639
|
return -1;
|
|
605
640
|
ser_array_end(s, n);
|
|
606
641
|
} else {
|
|
@@ -631,9 +666,9 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
631
666
|
if (i == 2*n) {
|
|
632
667
|
ser_object_begin(s);
|
|
633
668
|
for (i = 0; i < 2*n; i += 2) {
|
|
634
|
-
if (serialize1(s, refs, rb_ary_entry(a, i+0)))
|
|
669
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+0), depth + 1))
|
|
635
670
|
return -1;
|
|
636
|
-
if (serialize1(s, refs, rb_ary_entry(a, i+1)))
|
|
671
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+1), depth + 1))
|
|
637
672
|
return -1;
|
|
638
673
|
}
|
|
639
674
|
ser_object_end(s, n);
|
|
@@ -670,15 +705,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
670
705
|
ser_bool(s, 0);
|
|
671
706
|
break;
|
|
672
707
|
case T_BIGNUM:
|
|
673
|
-
|
|
674
|
-
// so we have to take additional steps to ensure correct conversion
|
|
675
|
-
memset(limbs, 0, sizeof(limbs));
|
|
676
|
-
sign = rb_big_sign(v) ? 1 : -1;
|
|
677
|
-
if (sign < 0)
|
|
678
|
-
v = rb_big_mul(v, LONG2FIX(-1));
|
|
679
|
-
rb_big_pack(v, limbs, countof(limbs));
|
|
680
|
-
ser_bigint(s, limbs, countof(limbs), sign);
|
|
681
|
-
break;
|
|
708
|
+
return serialize_bigint(s, v);
|
|
682
709
|
case T_FIXNUM:
|
|
683
710
|
ser_int(s, FIX2LONG(v));
|
|
684
711
|
break;
|
|
@@ -725,7 +752,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
725
752
|
// that will throw off the object reference count
|
|
726
753
|
static int serialize(Ser *s, VALUE v)
|
|
727
754
|
{
|
|
728
|
-
return serialize1(s, rb_hash_new(), v);
|
|
755
|
+
return serialize1(s, rb_hash_new(), v, 0);
|
|
729
756
|
}
|
|
730
757
|
|
|
731
758
|
static struct timespec deadline_ms(int ms)
|
|
@@ -958,6 +985,8 @@ static VALUE deserialize1(DesCtx *d, const uint8_t *p, size_t n)
|
|
|
958
985
|
|
|
959
986
|
if (des(&err, p, n, d))
|
|
960
987
|
rb_raise(runtime_error, "%s", err);
|
|
988
|
+
if (*d->err)
|
|
989
|
+
rb_raise(runtime_error, "%s", d->err);
|
|
961
990
|
if (d->tos != d->stack) // should not happen
|
|
962
991
|
rb_raise(runtime_error, "parse stack not empty");
|
|
963
992
|
return d->tos->a;
|
|
@@ -1000,11 +1029,54 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
1000
1029
|
return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
|
|
1001
1030
|
}
|
|
1002
1031
|
|
|
1032
|
+
struct callback_serialize_args
|
|
1033
|
+
{
|
|
1034
|
+
Ser *ser;
|
|
1035
|
+
VALUE value;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
static VALUE callback_serialize(VALUE arg)
|
|
1039
|
+
{
|
|
1040
|
+
struct callback_serialize_args *args;
|
|
1041
|
+
args = (void *)arg;
|
|
1042
|
+
return INT2FIX(serialize(args->ser, args->value));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
static VALUE callback_exception_to_s(VALUE exception)
|
|
1046
|
+
{
|
|
1047
|
+
VALUE message;
|
|
1048
|
+
message = rb_funcall(exception, rb_intern("to_s"), 0);
|
|
1049
|
+
StringValue(message);
|
|
1050
|
+
return message;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
static VALUE callback_exception_from_state(int state, int *pending_jump_tag)
|
|
1054
|
+
{
|
|
1055
|
+
VALUE exception = rb_errinfo();
|
|
1056
|
+
rb_set_errinfo(Qnil);
|
|
1057
|
+
if (RB_TYPE_P(exception, T_OBJECT) &&
|
|
1058
|
+
rb_obj_is_kind_of(exception, rb_eException)) {
|
|
1059
|
+
return exception;
|
|
1060
|
+
}
|
|
1061
|
+
// Thread#kill and other non-exception jumps leave a sentinel or internal
|
|
1062
|
+
// throw data in errinfo; do not hand that to rb_funcall/rb_exc_raise.
|
|
1063
|
+
// Replay the jump once the V8 roundtrip has fully unwound.
|
|
1064
|
+
if (state && pending_jump_tag && !*pending_jump_tag)
|
|
1065
|
+
*pending_jump_tag = state;
|
|
1066
|
+
return Qnil;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
static VALUE callback_internal_error(VALUE message)
|
|
1070
|
+
{
|
|
1071
|
+
return rb_exc_new_cstr(internal_error, (const char *)message);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1003
1074
|
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1004
1075
|
// callback data is in |a->res|, serialized result goes in |a->req|
|
|
1005
1076
|
static void *rendezvous_callback(void *arg)
|
|
1006
1077
|
{
|
|
1007
1078
|
struct rendezvous_nogvl *a;
|
|
1079
|
+
struct callback_serialize_args args;
|
|
1008
1080
|
const char *err;
|
|
1009
1081
|
Context *c;
|
|
1010
1082
|
int exc;
|
|
@@ -1015,13 +1087,29 @@ static void *rendezvous_callback(void *arg)
|
|
|
1015
1087
|
c = a->context;
|
|
1016
1088
|
r = rb_protect(rendezvous_callback_do, (VALUE)a, &exc);
|
|
1017
1089
|
if (exc) {
|
|
1018
|
-
c->exception =
|
|
1019
|
-
rb_set_errinfo(Qnil);
|
|
1090
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1020
1091
|
goto fail;
|
|
1021
1092
|
}
|
|
1022
1093
|
ser_init1(&s, 'c'); // callback reply
|
|
1023
|
-
|
|
1024
|
-
|
|
1094
|
+
args.ser = &s;
|
|
1095
|
+
args.value = r;
|
|
1096
|
+
r = rb_protect(callback_serialize, (VALUE)&args, &exc);
|
|
1097
|
+
if (exc) {
|
|
1098
|
+
// A Ruby conversion hook, Check_Type(), or stack overflow escaped
|
|
1099
|
+
// serialize(). Keep the V8 thread waiting for a reply and turn it
|
|
1100
|
+
// into a normal callback exception instead.
|
|
1101
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1102
|
+
ser_reset(&s);
|
|
1103
|
+
goto fail;
|
|
1104
|
+
}
|
|
1105
|
+
if (NUM2INT(r)) {
|
|
1106
|
+
VALUE exception = rb_protect(callback_internal_error,
|
|
1107
|
+
(VALUE)s.err, &exc);
|
|
1108
|
+
if (exc) {
|
|
1109
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1110
|
+
} else {
|
|
1111
|
+
c->exception = exception;
|
|
1112
|
+
}
|
|
1025
1113
|
ser_reset(&s);
|
|
1026
1114
|
goto fail;
|
|
1027
1115
|
}
|
|
@@ -1031,10 +1119,25 @@ out:
|
|
|
1031
1119
|
fail:
|
|
1032
1120
|
ser_init0(&s); // ruby exception pending
|
|
1033
1121
|
w_byte(&s, 'e'); // send ruby error message to v8 thread
|
|
1034
|
-
r =
|
|
1035
|
-
|
|
1036
|
-
|
|
1122
|
+
r = Qnil;
|
|
1123
|
+
if (!NIL_P(c->exception)) {
|
|
1124
|
+
VALUE original_exception = c->exception;
|
|
1125
|
+
r = rb_protect(callback_exception_to_s, c->exception, &exc);
|
|
1126
|
+
if (exc) {
|
|
1127
|
+
callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1128
|
+
r = Qnil;
|
|
1129
|
+
}
|
|
1130
|
+
// to_s can re-enter the context and consume c->exception. Keep the
|
|
1131
|
+
// original callback exception for the eventual Ruby-side re-raise.
|
|
1132
|
+
c->exception = original_exception;
|
|
1133
|
+
}
|
|
1134
|
+
if (NIL_P(r)) {
|
|
1135
|
+
err = "Ruby exception";
|
|
1136
|
+
w(&s, err, strlen(err));
|
|
1137
|
+
} else {
|
|
1138
|
+
err = RSTRING_PTR(r);
|
|
1037
1139
|
w(&s, err, RSTRING_LEN(r));
|
|
1140
|
+
}
|
|
1038
1141
|
goto out;
|
|
1039
1142
|
}
|
|
1040
1143
|
|
|
@@ -1325,6 +1428,12 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
|
1325
1428
|
int exc;
|
|
1326
1429
|
|
|
1327
1430
|
rendezvous_no_des(c, req, &res); // takes ownership of |req|
|
|
1431
|
+
int jump_tag = c->pending_jump_tag;
|
|
1432
|
+
c->pending_jump_tag = 0;
|
|
1433
|
+
if (jump_tag) {
|
|
1434
|
+
buf_reset(&res);
|
|
1435
|
+
rb_jump_tag(jump_tag);
|
|
1436
|
+
}
|
|
1328
1437
|
r = c->exception;
|
|
1329
1438
|
c->exception = Qnil;
|
|
1330
1439
|
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
@@ -2121,15 +2230,22 @@ static VALUE snapshot_dump(VALUE self)
|
|
|
2121
2230
|
Snapshot *ss;
|
|
2122
2231
|
|
|
2123
2232
|
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2124
|
-
|
|
2233
|
+
// Callers may mutate the returned String; never expose the blob that
|
|
2234
|
+
// Context.new/snapshot_warmup later hand to V8.
|
|
2235
|
+
return rb_str_dup(ss->blob);
|
|
2125
2236
|
}
|
|
2126
2237
|
|
|
2127
2238
|
static VALUE snapshot_load(VALUE klass, VALUE blob)
|
|
2128
2239
|
{
|
|
2129
2240
|
Snapshot *ss;
|
|
2130
2241
|
VALUE self;
|
|
2242
|
+
char err[128];
|
|
2131
2243
|
|
|
2132
2244
|
Check_Type(blob, T_STRING);
|
|
2245
|
+
if (v8_snapshot_validate((const uint8_t *)RSTRING_PTR(blob),
|
|
2246
|
+
(size_t)RSTRING_LEN(blob), err, sizeof(err))) {
|
|
2247
|
+
rb_raise(snapshot_error, "%s", err);
|
|
2248
|
+
}
|
|
2133
2249
|
self = snapshot_alloc(klass);
|
|
2134
2250
|
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2135
2251
|
ss->blob = rb_str_dup(blob);
|
|
@@ -276,6 +276,8 @@ bool reply(State& st, v8::Local<v8::Value> v)
|
|
|
276
276
|
Serialized serialized(st, v);
|
|
277
277
|
if (serialized.data)
|
|
278
278
|
v8_reply(st.ruby_context, serialized.data, serialized.size);
|
|
279
|
+
else
|
|
280
|
+
try_catch.ReThrow();
|
|
279
281
|
return serialized.data != nullptr; // exception pending if false
|
|
280
282
|
}
|
|
281
283
|
|
|
@@ -750,10 +752,35 @@ fail:
|
|
|
750
752
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
751
753
|
if (cause) result = v8::Undefined(st.isolate);
|
|
752
754
|
auto err = to_error(st, &try_catch, cause);
|
|
755
|
+
// Error formatting can run user JS (for example Error.prepareStackTrace)
|
|
756
|
+
// which may call Context#stop. Do not let that termination leak into the
|
|
757
|
+
// reply or the next evaluation.
|
|
758
|
+
bool terminated_during_error = false;
|
|
759
|
+
if (st.terminate_requested.load() || st.isolate->IsExecutionTerminating()) {
|
|
760
|
+
terminated_during_error = true;
|
|
761
|
+
preserve_termination =
|
|
762
|
+
suspend_termination(st, nested, cause) || preserve_termination;
|
|
763
|
+
}
|
|
764
|
+
if (terminated_during_error) {
|
|
765
|
+
result = v8::Undefined(st.isolate);
|
|
766
|
+
std::vector<char> buf;
|
|
767
|
+
set_error_message(buf, cause,
|
|
768
|
+
cause == MEMORY_ERROR ? "out of memory" : "terminated");
|
|
769
|
+
err = string_from_bytes(st.isolate, buf);
|
|
770
|
+
}
|
|
753
771
|
if (!reply(st, result, err)) {
|
|
754
772
|
assert(try_catch.HasCaught());
|
|
755
773
|
goto fail; // retry; can be termination exception
|
|
756
774
|
}
|
|
775
|
+
if (!nested &&
|
|
776
|
+
(st.terminate_requested.load() || st.isolate->IsExecutionTerminating())) {
|
|
777
|
+
// A callback invoked by the serializer (for example a getter) can
|
|
778
|
+
// request termination after the last suspend_termination check. Do not
|
|
779
|
+
// leak that request into the next outermost evaluation.
|
|
780
|
+
st.terminate_requested.store(0);
|
|
781
|
+
st.isolate->CancelTerminateExecution();
|
|
782
|
+
st.err_reason = NO_ERROR;
|
|
783
|
+
}
|
|
757
784
|
restore_termination(st, preserve_termination);
|
|
758
785
|
}
|
|
759
786
|
|
|
@@ -818,10 +845,35 @@ fail:
|
|
|
818
845
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
819
846
|
if (cause) result = v8::Undefined(st.isolate);
|
|
820
847
|
auto err = to_error(st, &try_catch, cause);
|
|
848
|
+
// Error formatting can run user JS (for example Error.prepareStackTrace)
|
|
849
|
+
// which may call Context#stop. Do not let that termination leak into the
|
|
850
|
+
// reply or the next evaluation.
|
|
851
|
+
bool terminated_during_error = false;
|
|
852
|
+
if (st.terminate_requested.load() || st.isolate->IsExecutionTerminating()) {
|
|
853
|
+
terminated_during_error = true;
|
|
854
|
+
preserve_termination =
|
|
855
|
+
suspend_termination(st, nested, cause) || preserve_termination;
|
|
856
|
+
}
|
|
857
|
+
if (terminated_during_error) {
|
|
858
|
+
result = v8::Undefined(st.isolate);
|
|
859
|
+
std::vector<char> buf;
|
|
860
|
+
set_error_message(buf, cause,
|
|
861
|
+
cause == MEMORY_ERROR ? "out of memory" : "terminated");
|
|
862
|
+
err = string_from_bytes(st.isolate, buf);
|
|
863
|
+
}
|
|
821
864
|
if (!reply(st, result, err)) {
|
|
822
865
|
assert(try_catch.HasCaught());
|
|
823
866
|
goto fail; // retry; can be termination exception
|
|
824
867
|
}
|
|
868
|
+
if (!nested &&
|
|
869
|
+
(st.terminate_requested.load() || st.isolate->IsExecutionTerminating())) {
|
|
870
|
+
// A callback invoked by the serializer (for example a getter) can
|
|
871
|
+
// request termination after the last suspend_termination check. Do not
|
|
872
|
+
// leak that request into the next outermost evaluation.
|
|
873
|
+
st.terminate_requested.store(0);
|
|
874
|
+
st.isolate->CancelTerminateExecution();
|
|
875
|
+
st.err_reason = NO_ERROR;
|
|
876
|
+
}
|
|
825
877
|
restore_termination(st, preserve_termination);
|
|
826
878
|
}
|
|
827
879
|
|
|
@@ -936,6 +988,138 @@ fail:
|
|
|
936
988
|
reply_retry(st, result);
|
|
937
989
|
}
|
|
938
990
|
|
|
991
|
+
static uint32_t read_le32(const uint8_t *p)
|
|
992
|
+
{
|
|
993
|
+
return static_cast<uint32_t>(p[0])
|
|
994
|
+
| static_cast<uint32_t>(p[1]) << 8
|
|
995
|
+
| static_cast<uint32_t>(p[2]) << 16
|
|
996
|
+
| static_cast<uint32_t>(p[3]) << 24;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
static bool snapshot_checksum_matches(const uint8_t *data, size_t n)
|
|
1000
|
+
{
|
|
1001
|
+
if (n < 13)
|
|
1002
|
+
return false;
|
|
1003
|
+
uint32_t expected = read_le32(data + 8);
|
|
1004
|
+
|
|
1005
|
+
// V8 uses zlib's adler32 when built with V8_USE_ZLIB, otherwise a
|
|
1006
|
+
// Fletcher-32 variant. Accept either so this check remains portable
|
|
1007
|
+
// across libv8-node builds. This is a corruption check, not a sandbox:
|
|
1008
|
+
// snapshots are still trusted-input-only.
|
|
1009
|
+
uint32_t s1 = 0, s2 = 0;
|
|
1010
|
+
for (size_t i = 12; i < n; i++) {
|
|
1011
|
+
s1 = (s1 + data[i]) % 65521;
|
|
1012
|
+
s2 = (s2 + s1) % 65521;
|
|
1013
|
+
}
|
|
1014
|
+
if ((s2 << 16 | s1) == expected)
|
|
1015
|
+
return true;
|
|
1016
|
+
|
|
1017
|
+
s1 = s2 = 0;
|
|
1018
|
+
for (size_t i = 12; i < n; i++) {
|
|
1019
|
+
s1 = (s1 + data[i]) % 65535;
|
|
1020
|
+
s2 = (s2 + s1) % 65535;
|
|
1021
|
+
}
|
|
1022
|
+
return (s2 << 16 | s1) == expected;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// The header/section offsets below mirror V8 13.6's SnapshotImpl. Keep them
|
|
1026
|
+
// in sync with libv8-node; the fallback is intentionally limited to preventing
|
|
1027
|
+
// V8's fatal deserializer checks, not to sandboxing untrusted snapshots.
|
|
1028
|
+
extern "C" int v8_snapshot_validate(const uint8_t *data, size_t n,
|
|
1029
|
+
char *err, size_t errlen)
|
|
1030
|
+
{
|
|
1031
|
+
#define SNAPSHOT_INVALID(msg) \
|
|
1032
|
+
do { \
|
|
1033
|
+
if (err && errlen) \
|
|
1034
|
+
snprintf(err, errlen, "%s", msg); \
|
|
1035
|
+
return -1; \
|
|
1036
|
+
} while (0)
|
|
1037
|
+
|
|
1038
|
+
if (n == 0)
|
|
1039
|
+
return 0; // Snapshot.load("") means "use V8's built-in snapshot".
|
|
1040
|
+
if (n > static_cast<size_t>(std::numeric_limits<int>::max()))
|
|
1041
|
+
SNAPSHOT_INVALID("snapshot is too large");
|
|
1042
|
+
if (n < 88)
|
|
1043
|
+
SNAPSHOT_INVALID("snapshot is too short");
|
|
1044
|
+
|
|
1045
|
+
v8::StartupData blob{reinterpret_cast<const char*>(data),
|
|
1046
|
+
static_cast<int>(n)};
|
|
1047
|
+
if (!blob.IsValid())
|
|
1048
|
+
SNAPSHOT_INVALID("snapshot version mismatch");
|
|
1049
|
+
|
|
1050
|
+
uint32_t num_contexts = read_le32(data);
|
|
1051
|
+
uint32_t rehashability = read_le32(data + 4);
|
|
1052
|
+
if (rehashability > 1)
|
|
1053
|
+
SNAPSHOT_INVALID("snapshot header is invalid");
|
|
1054
|
+
if (num_contexts == 0)
|
|
1055
|
+
SNAPSHOT_INVALID("snapshot has no contexts");
|
|
1056
|
+
if (num_contexts > (n - 88) / 4)
|
|
1057
|
+
SNAPSHOT_INVALID("snapshot context table is invalid");
|
|
1058
|
+
|
|
1059
|
+
const size_t pointer_size = sizeof(void*);
|
|
1060
|
+
size_t header_size = 88 + static_cast<size_t>(num_contexts) * 4;
|
|
1061
|
+
size_t startup_offset =
|
|
1062
|
+
(header_size + pointer_size - 1) & ~(pointer_size - 1);
|
|
1063
|
+
if (startup_offset > n)
|
|
1064
|
+
SNAPSHOT_INVALID("snapshot context table is invalid");
|
|
1065
|
+
|
|
1066
|
+
uint32_t read_only_offset = read_le32(data + 80);
|
|
1067
|
+
uint32_t shared_heap_offset = read_le32(data + 84);
|
|
1068
|
+
if (read_only_offset <= startup_offset || read_only_offset > n)
|
|
1069
|
+
SNAPSHOT_INVALID("snapshot read-only section is invalid");
|
|
1070
|
+
if (shared_heap_offset <= read_only_offset || shared_heap_offset > n)
|
|
1071
|
+
SNAPSHOT_INVALID("snapshot shared-heap section is invalid");
|
|
1072
|
+
|
|
1073
|
+
size_t previous_offset = shared_heap_offset;
|
|
1074
|
+
for (uint32_t i = 0; i < num_contexts; i++) {
|
|
1075
|
+
uint32_t offset = read_le32(data + 88 + static_cast<size_t>(i) * 4);
|
|
1076
|
+
if (offset <= previous_offset || offset >= n)
|
|
1077
|
+
SNAPSHOT_INVALID("snapshot context section is invalid");
|
|
1078
|
+
previous_offset = offset;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (!snapshot_checksum_matches(data, n))
|
|
1082
|
+
SNAPSHOT_INVALID("snapshot checksum mismatch");
|
|
1083
|
+
|
|
1084
|
+
return 0;
|
|
1085
|
+
#undef SNAPSHOT_INVALID
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Keep all guard state in a JavaScript closure. The setup script returns a
|
|
1089
|
+
// query function to C++; do not install a native callback or External global,
|
|
1090
|
+
// because a retained property descriptor would otherwise make CreateBlob fail
|
|
1091
|
+
// with "Unknown external reference".
|
|
1092
|
+
static const char snapshot_intl_setup_source[] = R"js(
|
|
1093
|
+
;(function() {
|
|
1094
|
+
const original = globalThis.Intl
|
|
1095
|
+
let used = false
|
|
1096
|
+
const getter = function() {
|
|
1097
|
+
used = true
|
|
1098
|
+
return original
|
|
1099
|
+
}
|
|
1100
|
+
const setter = function(value) {
|
|
1101
|
+
used = true
|
|
1102
|
+
}
|
|
1103
|
+
if (!original) {
|
|
1104
|
+
const query = function() { return used }
|
|
1105
|
+
query.getter = getter
|
|
1106
|
+
return query
|
|
1107
|
+
}
|
|
1108
|
+
Object.defineProperty(globalThis, "Intl", {
|
|
1109
|
+
configurable: true,
|
|
1110
|
+
enumerable: false,
|
|
1111
|
+
get: getter,
|
|
1112
|
+
set: setter
|
|
1113
|
+
})
|
|
1114
|
+
const query = function() { return used }
|
|
1115
|
+
query.getter = getter
|
|
1116
|
+
return query
|
|
1117
|
+
})()
|
|
1118
|
+
)js";
|
|
1119
|
+
|
|
1120
|
+
static const char snapshot_intl_error[] =
|
|
1121
|
+
"Intl is not supported in Snapshot.new scripts";
|
|
1122
|
+
|
|
939
1123
|
int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
940
1124
|
const v8::String::Utf8Value& code,
|
|
941
1125
|
v8::StartupData blob, v8::StartupData *result,
|
|
@@ -943,40 +1127,107 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
943
1127
|
{
|
|
944
1128
|
// SnapshotCreator takes ownership of isolate
|
|
945
1129
|
v8::Isolate *isolate = v8::Isolate::Allocate();
|
|
946
|
-
|
|
1130
|
+
// An empty loaded snapshot means "start from V8's built-in snapshot", just
|
|
1131
|
+
// like Context.new. Passing a non-null StartupData with raw_size == 0 to
|
|
1132
|
+
// SnapshotCreator trips CHECK(!artifacts) during warmup.
|
|
1133
|
+
v8::StartupData *existing_blob =
|
|
1134
|
+
(is_warmup && blob.data && blob.raw_size > 0) ? &blob : nullptr;
|
|
947
1135
|
v8::SnapshotCreator snapshot_creator(isolate, nullptr, existing_blob);
|
|
948
1136
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
949
|
-
v8::HandleScope handle_scope(isolate);
|
|
950
1137
|
v8::TryCatch try_catch(isolate);
|
|
951
1138
|
try_catch.SetVerbose(verbose_exceptions);
|
|
952
|
-
auto filename = is_warmup
|
|
953
|
-
? v8::String::NewFromUtf8Literal(isolate, "<warmup>")
|
|
954
|
-
: v8::String::NewFromUtf8Literal(isolate, "<snapshot>");
|
|
955
1139
|
auto mode = is_warmup
|
|
956
1140
|
? v8::SnapshotCreator::FunctionCodeHandling::kKeep
|
|
957
1141
|
: v8::SnapshotCreator::FunctionCodeHandling::kClear;
|
|
958
1142
|
int cause = INTERNAL_ERROR;
|
|
959
1143
|
{
|
|
1144
|
+
// Keep script-created values out of the scope that is alive when
|
|
1145
|
+
// CreateBlob() runs. V8's own warmup implementation uses separate
|
|
1146
|
+
// scopes; sharing one scope leaves Intl foreign handles visible to the
|
|
1147
|
+
// snapshot handle checker and aborts with
|
|
1148
|
+
// CheckGlobalAndEternalHandles failed.
|
|
1149
|
+
v8::HandleScope handle_scope(isolate);
|
|
1150
|
+
auto filename = is_warmup
|
|
1151
|
+
? v8::String::NewFromUtf8Literal(isolate, "<warmup>")
|
|
1152
|
+
: v8::String::NewFromUtf8Literal(isolate, "<snapshot>");
|
|
960
1153
|
auto context = v8::Context::New(isolate);
|
|
961
1154
|
v8::Context::Scope context_scope(context);
|
|
1155
|
+
auto intl_key = v8::String::NewFromUtf8Literal(isolate, "Intl");
|
|
1156
|
+
v8::Local<v8::Value> intl_original;
|
|
1157
|
+
v8::Local<v8::Function> intl_state_fn;
|
|
1158
|
+
v8::Local<v8::Function> intl_guard_getter;
|
|
1159
|
+
bool intl_guarded = false;
|
|
1160
|
+
if (!context->Global()->Get(context, intl_key).ToLocal(&intl_original)) {
|
|
1161
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1162
|
+
"failed to inspect snapshot Intl");
|
|
1163
|
+
return INTERNAL_ERROR;
|
|
1164
|
+
}
|
|
1165
|
+
if (!is_warmup && intl_original->IsObject()) {
|
|
1166
|
+
auto intl_setup_source = v8::String::NewFromUtf8Literal(
|
|
1167
|
+
isolate, snapshot_intl_setup_source);
|
|
1168
|
+
v8::Local<v8::Script> intl_setup_script;
|
|
1169
|
+
v8::Local<v8::Value> intl_state;
|
|
1170
|
+
if (!v8::Script::Compile(context, intl_setup_source)
|
|
1171
|
+
.ToLocal(&intl_setup_script) ||
|
|
1172
|
+
!intl_setup_script->Run(context).ToLocal(&intl_state) ||
|
|
1173
|
+
!intl_state->IsFunction()) {
|
|
1174
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1175
|
+
"failed to initialize snapshot Intl guard");
|
|
1176
|
+
return INTERNAL_ERROR;
|
|
1177
|
+
}
|
|
1178
|
+
intl_state_fn = intl_state.As<v8::Function>();
|
|
1179
|
+
v8::Local<v8::Value> getter;
|
|
1180
|
+
auto getter_key =
|
|
1181
|
+
v8::String::NewFromUtf8Literal(isolate, "getter");
|
|
1182
|
+
if (!intl_state_fn->Get(context, getter_key).ToLocal(&getter) ||
|
|
1183
|
+
!getter->IsFunction()) {
|
|
1184
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1185
|
+
"failed to initialize snapshot Intl guard");
|
|
1186
|
+
return INTERNAL_ERROR;
|
|
1187
|
+
}
|
|
1188
|
+
intl_guard_getter = getter.As<v8::Function>();
|
|
1189
|
+
intl_guarded = true;
|
|
1190
|
+
}
|
|
962
1191
|
v8::Local<v8::String> source;
|
|
963
1192
|
auto type = v8::NewStringType::kNormal;
|
|
964
1193
|
if (!v8::String::NewFromUtf8(isolate, *code, type, code.length()).ToLocal(&source)) {
|
|
965
1194
|
v8::String::Utf8Value s(isolate, try_catch.Exception());
|
|
966
1195
|
if (!set_error_message(*errbuf, cause, s))
|
|
967
1196
|
set_error_message(*errbuf, cause, "unexpected failure");
|
|
968
|
-
|
|
1197
|
+
return cause;
|
|
969
1198
|
}
|
|
970
1199
|
v8::ScriptOrigin origin(filename);
|
|
971
1200
|
v8::Local<v8::Script> script;
|
|
972
1201
|
cause = PARSE_ERROR;
|
|
973
1202
|
if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
|
|
974
|
-
|
|
1203
|
+
auto m = try_catch.Message();
|
|
1204
|
+
if (m.IsEmpty()) {
|
|
1205
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1206
|
+
return cause;
|
|
1207
|
+
}
|
|
1208
|
+
v8::String::Utf8Value s(isolate, m->Get());
|
|
1209
|
+
v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
|
|
1210
|
+
auto line = m->GetLineNumber(context).FromMaybe(0);
|
|
1211
|
+
auto column = m->GetStartColumn(context).FromMaybe(0);
|
|
1212
|
+
if (*s && *name) {
|
|
1213
|
+
errbuf->clear();
|
|
1214
|
+
errbuf->push_back(static_cast<char>(cause));
|
|
1215
|
+
append_utf8(*errbuf, s);
|
|
1216
|
+
append_literal(*errbuf, "\n");
|
|
1217
|
+
append_utf8(*errbuf, name);
|
|
1218
|
+
append_format(*errbuf, ":%d:%d", line, column);
|
|
1219
|
+
} else {
|
|
1220
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1221
|
+
}
|
|
1222
|
+
return cause;
|
|
975
1223
|
}
|
|
976
1224
|
cause = RUNTIME_ERROR;
|
|
977
1225
|
if (script->Run(context).IsEmpty()) {
|
|
978
|
-
err:
|
|
979
1226
|
auto m = try_catch.Message();
|
|
1227
|
+
if (m.IsEmpty()) {
|
|
1228
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1229
|
+
return cause;
|
|
1230
|
+
}
|
|
980
1231
|
v8::String::Utf8Value s(isolate, m->Get());
|
|
981
1232
|
v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
|
|
982
1233
|
auto line = m->GetLineNumber(context).FromMaybe(0);
|
|
@@ -991,20 +1242,82 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
991
1242
|
} else {
|
|
992
1243
|
set_error_message(*errbuf, cause, "unexpected failure");
|
|
993
1244
|
}
|
|
994
|
-
|
|
1245
|
+
return cause;
|
|
1246
|
+
}
|
|
1247
|
+
if (intl_guarded) {
|
|
1248
|
+
v8::Local<v8::Value> used;
|
|
1249
|
+
auto recv = v8::Undefined(isolate);
|
|
1250
|
+
if (!intl_state_fn->Call(context, recv, 0, nullptr)
|
|
1251
|
+
.ToLocal(&used)) {
|
|
1252
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1253
|
+
"failed to inspect snapshot Intl guard");
|
|
1254
|
+
return INTERNAL_ERROR;
|
|
1255
|
+
}
|
|
1256
|
+
if (used->IsTrue()) {
|
|
1257
|
+
set_error_message(*errbuf, RUNTIME_ERROR, snapshot_intl_error);
|
|
1258
|
+
return RUNTIME_ERROR;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (intl_guarded) {
|
|
1262
|
+
// Only replace our accessor. If the script deliberately replaced
|
|
1263
|
+
// or deleted Intl, leave that change in the snapshot.
|
|
1264
|
+
v8::Local<v8::Value> descriptor;
|
|
1265
|
+
if (!context->Global()
|
|
1266
|
+
->GetOwnPropertyDescriptor(context, intl_key)
|
|
1267
|
+
.ToLocal(&descriptor)) {
|
|
1268
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1269
|
+
"failed to inspect Intl after snapshot script");
|
|
1270
|
+
return INTERNAL_ERROR;
|
|
1271
|
+
}
|
|
1272
|
+
bool restore_intl = false;
|
|
1273
|
+
if (descriptor->IsObject()) {
|
|
1274
|
+
auto get_key =
|
|
1275
|
+
v8::String::NewFromUtf8Literal(isolate, "get");
|
|
1276
|
+
v8::Local<v8::Value> getter_descriptor;
|
|
1277
|
+
if (!descriptor.As<v8::Object>()
|
|
1278
|
+
->GetOwnPropertyDescriptor(context, get_key)
|
|
1279
|
+
.ToLocal(&getter_descriptor)) {
|
|
1280
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1281
|
+
"failed to inspect Intl after snapshot script");
|
|
1282
|
+
return INTERNAL_ERROR;
|
|
1283
|
+
}
|
|
1284
|
+
if (getter_descriptor->IsObject()) {
|
|
1285
|
+
v8::Local<v8::Value> getter;
|
|
1286
|
+
auto value_key =
|
|
1287
|
+
v8::String::NewFromUtf8Literal(isolate, "value");
|
|
1288
|
+
if (!getter_descriptor.As<v8::Object>()
|
|
1289
|
+
->Get(context, value_key)
|
|
1290
|
+
.ToLocal(&getter)) {
|
|
1291
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1292
|
+
"failed to inspect Intl after snapshot script");
|
|
1293
|
+
return INTERNAL_ERROR;
|
|
1294
|
+
}
|
|
1295
|
+
restore_intl =
|
|
1296
|
+
getter->IsFunction() &&
|
|
1297
|
+
getter->SameValue(intl_guard_getter);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (restore_intl &&
|
|
1301
|
+
!context->Global()
|
|
1302
|
+
->DefineOwnProperty(
|
|
1303
|
+
context, intl_key, intl_original,
|
|
1304
|
+
v8::PropertyAttribute::DontEnum)
|
|
1305
|
+
.FromMaybe(false)) {
|
|
1306
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1307
|
+
"failed to restore Intl after snapshot script");
|
|
1308
|
+
return INTERNAL_ERROR;
|
|
1309
|
+
}
|
|
995
1310
|
}
|
|
996
|
-
cause = INTERNAL_ERROR;
|
|
997
1311
|
if (!is_warmup) snapshot_creator.SetDefaultContext(context);
|
|
998
1312
|
}
|
|
999
1313
|
if (is_warmup) {
|
|
1314
|
+
v8::HandleScope handle_scope(isolate);
|
|
1000
1315
|
isolate->ContextDisposedNotification(false);
|
|
1001
1316
|
auto context = v8::Context::New(isolate);
|
|
1002
1317
|
snapshot_creator.SetDefaultContext(context);
|
|
1003
1318
|
}
|
|
1004
1319
|
*result = snapshot_creator.CreateBlob(mode);
|
|
1005
|
-
|
|
1006
|
-
fail:
|
|
1007
|
-
return cause;
|
|
1320
|
+
return NO_ERROR;
|
|
1008
1321
|
}
|
|
1009
1322
|
|
|
1010
1323
|
// response is errback [result, err] array
|
|
@@ -48,6 +48,10 @@ void v8_perform_microtask_checkpoint(struct State *pst);
|
|
|
48
48
|
void v8_pump_message_loop(struct State *pst);
|
|
49
49
|
void v8_snapshot(struct State *pst, const uint8_t *p, size_t n);
|
|
50
50
|
void v8_warmup(struct State *pst, const uint8_t *p, size_t n);
|
|
51
|
+
// Returns 0 for a valid blob (including an empty blob), otherwise nonzero and
|
|
52
|
+
// writes a short reason to |err|. This performs header/section/checksum checks
|
|
53
|
+
// before V8's fatal snapshot deserializer sees the blob.
|
|
54
|
+
int v8_snapshot_validate(const uint8_t *data, size_t n, char *err, size_t errlen);
|
|
51
55
|
void v8_low_memory_notification(struct State *pst);
|
|
52
56
|
void v8_terminate_execution(struct State *pst); // called from ruby thread
|
|
53
57
|
void v8_terminate_watchdog(struct State *pst); // called from watchdog thread
|
|
@@ -243,33 +243,38 @@ static void ser_num(Ser *s, double v)
|
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
// ser_bigint: |
|
|
247
|
-
|
|
246
|
+
// ser_bigint: |p| points to |n| bytes, interpreted as little-endian
|
|
247
|
+
// 64-bit words. Keep the interface byte-oriented so callers don't need to
|
|
248
|
+
// expose a concrete word type.
|
|
249
|
+
static void ser_bigint(Ser *s, const void *p, size_t n, int sign)
|
|
248
250
|
{
|
|
251
|
+
const uint8_t *bytes;
|
|
252
|
+
|
|
249
253
|
if (*s->err)
|
|
250
254
|
return;
|
|
251
255
|
if (n % 8) {
|
|
252
256
|
snprintf(s->err, sizeof(s->err), "bad bigint");
|
|
253
257
|
return;
|
|
254
258
|
}
|
|
259
|
+
bytes = p;
|
|
255
260
|
w_byte(s, 'Z');
|
|
256
261
|
// chop off high all-zero words
|
|
257
|
-
n
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
break;
|
|
261
|
-
if (n == (size_t)-1) {
|
|
262
|
+
while (n > 0 && bytes[n-1] == 0)
|
|
263
|
+
n--;
|
|
264
|
+
if (n == 0) {
|
|
262
265
|
w_byte(s, 0); // normalized zero
|
|
263
266
|
} else {
|
|
264
|
-
n =
|
|
267
|
+
n = (n + 7) & ~(size_t)7;
|
|
265
268
|
w_varint(s, 2*n + (sign < 0));
|
|
266
|
-
w(s,
|
|
269
|
+
w(s, bytes, n);
|
|
267
270
|
}
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
static void ser_int(Ser *s, int64_t v)
|
|
271
274
|
{
|
|
275
|
+
uint8_t bytes[8];
|
|
272
276
|
uint64_t t;
|
|
277
|
+
size_t i;
|
|
273
278
|
int sign;
|
|
274
279
|
|
|
275
280
|
if (*s->err)
|
|
@@ -279,8 +284,10 @@ static void ser_int(Ser *s, int64_t v)
|
|
|
279
284
|
if (v <= INT64_MAX/1024)
|
|
280
285
|
return ser_num(s, v);
|
|
281
286
|
t = v < 0 ? (uint64_t)(-(v + 1)) + 1 : (uint64_t)v;
|
|
287
|
+
for (i = 0; i < sizeof(bytes); i++)
|
|
288
|
+
bytes[i] = t >> (8*i);
|
|
282
289
|
sign = v < 0 ? -1 : 1;
|
|
283
|
-
ser_bigint(s,
|
|
290
|
+
ser_bigint(s, bytes, sizeof(bytes), sign);
|
|
284
291
|
} else {
|
|
285
292
|
w_byte(s, 'I');
|
|
286
293
|
w_zigzag(s, v);
|
data/lib/mini_racer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mini_racer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.22.
|
|
4
|
+
version: 0.22.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -164,9 +164,9 @@ licenses:
|
|
|
164
164
|
- MIT
|
|
165
165
|
metadata:
|
|
166
166
|
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
|
167
|
-
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.22.
|
|
168
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.22.
|
|
169
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.22.
|
|
167
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.22.2/CHANGELOG
|
|
168
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.22.2
|
|
169
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.22.2
|
|
170
170
|
rdoc_options: []
|
|
171
171
|
require_paths:
|
|
172
172
|
- lib
|