mini_racer 0.21.1 → 0.21.3
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 +14 -0
- data/README.md +32 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +246 -59
- data/ext/mini_racer_extension/mini_racer_v8.cc +180 -54
- data/ext/mini_racer_extension/mini_racer_v8.h +1 -0
- data/ext/mini_racer_extension/serde.c +1 -1
- data/lib/mini_racer/truffleruby.rb +27 -2
- data/lib/mini_racer/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e370b2c6cdb60e5081d5f97840cff28489119f0225a3c9a74ed7f6859caf40e
|
|
4
|
+
data.tar.gz: 7405ed87278cbaa1d155bc72f8c99d8e8c744df743252eb4128b9ae9ff899d07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c9f53c6a9052cca6a5129d023ecece59613c40c17f4c7666c728f8f6327200e34d707557ea4dcd9b626aed332b327c6c3e3f90e365c630adbec0b2d02c4b1c1
|
|
7
|
+
data.tar.gz: 4dc0348a006f7620c4c9688c0b877f2bd9500b7ece37fba4fa6e868006af1517e4279537693fd87b2f18c2b0602067084e2332e2bf9fa862504ebaab4ddbbcfc
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
- 0.21.3 - 18-06-2026
|
|
2
|
+
- Fix `:single_threaded` contexts inherited across `fork` by recovering idle reusable native runners in the child process without falling back to per-dispatch native thread spawning
|
|
3
|
+
- Avoid intermittent heap corruption during `:single_threaded` context finalization, especially when forked children exit normally after touching inherited contexts
|
|
4
|
+
- Avoid finalizer hangs when a forked child garbage-collects a non-idle inherited `:single_threaded` context
|
|
5
|
+
- Allow Ruby thread interrupts, process shutdown, and cross-thread `Context#dispose` to terminate busy `:single_threaded` JavaScript execution instead of hanging
|
|
6
|
+
- Make `Context#dispose` while an attached Ruby callback is active either terminate safely or raise instead of deadlocking
|
|
7
|
+
|
|
8
|
+
- 0.21.2 - 11-06-2026
|
|
9
|
+
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
|
|
10
|
+
- Fix native memory leaks in `Context#heap_snapshot`/`Context#write_heap_snapshot`; thanks to Pranjali Thakur from depthfirst.com
|
|
11
|
+
- Fix large integral JavaScript numbers wrapping to negative Ruby integers; thanks to Pranjali Thakur from depthfirst.com
|
|
12
|
+
- Fix Ruby callback exceptions with embedded NUL bytes permanently deadlocking a context; thanks to Pranjali Thakur from depthfirst.com
|
|
13
|
+
- Preserve embedded NUL bytes in JavaScript exception messages and attached function names, and reject unsafe V8 flags containing NUL bytes or overly long values
|
|
14
|
+
|
|
1
15
|
- 0.21.1 - 25-05-2026
|
|
2
16
|
- Run `:single_threaded` V8 dispatches on a reusable mini_racer-owned native thread so V8 does not execute on Ruby-owned threads
|
|
3
17
|
- Stop and join the reusable `:single_threaded` runner when contexts are disposed
|
data/README.md
CHANGED
|
@@ -348,6 +348,38 @@ Performance is slightly better than running `context.eval("hello('George')")` si
|
|
|
348
348
|
* compilation of eval'd string is avoided
|
|
349
349
|
* function arguments don't need to be converted to JSON
|
|
350
350
|
|
|
351
|
+
### Microtask checkpoints
|
|
352
|
+
|
|
353
|
+
V8 drains its microtask queue (e.g. callbacks queued via `Promise.resolve().then(...)`) automatically when script execution returns to the embedder, so most code "just works":
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
context = MiniRacer::Context.new
|
|
357
|
+
context.eval(<<~JS)
|
|
358
|
+
let x = 0;
|
|
359
|
+
Promise.resolve().then(() => x = 99);
|
|
360
|
+
JS
|
|
361
|
+
context.eval("x")
|
|
362
|
+
# => 99
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
When JavaScript invokes a Ruby callback synchronously and you need queued microtasks to drain mid-execution — e.g. for spec-compliant ordering across a chain of synchronous `dispatchEvent` listeners — call `context.perform_microtask_checkpoint` from the callback:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
context = MiniRacer::Context.new
|
|
369
|
+
context.attach("drain", -> { context.perform_microtask_checkpoint })
|
|
370
|
+
context.eval(<<~JS)
|
|
371
|
+
globalThis.log = [];
|
|
372
|
+
Promise.resolve().then(() => log.push("microtask"));
|
|
373
|
+
log.push("before");
|
|
374
|
+
drain();
|
|
375
|
+
log.push("after");
|
|
376
|
+
JS
|
|
377
|
+
context.eval("log")
|
|
378
|
+
# => ["before", "microtask", "after"]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Without `drain()` the order would be `["before", "after", "microtask"]` because the microtask only runs once the outermost script returns. `perform_microtask_checkpoint` is a thin wrapper over V8's `MicrotasksScope::PerformCheckpoint`.
|
|
382
|
+
|
|
351
383
|
## Performance
|
|
352
384
|
|
|
353
385
|
The `bench` folder contains benchmark.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <string.h>
|
|
6
6
|
#include <pthread.h>
|
|
7
7
|
#include <unistd.h>
|
|
8
|
+
#include <errno.h>
|
|
8
9
|
#include <math.h>
|
|
9
10
|
|
|
10
11
|
#if defined(__linux__) && !defined(__GLIBC__)
|
|
@@ -159,6 +160,7 @@ typedef struct Snapshot {
|
|
|
159
160
|
} Snapshot;
|
|
160
161
|
|
|
161
162
|
static void context_destroy(Context *c);
|
|
163
|
+
static void context_abandon(Context *c);
|
|
162
164
|
static void context_free(void *arg);
|
|
163
165
|
static void context_mark(void *arg);
|
|
164
166
|
static size_t context_size(const void *arg);
|
|
@@ -227,6 +229,7 @@ struct rendezvous_nogvl
|
|
|
227
229
|
{
|
|
228
230
|
Context *context;
|
|
229
231
|
Buf *req, *res;
|
|
232
|
+
atomic_int active;
|
|
230
233
|
};
|
|
231
234
|
|
|
232
235
|
struct rendezvous_des
|
|
@@ -316,13 +319,19 @@ static void des_bool(void *arg, int v)
|
|
|
316
319
|
|
|
317
320
|
static void des_int(void *arg, int64_t v)
|
|
318
321
|
{
|
|
319
|
-
put(arg,
|
|
322
|
+
put(arg, LL2NUM((LONG_LONG)v));
|
|
320
323
|
}
|
|
321
324
|
|
|
322
325
|
static void des_num(void *arg, double v)
|
|
323
326
|
{
|
|
324
|
-
if (isfinite(v) && v == trunc(v)
|
|
325
|
-
|
|
327
|
+
if (isfinite(v) && v == trunc(v)) {
|
|
328
|
+
// INT64_MAX is not exactly representable as a double: it rounds up to
|
|
329
|
+
// 2^63, which would let 2^63 through and make the cast undefined.
|
|
330
|
+
if (v >= -0x1p63 && v < 0x1p63) {
|
|
331
|
+
put(arg, LL2NUM((LONG_LONG)v));
|
|
332
|
+
} else {
|
|
333
|
+
put(arg, rb_dbl2big(v));
|
|
334
|
+
}
|
|
326
335
|
} else {
|
|
327
336
|
put(arg, DBL2NUM(v));
|
|
328
337
|
}
|
|
@@ -364,19 +373,8 @@ static void des_bigint(void *arg, const void *p, size_t n, int sign)
|
|
|
364
373
|
if (t >> 63)
|
|
365
374
|
*a++ = 0; // suppress sign extension
|
|
366
375
|
v = rb_big_unpack(limbs, a-limbs);
|
|
367
|
-
if (sign < 0)
|
|
368
|
-
|
|
369
|
-
switch (TYPE(v)) {
|
|
370
|
-
case T_BIGNUM:
|
|
371
|
-
v = rb_big_mul(v, LONG2FIX(-1));
|
|
372
|
-
break;
|
|
373
|
-
case T_FIXNUM:
|
|
374
|
-
v = LONG2FIX(-1 * FIX2LONG(v));
|
|
375
|
-
break;
|
|
376
|
-
default:
|
|
377
|
-
abort();
|
|
378
|
-
}
|
|
379
|
-
}
|
|
376
|
+
if (sign < 0)
|
|
377
|
+
v = rb_funcall(v, rb_intern("-@"), 0);
|
|
380
378
|
put(c, v);
|
|
381
379
|
}
|
|
382
380
|
|
|
@@ -810,6 +808,7 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
|
810
808
|
case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
|
|
811
809
|
case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
|
|
812
810
|
case 'H': return v8_heap_snapshot(c->pst);
|
|
811
|
+
case 'M': return v8_perform_microtask_checkpoint(c->pst);
|
|
813
812
|
case 'P': return v8_pump_message_loop(c->pst);
|
|
814
813
|
case 'S': return v8_heap_stats(c->pst);
|
|
815
814
|
case 'T': return v8_snapshot(c->pst, p+1, n-1);
|
|
@@ -874,8 +873,14 @@ void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
|
|
|
874
873
|
{
|
|
875
874
|
buf_reset(&c->req);
|
|
876
875
|
pthread_cond_signal(&c->cv);
|
|
877
|
-
while (!c->req.len)
|
|
876
|
+
while (!c->req.len && !atomic_load(&c->quit))
|
|
878
877
|
pthread_cond_wait(&c->cv, &c->mtx);
|
|
878
|
+
if (!c->req.len && atomic_load(&c->quit)) {
|
|
879
|
+
static const uint8_t disposed[] = "edisposed context";
|
|
880
|
+
*p = disposed;
|
|
881
|
+
*n = sizeof(disposed) - 1;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
879
884
|
buf_reset(&c->res);
|
|
880
885
|
*p = c->req.buf;
|
|
881
886
|
*n = c->req.len;
|
|
@@ -936,6 +941,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
936
941
|
Context *c;
|
|
937
942
|
DesCtx d;
|
|
938
943
|
Buf *b;
|
|
944
|
+
long id;
|
|
939
945
|
|
|
940
946
|
a = (void *)arg;
|
|
941
947
|
b = a->res;
|
|
@@ -945,7 +951,12 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
945
951
|
DesCtx_init(&d);
|
|
946
952
|
args = deserialize1(&d, b->buf+1, b->len-1); // skip 'c' marker
|
|
947
953
|
func = rb_ary_pop(args); // callback id
|
|
948
|
-
|
|
954
|
+
if (!RB_INTEGER_TYPE_P(func))
|
|
955
|
+
rb_raise(runtime_error, "bad callback id");
|
|
956
|
+
id = NUM2LONG(func);
|
|
957
|
+
if (id < 0 || id >= RARRAY_LEN(c->procs))
|
|
958
|
+
rb_raise(runtime_error, "bad callback id");
|
|
959
|
+
func = rb_ary_entry(c->procs, id);
|
|
949
960
|
return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
|
|
950
961
|
}
|
|
951
962
|
|
|
@@ -981,9 +992,9 @@ fail:
|
|
|
981
992
|
ser_init0(&s); // ruby exception pending
|
|
982
993
|
w_byte(&s, 'e'); // send ruby error message to v8 thread
|
|
983
994
|
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
984
|
-
err =
|
|
995
|
+
err = StringValuePtr(r);
|
|
985
996
|
if (err)
|
|
986
|
-
w(&s, err,
|
|
997
|
+
w(&s, err, RSTRING_LEN(r));
|
|
987
998
|
goto out;
|
|
988
999
|
}
|
|
989
1000
|
|
|
@@ -1005,6 +1016,33 @@ static void *single_threaded_runner(void *arg)
|
|
|
1005
1016
|
return NULL;
|
|
1006
1017
|
}
|
|
1007
1018
|
|
|
1019
|
+
static int single_threaded_recover_after_fork(Context *c)
|
|
1020
|
+
{
|
|
1021
|
+
pthread_condattr_t cattr;
|
|
1022
|
+
pid_t pid;
|
|
1023
|
+
int r;
|
|
1024
|
+
|
|
1025
|
+
pid = getpid();
|
|
1026
|
+
if (!c->single_threaded_thr_started || c->single_threaded_pid == pid)
|
|
1027
|
+
return 0;
|
|
1028
|
+
if (c->depth || c->req.len || c->res.len)
|
|
1029
|
+
return EBUSY;
|
|
1030
|
+
|
|
1031
|
+
if ((r = pthread_condattr_init(&cattr)))
|
|
1032
|
+
return r;
|
|
1033
|
+
#ifndef __APPLE__
|
|
1034
|
+
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
|
|
1035
|
+
#endif
|
|
1036
|
+
r = pthread_cond_init(&c->cv, &cattr);
|
|
1037
|
+
pthread_condattr_destroy(&cattr);
|
|
1038
|
+
if (r)
|
|
1039
|
+
return r;
|
|
1040
|
+
|
|
1041
|
+
c->single_threaded_thr_started = 0;
|
|
1042
|
+
c->single_threaded_pid = pid;
|
|
1043
|
+
return 0;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1008
1046
|
static int single_threaded_runner_start(Context *c)
|
|
1009
1047
|
{
|
|
1010
1048
|
pid_t pid;
|
|
@@ -1029,7 +1067,10 @@ static inline void *rendezvous_nogvl(void *arg)
|
|
|
1029
1067
|
|
|
1030
1068
|
a = arg;
|
|
1031
1069
|
c = a->context;
|
|
1070
|
+
if (single_threaded && (r = single_threaded_recover_after_fork(c)))
|
|
1071
|
+
return (void *)(intptr_t)r;
|
|
1032
1072
|
pthread_mutex_lock(&c->rr_mtx);
|
|
1073
|
+
atomic_store(&a->active, 1);
|
|
1033
1074
|
if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
|
|
1034
1075
|
fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
|
|
1035
1076
|
fflush(stderr);
|
|
@@ -1037,6 +1078,14 @@ static inline void *rendezvous_nogvl(void *arg)
|
|
|
1037
1078
|
c->depth++;
|
|
1038
1079
|
next:
|
|
1039
1080
|
pthread_mutex_lock(&c->mtx);
|
|
1081
|
+
if (atomic_load(&c->quit)) {
|
|
1082
|
+
buf_reset(a->req);
|
|
1083
|
+
pthread_mutex_unlock(&c->mtx);
|
|
1084
|
+
c->depth--;
|
|
1085
|
+
atomic_store(&a->active, 0);
|
|
1086
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1087
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1088
|
+
}
|
|
1040
1089
|
assert(c->req.len == 0);
|
|
1041
1090
|
assert(c->res.len == 0);
|
|
1042
1091
|
buf_move(a->req, &c->req); // v8 thread takes ownership of req
|
|
@@ -1046,6 +1095,7 @@ next:
|
|
|
1046
1095
|
buf_move(&c->req, a->req);
|
|
1047
1096
|
pthread_mutex_unlock(&c->mtx);
|
|
1048
1097
|
c->depth--;
|
|
1098
|
+
atomic_store(&a->active, 0);
|
|
1049
1099
|
pthread_mutex_unlock(&c->rr_mtx);
|
|
1050
1100
|
return (void *)(intptr_t)r;
|
|
1051
1101
|
}
|
|
@@ -1056,29 +1106,66 @@ next:
|
|
|
1056
1106
|
do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
|
|
1057
1107
|
}
|
|
1058
1108
|
buf_move(&c->res, a->res);
|
|
1109
|
+
pthread_cond_broadcast(&c->cv);
|
|
1059
1110
|
pthread_mutex_unlock(&c->mtx);
|
|
1060
1111
|
if (*a->res->buf == 'c') { // js -> ruby callback?
|
|
1061
1112
|
rb_thread_call_with_gvl(rendezvous_callback, a);
|
|
1062
1113
|
buf_reset(a->res);
|
|
1114
|
+
if (atomic_load(&c->quit)) {
|
|
1115
|
+
buf_reset(a->req);
|
|
1116
|
+
c->depth--;
|
|
1117
|
+
atomic_store(&a->active, 0);
|
|
1118
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1119
|
+
return (void *)(intptr_t)ECANCELED;
|
|
1120
|
+
}
|
|
1063
1121
|
goto next;
|
|
1064
1122
|
}
|
|
1065
1123
|
c->depth--;
|
|
1124
|
+
atomic_store(&a->active, 0);
|
|
1066
1125
|
pthread_mutex_unlock(&c->rr_mtx);
|
|
1067
1126
|
return NULL;
|
|
1068
1127
|
}
|
|
1069
1128
|
|
|
1129
|
+
static void rendezvous_ubf(void *arg)
|
|
1130
|
+
{
|
|
1131
|
+
struct rendezvous_nogvl *a;
|
|
1132
|
+
Context *c;
|
|
1133
|
+
|
|
1134
|
+
a = arg;
|
|
1135
|
+
if (!atomic_load(&a->active))
|
|
1136
|
+
return;
|
|
1137
|
+
c = a->context;
|
|
1138
|
+
if (c->pst)
|
|
1139
|
+
v8_terminate_execution(c->pst);
|
|
1140
|
+
pthread_cond_broadcast(&c->cv);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
static void terminate_ubf(void *arg)
|
|
1144
|
+
{
|
|
1145
|
+
Context *c;
|
|
1146
|
+
|
|
1147
|
+
c = arg;
|
|
1148
|
+
if (c->pst)
|
|
1149
|
+
v8_terminate_execution(c->pst);
|
|
1150
|
+
pthread_cond_broadcast(&c->cv);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1070
1153
|
static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
|
|
1071
1154
|
{
|
|
1072
1155
|
void *r;
|
|
1156
|
+
struct rendezvous_nogvl a;
|
|
1073
1157
|
|
|
1074
1158
|
if (atomic_load(&c->quit)) {
|
|
1075
1159
|
buf_reset(req);
|
|
1076
1160
|
rb_raise(context_disposed_error, "disposed context");
|
|
1077
1161
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1162
|
+
a.context = c;
|
|
1163
|
+
a.req = req;
|
|
1164
|
+
a.res = res;
|
|
1165
|
+
atomic_init(&a.active, 0);
|
|
1166
|
+
r = rb_nogvl(rendezvous_nogvl, &a, rendezvous_ubf, &a, 0);
|
|
1080
1167
|
if (r)
|
|
1081
|
-
rb_raise(runtime_error, "
|
|
1168
|
+
rb_raise(runtime_error, "single-threaded runner: %s", strerror((int)(intptr_t)r));
|
|
1082
1169
|
}
|
|
1083
1170
|
|
|
1084
1171
|
// send request to & receive reply from v8 thread; takes ownership of |req|
|
|
@@ -1094,7 +1181,8 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
|
1094
1181
|
c->exception = Qnil;
|
|
1095
1182
|
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
1096
1183
|
if (res.len == 1 && *res.buf == 'e') {
|
|
1097
|
-
|
|
1184
|
+
if (NIL_P(r))
|
|
1185
|
+
rb_raise(context_disposed_error, "disposed context");
|
|
1098
1186
|
rb_exc_raise(r);
|
|
1099
1187
|
}
|
|
1100
1188
|
r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
|
|
@@ -1115,16 +1203,35 @@ static VALUE rendezvous(Context *c, Buf *req)
|
|
|
1115
1203
|
return rendezvous1(c, req, &d);
|
|
1116
1204
|
}
|
|
1117
1205
|
|
|
1206
|
+
static void raise_exception_with_message(VALUE klass, VALUE e)
|
|
1207
|
+
{
|
|
1208
|
+
long n;
|
|
1209
|
+
VALUE message;
|
|
1210
|
+
|
|
1211
|
+
if (NIL_P(e))
|
|
1212
|
+
return;
|
|
1213
|
+
e = StringValue(e);
|
|
1214
|
+
n = RSTRING_LEN(e);
|
|
1215
|
+
if (n == 0 || RSTRING_PTR(e)[0] == NO_ERROR)
|
|
1216
|
+
return;
|
|
1217
|
+
message = rb_str_subseq(e, 1, n - 1);
|
|
1218
|
+
rb_exc_raise(rb_exc_new_str(klass, message));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1118
1221
|
static void handle_exception(VALUE e)
|
|
1119
1222
|
{
|
|
1120
1223
|
const char *s;
|
|
1121
1224
|
VALUE klass;
|
|
1225
|
+
long n;
|
|
1122
1226
|
|
|
1123
1227
|
if (NIL_P(e))
|
|
1124
1228
|
return;
|
|
1125
1229
|
e = StringValue(e);
|
|
1126
|
-
|
|
1127
|
-
|
|
1230
|
+
n = RSTRING_LEN(e);
|
|
1231
|
+
if (n == 0)
|
|
1232
|
+
return;
|
|
1233
|
+
s = RSTRING_PTR(e);
|
|
1234
|
+
switch ((unsigned char)*s) {
|
|
1128
1235
|
case NO_ERROR:
|
|
1129
1236
|
return;
|
|
1130
1237
|
case INTERNAL_ERROR:
|
|
@@ -1143,9 +1250,9 @@ static void handle_exception(VALUE e)
|
|
|
1143
1250
|
klass = terminated_error;
|
|
1144
1251
|
break;
|
|
1145
1252
|
default:
|
|
1146
|
-
rb_raise(internal_error, "bad error class %02x", *s);
|
|
1253
|
+
rb_raise(internal_error, "bad error class %02x", (unsigned char)*s);
|
|
1147
1254
|
}
|
|
1148
|
-
|
|
1255
|
+
raise_exception_with_message(klass, e);
|
|
1149
1256
|
}
|
|
1150
1257
|
|
|
1151
1258
|
static VALUE context_alloc(VALUE klass)
|
|
@@ -1230,11 +1337,19 @@ fail0:
|
|
|
1230
1337
|
return Qnil; // pacify compiler
|
|
1231
1338
|
}
|
|
1232
1339
|
|
|
1233
|
-
static void *
|
|
1340
|
+
static void *context_free_do(void *arg)
|
|
1234
1341
|
{
|
|
1235
1342
|
Context *c;
|
|
1236
1343
|
|
|
1237
1344
|
c = arg;
|
|
1345
|
+
if (single_threaded && single_threaded_recover_after_fork(c)) {
|
|
1346
|
+
// The child forked while this inherited context was not idle. There is
|
|
1347
|
+
// no live runner thread to join and the inherited V8/pthread state is
|
|
1348
|
+
// not safe to tear down. A finalizer must not hang here; let the OS
|
|
1349
|
+
// reclaim the abandoned V8 state when the child exits.
|
|
1350
|
+
context_abandon(c);
|
|
1351
|
+
return NULL;
|
|
1352
|
+
}
|
|
1238
1353
|
if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
|
|
1239
1354
|
pthread_mutex_lock(&c->mtx);
|
|
1240
1355
|
atomic_store(&c->quit, 2);
|
|
@@ -1249,31 +1364,16 @@ static void *context_free_thread_do(void *arg)
|
|
|
1249
1364
|
return NULL;
|
|
1250
1365
|
}
|
|
1251
1366
|
|
|
1252
|
-
static void context_free_thread(Context *c)
|
|
1253
|
-
{
|
|
1254
|
-
pthread_t thr;
|
|
1255
|
-
int r;
|
|
1256
|
-
|
|
1257
|
-
// dispose on another thread so we don't block when trying to
|
|
1258
|
-
// enter an isolate that's in a stuck state; that *should* be
|
|
1259
|
-
// impossible but apparently it happened regularly before the
|
|
1260
|
-
// rewrite and I'm carrying it over out of an abundance of caution
|
|
1261
|
-
if ((r = pthread_create(&thr, NULL, context_free_thread_do, c))) {
|
|
1262
|
-
fprintf(stderr, "mini_racer: pthread_create: %s", strerror(r));
|
|
1263
|
-
fflush(stderr);
|
|
1264
|
-
context_free_thread_do(c);
|
|
1265
|
-
} else {
|
|
1266
|
-
pthread_detach(thr);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
1367
|
static void context_free(void *arg)
|
|
1271
1368
|
{
|
|
1272
1369
|
Context *c;
|
|
1273
1370
|
|
|
1274
1371
|
c = arg;
|
|
1275
1372
|
if (single_threaded) {
|
|
1276
|
-
|
|
1373
|
+
// Free synchronously. A detached cleanup thread can race normal Ruby
|
|
1374
|
+
// process shutdown and trip glibc malloc corruption checks while V8 is
|
|
1375
|
+
// tearing down single-threaded contexts.
|
|
1376
|
+
context_free_do(c);
|
|
1277
1377
|
} else {
|
|
1278
1378
|
pthread_mutex_lock(&c->mtx);
|
|
1279
1379
|
c->quit = 2; // 2 = v8 thread frees
|
|
@@ -1282,6 +1382,14 @@ static void context_free(void *arg)
|
|
|
1282
1382
|
}
|
|
1283
1383
|
}
|
|
1284
1384
|
|
|
1385
|
+
static void context_abandon(Context *c)
|
|
1386
|
+
{
|
|
1387
|
+
buf_reset(&c->snapshot);
|
|
1388
|
+
buf_reset(&c->req);
|
|
1389
|
+
buf_reset(&c->res);
|
|
1390
|
+
ruby_xfree(c);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1285
1393
|
static void context_destroy(Context *c)
|
|
1286
1394
|
{
|
|
1287
1395
|
pthread_mutex_unlock(&c->mtx);
|
|
@@ -1317,13 +1425,17 @@ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
|
1317
1425
|
Context *c;
|
|
1318
1426
|
VALUE e;
|
|
1319
1427
|
Ser s;
|
|
1428
|
+
long id;
|
|
1320
1429
|
|
|
1321
1430
|
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1431
|
+
id = RARRAY_LEN(c->procs);
|
|
1432
|
+
if (id > INT32_MAX)
|
|
1433
|
+
rb_raise(runtime_error, "too many callbacks");
|
|
1322
1434
|
// request is (A)ttach, [name, id] array
|
|
1323
1435
|
ser_init1(&s, 'A');
|
|
1324
1436
|
ser_array_begin(&s, 2);
|
|
1325
1437
|
add_string(&s, name);
|
|
1326
|
-
ser_int(&s,
|
|
1438
|
+
ser_int(&s, id);
|
|
1327
1439
|
ser_array_end(&s, 2);
|
|
1328
1440
|
rb_ary_push(c->procs, proc);
|
|
1329
1441
|
// response is an exception or undefined
|
|
@@ -1335,8 +1447,25 @@ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
|
1335
1447
|
static void *context_dispose_do(void *arg)
|
|
1336
1448
|
{
|
|
1337
1449
|
Context *c;
|
|
1450
|
+
int r;
|
|
1338
1451
|
|
|
1339
1452
|
c = arg;
|
|
1453
|
+
if (single_threaded) {
|
|
1454
|
+
if ((r = single_threaded_recover_after_fork(c)))
|
|
1455
|
+
return (void *)(intptr_t)r;
|
|
1456
|
+
}
|
|
1457
|
+
if (c->depth > 0) {
|
|
1458
|
+
r = pthread_mutex_trylock(&c->rr_mtx);
|
|
1459
|
+
if (!r) {
|
|
1460
|
+
pthread_mutex_unlock(&c->rr_mtx);
|
|
1461
|
+
return (void *)(intptr_t)EBUSY;
|
|
1462
|
+
}
|
|
1463
|
+
if (r != EBUSY)
|
|
1464
|
+
return (void *)(intptr_t)r;
|
|
1465
|
+
if (c->pst)
|
|
1466
|
+
v8_terminate_execution(c->pst);
|
|
1467
|
+
pthread_cond_broadcast(&c->cv);
|
|
1468
|
+
}
|
|
1340
1469
|
if (single_threaded) {
|
|
1341
1470
|
pthread_mutex_lock(&c->mtx);
|
|
1342
1471
|
while (c->req.len || c->res.len)
|
|
@@ -1364,9 +1493,12 @@ static void *context_dispose_do(void *arg)
|
|
|
1364
1493
|
static VALUE context_dispose(VALUE self)
|
|
1365
1494
|
{
|
|
1366
1495
|
Context *c;
|
|
1496
|
+
void *r;
|
|
1367
1497
|
|
|
1368
1498
|
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1369
|
-
rb_thread_call_without_gvl(context_dispose_do, c,
|
|
1499
|
+
r = rb_thread_call_without_gvl(context_dispose_do, c, terminate_ubf, c);
|
|
1500
|
+
if (r)
|
|
1501
|
+
rb_raise(runtime_error, "context dispose: %s", strerror((int)(intptr_t)r));
|
|
1370
1502
|
return Qnil;
|
|
1371
1503
|
}
|
|
1372
1504
|
|
|
@@ -1457,6 +1589,20 @@ static VALUE context_heap_stats(VALUE self)
|
|
|
1457
1589
|
return h;
|
|
1458
1590
|
}
|
|
1459
1591
|
|
|
1592
|
+
static VALUE buf_reset_ensure(VALUE arg)
|
|
1593
|
+
{
|
|
1594
|
+
buf_reset((Buf *)arg);
|
|
1595
|
+
return Qnil;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
static VALUE heap_snapshot_to_str(VALUE arg)
|
|
1599
|
+
{
|
|
1600
|
+
Buf *res;
|
|
1601
|
+
|
|
1602
|
+
res = (Buf *)arg;
|
|
1603
|
+
return rb_utf8_str_new((char *)res->buf, res->len);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1460
1606
|
static VALUE context_heap_snapshot(VALUE self)
|
|
1461
1607
|
{
|
|
1462
1608
|
Buf req, res;
|
|
@@ -1466,7 +1612,19 @@ static VALUE context_heap_snapshot(VALUE self)
|
|
|
1466
1612
|
buf_init(&req);
|
|
1467
1613
|
buf_putc(&req, 'H'); // (H)eap snapshot, returns plain bytes
|
|
1468
1614
|
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1469
|
-
return
|
|
1615
|
+
return rb_ensure(heap_snapshot_to_str, (VALUE)&res,
|
|
1616
|
+
buf_reset_ensure, (VALUE)&res);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
static VALUE context_perform_microtask_checkpoint(VALUE self)
|
|
1620
|
+
{
|
|
1621
|
+
Context *c;
|
|
1622
|
+
Buf b;
|
|
1623
|
+
|
|
1624
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1625
|
+
buf_init(&b);
|
|
1626
|
+
buf_putc(&b, 'M'); // (M)icrotask checkpoint, returns nil
|
|
1627
|
+
return rendezvous(c, &b); // takes ownership of |b|
|
|
1470
1628
|
}
|
|
1471
1629
|
|
|
1472
1630
|
static VALUE context_pump_message_loop(VALUE self)
|
|
@@ -1489,12 +1647,14 @@ static VALUE context_low_memory_notification(VALUE self)
|
|
|
1489
1647
|
buf_init(&req);
|
|
1490
1648
|
buf_putc(&req, 'L'); // (L)ow memory notification, returns nothing
|
|
1491
1649
|
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1650
|
+
buf_reset(&res);
|
|
1492
1651
|
return Qnil;
|
|
1493
1652
|
}
|
|
1494
1653
|
|
|
1495
1654
|
static int platform_set_flag1(VALUE k, VALUE v)
|
|
1496
1655
|
{
|
|
1497
|
-
char *p, *q, buf[256];
|
|
1656
|
+
char *p, *q, *r, buf[256];
|
|
1657
|
+
long pn, vn, len;
|
|
1498
1658
|
int ok;
|
|
1499
1659
|
|
|
1500
1660
|
k = rb_funcall(k, rb_intern("to_s"), 0);
|
|
@@ -1504,12 +1664,40 @@ static int platform_set_flag1(VALUE k, VALUE v)
|
|
|
1504
1664
|
Check_Type(v, T_STRING);
|
|
1505
1665
|
}
|
|
1506
1666
|
p = RSTRING_PTR(k);
|
|
1507
|
-
|
|
1667
|
+
pn = RSTRING_LEN(k);
|
|
1668
|
+
if (memchr(p, '\0', pn))
|
|
1669
|
+
rb_raise(rb_eArgError, "flag contains NUL byte");
|
|
1670
|
+
if (pn >= 2 && p[0] == '-' && p[1] == '-') {
|
|
1508
1671
|
p += 2;
|
|
1672
|
+
pn -= 2;
|
|
1673
|
+
}
|
|
1509
1674
|
if (NIL_P(v)) {
|
|
1510
|
-
|
|
1675
|
+
len = 2 + pn;
|
|
1676
|
+
if (len >= (long)sizeof(buf))
|
|
1677
|
+
rb_raise(rb_eArgError, "flag too long");
|
|
1678
|
+
q = buf;
|
|
1679
|
+
*q++ = '-';
|
|
1680
|
+
*q++ = '-';
|
|
1681
|
+
memcpy(q, p, pn);
|
|
1682
|
+
q += pn;
|
|
1683
|
+
*q = '\0';
|
|
1511
1684
|
} else {
|
|
1512
|
-
|
|
1685
|
+
q = RSTRING_PTR(v);
|
|
1686
|
+
vn = RSTRING_LEN(v);
|
|
1687
|
+
if (memchr(q, '\0', vn))
|
|
1688
|
+
rb_raise(rb_eArgError, "flag contains NUL byte");
|
|
1689
|
+
len = 3 + pn + vn;
|
|
1690
|
+
if (len >= (long)sizeof(buf))
|
|
1691
|
+
rb_raise(rb_eArgError, "flag too long");
|
|
1692
|
+
r = buf;
|
|
1693
|
+
*r++ = '-';
|
|
1694
|
+
*r++ = '-';
|
|
1695
|
+
memcpy(r, p, pn);
|
|
1696
|
+
r += pn;
|
|
1697
|
+
*r++ = '=';
|
|
1698
|
+
memcpy(r, q, vn);
|
|
1699
|
+
r += vn;
|
|
1700
|
+
*r = '\0';
|
|
1513
1701
|
}
|
|
1514
1702
|
p = buf;
|
|
1515
1703
|
pthread_mutex_lock(&flags_mtx);
|
|
@@ -1722,8 +1910,7 @@ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
1722
1910
|
a = rendezvous1(c, &s.b, &d);
|
|
1723
1911
|
e = rb_ary_pop(a);
|
|
1724
1912
|
context_dispose(cv);
|
|
1725
|
-
|
|
1726
|
-
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
1913
|
+
raise_exception_with_message(snapshot_error, e);
|
|
1727
1914
|
ss->blob = rb_ary_pop(a);
|
|
1728
1915
|
return Qnil;
|
|
1729
1916
|
}
|
|
@@ -1753,8 +1940,7 @@ static VALUE snapshot_warmup(VALUE self, VALUE arg)
|
|
|
1753
1940
|
a = rendezvous1(c, &s.b, &d);
|
|
1754
1941
|
e = rb_ary_pop(a);
|
|
1755
1942
|
context_dispose(cv);
|
|
1756
|
-
|
|
1757
|
-
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
1943
|
+
raise_exception_with_message(snapshot_error, e);
|
|
1758
1944
|
ss->blob = rb_ary_pop(a);
|
|
1759
1945
|
return self;
|
|
1760
1946
|
}
|
|
@@ -1824,6 +2010,7 @@ void Init_mini_racer_extension(void)
|
|
|
1824
2010
|
rb_define_method(c, "eval", context_eval, -1);
|
|
1825
2011
|
rb_define_method(c, "heap_stats", context_heap_stats, 0);
|
|
1826
2012
|
rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
|
|
2013
|
+
rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);
|
|
1827
2014
|
rb_define_method(c, "pump_message_loop", context_pump_message_loop, 0);
|
|
1828
2015
|
rb_define_method(c, "low_memory_notification", context_low_memory_notification, 0);
|
|
1829
2016
|
rb_define_alloc_func(c, context_alloc);
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
#include <memory>
|
|
6
6
|
#include <vector>
|
|
7
7
|
#include <cassert>
|
|
8
|
+
#include <cstdarg>
|
|
8
9
|
#include <cstdio>
|
|
9
10
|
#include <cstdint>
|
|
10
11
|
#include <cstdlib>
|
|
11
12
|
#include <cstring>
|
|
12
|
-
#include <
|
|
13
|
+
#include <limits>
|
|
13
14
|
|
|
14
15
|
// note: the filter function gets called inside the safe context,
|
|
15
16
|
// i.e., the context that has not been tampered with by user JS
|
|
@@ -123,6 +124,106 @@ struct Serialized
|
|
|
123
124
|
}
|
|
124
125
|
};
|
|
125
126
|
|
|
127
|
+
void append_bytes(std::vector<char>& out, const char *p, size_t n)
|
|
128
|
+
{
|
|
129
|
+
out.insert(out.end(), p, p + n);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void append_literal(std::vector<char>& out, const char *s)
|
|
133
|
+
{
|
|
134
|
+
append_bytes(out, s, strlen(s));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
bool append_utf8(std::vector<char>& out, const v8::String::Utf8Value& s)
|
|
138
|
+
{
|
|
139
|
+
if (!*s) return false;
|
|
140
|
+
append_bytes(out, *s, s.length());
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
void append_format(std::vector<char>& out, const char *fmt, ...)
|
|
145
|
+
{
|
|
146
|
+
char buf[128];
|
|
147
|
+
va_list ap;
|
|
148
|
+
va_start(ap, fmt);
|
|
149
|
+
int n = vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
150
|
+
va_end(ap);
|
|
151
|
+
if (n <= 0) return;
|
|
152
|
+
if (static_cast<size_t>(n) < sizeof(buf)) {
|
|
153
|
+
append_bytes(out, buf, n);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
std::vector<char> tmp(static_cast<size_t>(n) + 1);
|
|
158
|
+
va_start(ap, fmt);
|
|
159
|
+
vsnprintf(tmp.data(), tmp.size(), fmt, ap);
|
|
160
|
+
va_end(ap);
|
|
161
|
+
append_bytes(out, tmp.data(), n);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
v8::Local<v8::String> string_from_bytes(v8::Isolate *isolate, const std::vector<char>& bytes)
|
|
165
|
+
{
|
|
166
|
+
v8::Local<v8::String> s;
|
|
167
|
+
auto type = v8::NewStringType::kNormal;
|
|
168
|
+
if (bytes.size() <= static_cast<size_t>(std::numeric_limits<int>::max()) &&
|
|
169
|
+
v8::String::NewFromUtf8(isolate, bytes.data(), type,
|
|
170
|
+
static_cast<int>(bytes.size())).ToLocal(&s)) {
|
|
171
|
+
return s;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
char fallback[] = {
|
|
175
|
+
bytes.empty() ? static_cast<char>(INTERNAL_ERROR) : bytes[0],
|
|
176
|
+
'u', 'n', 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd', ' ', 'e', 'r', 'r', 'o', 'r'
|
|
177
|
+
};
|
|
178
|
+
if (v8::String::NewFromUtf8(isolate, fallback, type, static_cast<int>(sizeof(fallback))).ToLocal(&s)) return s;
|
|
179
|
+
return v8::String::Empty(isolate);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
void set_error_message(std::vector<char>& out, int cause, const char *message)
|
|
183
|
+
{
|
|
184
|
+
out.clear();
|
|
185
|
+
out.push_back(static_cast<char>(cause));
|
|
186
|
+
append_literal(out, message);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
bool set_error_message(std::vector<char>& out, int cause, const v8::String::Utf8Value& message)
|
|
190
|
+
{
|
|
191
|
+
out.clear();
|
|
192
|
+
out.push_back(static_cast<char>(cause));
|
|
193
|
+
return append_utf8(out, message);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void set_fallback_error(State& st, v8::TryCatch *try_catch, int cause,
|
|
197
|
+
std::vector<char>& out)
|
|
198
|
+
{
|
|
199
|
+
v8::String::Utf8Value s(st.isolate, try_catch->Exception());
|
|
200
|
+
const char *message = *s ? nullptr : "unexpected failure";
|
|
201
|
+
if (cause == MEMORY_ERROR) message = "out of memory";
|
|
202
|
+
if (cause == TERMINATED_ERROR) message = "terminated";
|
|
203
|
+
if (message) {
|
|
204
|
+
set_error_message(out, cause, message);
|
|
205
|
+
} else if (!set_error_message(out, cause, s)) {
|
|
206
|
+
set_error_message(out, cause, "unexpected failure");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
bool read_path_key(State& st, const char *&p, const char *pe,
|
|
211
|
+
v8::Local<v8::String> *key, bool *last)
|
|
212
|
+
{
|
|
213
|
+
const char *dot;
|
|
214
|
+
size_t n;
|
|
215
|
+
|
|
216
|
+
if (p > pe) return false;
|
|
217
|
+
dot = static_cast<const char*>(memchr(p, '.', pe - p));
|
|
218
|
+
*last = (dot == nullptr);
|
|
219
|
+
n = *last ? static_cast<size_t>(pe - p) : static_cast<size_t>(dot - p);
|
|
220
|
+
if (n > static_cast<size_t>(std::numeric_limits<int>::max())) return false;
|
|
221
|
+
auto type = v8::NewStringType::kNormal;
|
|
222
|
+
if (!v8::String::NewFromUtf8(st.isolate, p, type, static_cast<int>(n)).ToLocal(key)) return false;
|
|
223
|
+
p = *last ? pe : dot + 1;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
126
227
|
bool bubble_up_ruby_exception(State& st, v8::TryCatch *try_catch)
|
|
127
228
|
{
|
|
128
229
|
auto exception = try_catch->Exception();
|
|
@@ -249,35 +350,40 @@ v8::Local<v8::Value> sanitize(State& st, v8::Local<v8::Value> v)
|
|
|
249
350
|
|
|
250
351
|
v8::Local<v8::String> to_error(State& st, v8::TryCatch *try_catch, int cause)
|
|
251
352
|
{
|
|
252
|
-
|
|
253
|
-
char buf[1024];
|
|
353
|
+
std::vector<char> buf;
|
|
254
354
|
|
|
255
|
-
*buf = '\0';
|
|
256
355
|
if (cause == NO_ERROR) {
|
|
257
|
-
|
|
356
|
+
return v8::String::Empty(st.isolate);
|
|
258
357
|
} else if (cause == PARSE_ERROR) {
|
|
259
358
|
auto message = try_catch->Message();
|
|
260
359
|
v8::String::Utf8Value s(st.isolate, message->Get());
|
|
261
360
|
v8::String::Utf8Value name(st.isolate, message->GetScriptResourceName());
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
361
|
+
if (*s && *name) {
|
|
362
|
+
buf.push_back(static_cast<char>(cause));
|
|
363
|
+
append_utf8(buf, s);
|
|
364
|
+
append_literal(buf, " at ");
|
|
365
|
+
append_utf8(buf, name);
|
|
366
|
+
auto line = message->GetLineNumber(st.context).FromMaybe(0);
|
|
367
|
+
auto column = message->GetStartColumn(st.context).FromMaybe(0);
|
|
368
|
+
append_format(buf, ":%d:%d", line, column);
|
|
369
|
+
} else {
|
|
370
|
+
set_fallback_error(st, try_catch, cause, buf);
|
|
371
|
+
}
|
|
270
372
|
} else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
373
|
+
v8::Local<v8::Value> t;
|
|
374
|
+
if (try_catch->StackTrace(st.context).ToLocal(&t)) {
|
|
375
|
+
v8::String::Utf8Value s(st.isolate, t);
|
|
376
|
+
if (*s) {
|
|
377
|
+
buf.push_back(static_cast<char>(cause));
|
|
378
|
+
append_utf8(buf, s);
|
|
379
|
+
} else {
|
|
380
|
+
set_fallback_error(st, try_catch, cause, buf);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
set_fallback_error(st, try_catch, cause, buf);
|
|
384
|
+
}
|
|
277
385
|
}
|
|
278
|
-
|
|
279
|
-
if (v8::String::NewFromUtf8(st.isolate, buf).ToLocal(&s)) return s;
|
|
280
|
-
return v8::String::Empty(st.isolate);
|
|
386
|
+
return string_from_bytes(st.isolate, buf);
|
|
281
387
|
}
|
|
282
388
|
|
|
283
389
|
extern "C" void v8_global_init(void)
|
|
@@ -445,23 +551,19 @@ extern "C" void v8_attach(State *pst, const uint8_t *p, size_t n)
|
|
|
445
551
|
v8::Local<v8::String> name;
|
|
446
552
|
if (!name_v->ToString(st.context).ToLocal(&name)) goto fail;
|
|
447
553
|
int32_t id;
|
|
448
|
-
if (!id_v->
|
|
449
|
-
|
|
450
|
-
st.callbacks.push_back(cb);
|
|
451
|
-
v8::Local<v8::External> ext = v8::External::New(st.isolate, cb);
|
|
452
|
-
v8::Local<v8::Function> function;
|
|
453
|
-
if (!v8::Function::New(st.context, v8_api_callback, ext).ToLocal(&function)) goto fail;
|
|
554
|
+
if (!id_v->IsInt32()) goto fail;
|
|
555
|
+
id = id_v.As<v8::Int32>()->Value();
|
|
454
556
|
// support foo.bar.baz paths
|
|
455
557
|
v8::String::Utf8Value path(st.isolate, name);
|
|
456
558
|
if (!*path) goto fail;
|
|
559
|
+
const char *p = *path;
|
|
560
|
+
const char *pe = p + path.length();
|
|
457
561
|
v8::Local<v8::Object> obj = st.context->Global();
|
|
458
562
|
v8::Local<v8::String> key;
|
|
459
|
-
for (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
if (p[n] == '\0') break;
|
|
464
|
-
p += n + 1;
|
|
563
|
+
for (;;) {
|
|
564
|
+
bool last;
|
|
565
|
+
if (!read_path_key(st, p, pe, &key, &last)) goto fail;
|
|
566
|
+
if (last) break;
|
|
465
567
|
v8::Local<v8::Value> val;
|
|
466
568
|
if (!obj->Get(st.context, key).ToLocal(&val)) goto fail;
|
|
467
569
|
if (!val->IsObject() && !val->IsFunction()) {
|
|
@@ -470,6 +572,11 @@ extern "C" void v8_attach(State *pst, const uint8_t *p, size_t n)
|
|
|
470
572
|
}
|
|
471
573
|
obj = val.As<v8::Object>();
|
|
472
574
|
}
|
|
575
|
+
std::unique_ptr<Callback> cb(new Callback{pst, id});
|
|
576
|
+
v8::Local<v8::External> ext = v8::External::New(st.isolate, cb.get());
|
|
577
|
+
v8::Local<v8::Function> function;
|
|
578
|
+
if (!v8::Function::New(st.context, v8_api_callback, ext).ToLocal(&function)) goto fail;
|
|
579
|
+
st.callbacks.push_back(cb.release());
|
|
473
580
|
if (!obj->Set(st.context, key, function).FromMaybe(false)) goto fail;
|
|
474
581
|
}
|
|
475
582
|
cause = NO_ERROR;
|
|
@@ -504,14 +611,14 @@ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
|
|
|
504
611
|
// support foo.bar.baz paths
|
|
505
612
|
v8::String::Utf8Value path(st.isolate, name);
|
|
506
613
|
if (!*path) goto fail;
|
|
614
|
+
const char *p = *path;
|
|
615
|
+
const char *pe = p + path.length();
|
|
507
616
|
v8::Local<v8::Object> obj = st.context->Global();
|
|
508
617
|
v8::Local<v8::String> key;
|
|
509
|
-
for (
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
if (p[n] == '\0') break;
|
|
514
|
-
p += n + 1;
|
|
618
|
+
for (;;) {
|
|
619
|
+
bool last;
|
|
620
|
+
if (!read_path_key(st, p, pe, &key, &last)) goto fail;
|
|
621
|
+
if (last) break;
|
|
515
622
|
v8::Local<v8::Value> val;
|
|
516
623
|
if (!obj->Get(st.context, key).ToLocal(&val)) goto fail;
|
|
517
624
|
if (!val->ToObject(st.context).ToLocal(&obj)) goto fail;
|
|
@@ -659,9 +766,22 @@ extern "C" void v8_heap_snapshot(State *pst)
|
|
|
659
766
|
auto snapshot = st.isolate->GetHeapProfiler()->TakeHeapSnapshot();
|
|
660
767
|
OutputStream os;
|
|
661
768
|
snapshot->Serialize(&os, v8::HeapSnapshot::kJSON);
|
|
769
|
+
const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
|
|
662
770
|
v8_reply(st.ruby_context, os.buf.data(), os.buf.size()); // not serialized because big
|
|
663
771
|
}
|
|
664
772
|
|
|
773
|
+
extern "C" void v8_perform_microtask_checkpoint(State *pst)
|
|
774
|
+
{
|
|
775
|
+
// Leave any termination active so the enclosing v8_call/v8_eval frame
|
|
776
|
+
// surfaces OOM (set by v8_gc_callback) or watchdog termination to Ruby.
|
|
777
|
+
State& st = *pst;
|
|
778
|
+
v8::TryCatch try_catch(st.isolate);
|
|
779
|
+
try_catch.SetVerbose(st.verbose_exceptions);
|
|
780
|
+
v8::HandleScope handle_scope(st.isolate);
|
|
781
|
+
v8::MicrotasksScope::PerformCheckpoint(st.isolate);
|
|
782
|
+
reply_retry(st, v8::Undefined(st.isolate));
|
|
783
|
+
}
|
|
784
|
+
|
|
665
785
|
extern "C" void v8_pump_message_loop(State *pst)
|
|
666
786
|
{
|
|
667
787
|
State& st = *pst;
|
|
@@ -691,7 +811,7 @@ fail:
|
|
|
691
811
|
int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
692
812
|
const v8::String::Utf8Value& code,
|
|
693
813
|
v8::StartupData blob, v8::StartupData *result,
|
|
694
|
-
char
|
|
814
|
+
std::vector<char> *errbuf)
|
|
695
815
|
{
|
|
696
816
|
// SnapshotCreator takes ownership of isolate
|
|
697
817
|
v8::Isolate *isolate = v8::Isolate::Allocate();
|
|
@@ -715,7 +835,8 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
715
835
|
auto type = v8::NewStringType::kNormal;
|
|
716
836
|
if (!v8::String::NewFromUtf8(isolate, *code, type, code.length()).ToLocal(&source)) {
|
|
717
837
|
v8::String::Utf8Value s(isolate, try_catch.Exception());
|
|
718
|
-
if (
|
|
838
|
+
if (!set_error_message(*errbuf, cause, s))
|
|
839
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
719
840
|
goto fail;
|
|
720
841
|
}
|
|
721
842
|
v8::ScriptOrigin origin(filename);
|
|
@@ -732,8 +853,16 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
732
853
|
v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
|
|
733
854
|
auto line = m->GetLineNumber(context).FromMaybe(0);
|
|
734
855
|
auto column = m->GetStartColumn(context).FromMaybe(0);
|
|
735
|
-
|
|
736
|
-
|
|
856
|
+
if (*s && *name) {
|
|
857
|
+
errbuf->clear();
|
|
858
|
+
errbuf->push_back(static_cast<char>(cause));
|
|
859
|
+
append_utf8(*errbuf, s);
|
|
860
|
+
append_literal(*errbuf, "\n");
|
|
861
|
+
append_utf8(*errbuf, name);
|
|
862
|
+
append_format(*errbuf, ":%d:%d", line, column);
|
|
863
|
+
} else {
|
|
864
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
865
|
+
}
|
|
737
866
|
goto fail;
|
|
738
867
|
}
|
|
739
868
|
cause = INTERNAL_ERROR;
|
|
@@ -764,7 +893,7 @@ extern "C" void v8_snapshot(State *pst, const uint8_t *p, size_t n)
|
|
|
764
893
|
v8::Local<v8::Value> result;
|
|
765
894
|
v8::StartupData blob{nullptr, 0};
|
|
766
895
|
int cause = INTERNAL_ERROR;
|
|
767
|
-
char errbuf
|
|
896
|
+
std::vector<char> errbuf;
|
|
768
897
|
{
|
|
769
898
|
v8::Local<v8::Value> code_v;
|
|
770
899
|
if (!des.ReadValue(st.context).ToLocal(&code_v)) goto fail;
|
|
@@ -793,10 +922,8 @@ fail:
|
|
|
793
922
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
794
923
|
if (cause) result = v8::Undefined(st.isolate);
|
|
795
924
|
v8::Local<v8::Value> err;
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error");
|
|
799
|
-
}
|
|
925
|
+
if (!errbuf.empty()) {
|
|
926
|
+
err = string_from_bytes(st.isolate, errbuf);
|
|
800
927
|
} else {
|
|
801
928
|
err = to_error(st, &try_catch, cause);
|
|
802
929
|
}
|
|
@@ -818,7 +945,7 @@ extern "C" void v8_warmup(State *pst, const uint8_t *p, size_t n)
|
|
|
818
945
|
v8::Local<v8::Value> result;
|
|
819
946
|
v8::StartupData blob{nullptr, 0};
|
|
820
947
|
int cause = INTERNAL_ERROR;
|
|
821
|
-
char errbuf
|
|
948
|
+
std::vector<char> errbuf;
|
|
822
949
|
{
|
|
823
950
|
v8::Local<v8::Value> request_v;
|
|
824
951
|
if (!des.ReadValue(st.context).ToLocal(&request_v)) goto fail;
|
|
@@ -865,10 +992,8 @@ fail:
|
|
|
865
992
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
866
993
|
if (cause) result = v8::Undefined(st.isolate);
|
|
867
994
|
v8::Local<v8::Value> err;
|
|
868
|
-
if (
|
|
869
|
-
|
|
870
|
-
err = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error");
|
|
871
|
-
}
|
|
995
|
+
if (!errbuf.empty()) {
|
|
996
|
+
err = string_from_bytes(st.isolate, errbuf);
|
|
872
997
|
} else {
|
|
873
998
|
err = to_error(st, &try_catch, cause);
|
|
874
999
|
}
|
|
@@ -919,6 +1044,7 @@ State::~State()
|
|
|
919
1044
|
{
|
|
920
1045
|
v8::Locker locker(isolate);
|
|
921
1046
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
1047
|
+
persistent_safe_context_function.Reset();
|
|
922
1048
|
persistent_safe_context.Reset();
|
|
923
1049
|
persistent_context.Reset();
|
|
924
1050
|
ruby_exception.Reset();
|
|
@@ -42,6 +42,7 @@ void v8_call(struct State *pst, const uint8_t *p, size_t n);
|
|
|
42
42
|
void v8_eval(struct State *pst, const uint8_t *p, size_t n);
|
|
43
43
|
void v8_heap_stats(struct State *pst);
|
|
44
44
|
void v8_heap_snapshot(struct State *pst);
|
|
45
|
+
void v8_perform_microtask_checkpoint(struct State *pst);
|
|
45
46
|
void v8_pump_message_loop(struct State *pst);
|
|
46
47
|
void v8_snapshot(struct State *pst, const uint8_t *p, size_t n);
|
|
47
48
|
void v8_warmup(struct State *pst, const uint8_t *p, size_t n);
|
|
@@ -278,7 +278,7 @@ static void ser_int(Ser *s, int64_t v)
|
|
|
278
278
|
if (v > INT64_MIN/1024)
|
|
279
279
|
if (v <= INT64_MAX/1024)
|
|
280
280
|
return ser_num(s, v);
|
|
281
|
-
t = v < 0 ? -v : v;
|
|
281
|
+
t = v < 0 ? (uint64_t)(-(v + 1)) + 1 : (uint64_t)v;
|
|
282
282
|
sign = v < 0 ? -1 : 1;
|
|
283
283
|
ser_bigint(s, &t, sizeof(t), sign);
|
|
284
284
|
} else {
|
|
@@ -118,6 +118,24 @@ module MiniRacer
|
|
|
118
118
|
@js_symbol_to_symbol_func = eval_in_context "(x) => { var r = x.description; return r === undefined ? 'undefined' : r }"
|
|
119
119
|
@js_new_date_func = eval_in_context "(x) => { return new Date(x) }"
|
|
120
120
|
@js_new_array_func = eval_in_context "(x) => { return new Array(x) }"
|
|
121
|
+
# looks up a (dotted) function name as properties from globalThis,
|
|
122
|
+
# instead of evaluating the name as source code, so that names with
|
|
123
|
+
# embedded NUL bytes or other invalid syntax resolve correctly
|
|
124
|
+
@js_lookup_call_target_func = eval_in_context <<~JS
|
|
125
|
+
(name) => {
|
|
126
|
+
let target = globalThis;
|
|
127
|
+
for (const key of name.split(".")) {
|
|
128
|
+
if (target == null) {
|
|
129
|
+
throw new ReferenceError(name + " is not defined");
|
|
130
|
+
}
|
|
131
|
+
target = target[key];
|
|
132
|
+
}
|
|
133
|
+
if (target === undefined) {
|
|
134
|
+
throw new ReferenceError(name + " is not defined");
|
|
135
|
+
}
|
|
136
|
+
return target;
|
|
137
|
+
}
|
|
138
|
+
JS
|
|
121
139
|
@js_new_uint8array_func = eval_in_context "(x) => { return new Uint8Array(x) }"
|
|
122
140
|
end
|
|
123
141
|
|
|
@@ -174,7 +192,7 @@ module MiniRacer
|
|
|
174
192
|
raise RuntimeError, "TruffleRuby does not support call after stop" if @stopped
|
|
175
193
|
begin
|
|
176
194
|
translate do
|
|
177
|
-
function =
|
|
195
|
+
function = @js_lookup_call_target_func.call(convert_ruby_to_js(encode(function_name)))
|
|
178
196
|
function.call(*convert_ruby_to_js(arguments))
|
|
179
197
|
end
|
|
180
198
|
rescue Polyglot::ForeignException => e
|
|
@@ -225,8 +243,11 @@ module MiniRacer
|
|
|
225
243
|
|
|
226
244
|
def convert_js_to_ruby(value)
|
|
227
245
|
case value
|
|
228
|
-
when true, false, Integer
|
|
246
|
+
when true, false, Integer
|
|
229
247
|
value
|
|
248
|
+
when Float
|
|
249
|
+
# match the C extension: integral doubles convert to Ruby Integers
|
|
250
|
+
value.finite? && value == value.truncate ? value.to_i : value
|
|
230
251
|
else
|
|
231
252
|
if value.nil?
|
|
232
253
|
nil
|
|
@@ -370,6 +391,10 @@ module MiniRacer
|
|
|
370
391
|
class Platform
|
|
371
392
|
def self.set_flag_as_str!(flag)
|
|
372
393
|
raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
|
|
394
|
+
raise ArgumentError, "flag contains NUL byte" if flag.include?("\0")
|
|
395
|
+
# the C extension normalizes flags into a 256 byte "--flag" buffer
|
|
396
|
+
normalized = flag.start_with?("--") ? flag : "--#{flag}"
|
|
397
|
+
raise ArgumentError, "flag too long" if normalized.bytesize >= 256
|
|
373
398
|
raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
|
|
374
399
|
Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
|
|
375
400
|
end
|
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.21.
|
|
4
|
+
version: 0.21.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -136,9 +136,9 @@ licenses:
|
|
|
136
136
|
- MIT
|
|
137
137
|
metadata:
|
|
138
138
|
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
|
139
|
-
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.21.
|
|
140
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.
|
|
141
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.
|
|
139
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.21.3/CHANGELOG
|
|
140
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.3
|
|
141
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.3
|
|
142
142
|
rdoc_options: []
|
|
143
143
|
require_paths:
|
|
144
144
|
- lib
|
|
@@ -147,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
147
147
|
requirements:
|
|
148
148
|
- - ">="
|
|
149
149
|
- !ruby/object:Gem::Version
|
|
150
|
-
version: '3.
|
|
150
|
+
version: '3.3'
|
|
151
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
152
|
requirements:
|
|
153
153
|
- - ">="
|