mini_racer 0.21.1 → 0.21.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 +7 -0
- data/README.md +32 -0
- data/ext/mini_racer_extension/mini_racer_extension.c +113 -33
- 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: 70ca9828687a7fcdb44048eb1a3e9ad2fe3f38082492504fc22563fe94fa524a
|
|
4
|
+
data.tar.gz: 61719146ed1430235269e80a21a60ef59b98f22f470cda13820074f9cecea818
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 944e4a0e403b134e0206c6d6381efa3d9f120af9cfce18f61fc85c3eef5e6a077c64a457142d347041059a6b76f24990dbd15c17691bbcb3306ff1ea877983a4
|
|
7
|
+
data.tar.gz: f3003a61146634c76ad28ab99be4d6e96b34490522edb13607b03fcf9354fce135844947af3d23f9d37cc012a26ba4c280c8d4b19d410d47bcebc753cdc6c34f
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
- 0.21.2 - 11-06-2026
|
|
2
|
+
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
|
|
3
|
+
- Fix native memory leaks in `Context#heap_snapshot`/`Context#write_heap_snapshot`; thanks to Pranjali Thakur from depthfirst.com
|
|
4
|
+
- Fix large integral JavaScript numbers wrapping to negative Ruby integers; thanks to Pranjali Thakur from depthfirst.com
|
|
5
|
+
- Fix Ruby callback exceptions with embedded NUL bytes permanently deadlocking a context; thanks to Pranjali Thakur from depthfirst.com
|
|
6
|
+
- Preserve embedded NUL bytes in JavaScript exception messages and attached function names, and reject unsafe V8 flags containing NUL bytes or overly long values
|
|
7
|
+
|
|
1
8
|
- 0.21.1 - 25-05-2026
|
|
2
9
|
- Run `:single_threaded` V8 dispatches on a reusable mini_racer-owned native thread so V8 does not execute on Ruby-owned threads
|
|
3
10
|
- 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.
|
|
@@ -316,13 +316,19 @@ static void des_bool(void *arg, int v)
|
|
|
316
316
|
|
|
317
317
|
static void des_int(void *arg, int64_t v)
|
|
318
318
|
{
|
|
319
|
-
put(arg,
|
|
319
|
+
put(arg, LL2NUM((LONG_LONG)v));
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
static void des_num(void *arg, double v)
|
|
323
323
|
{
|
|
324
|
-
if (isfinite(v) && v == trunc(v)
|
|
325
|
-
|
|
324
|
+
if (isfinite(v) && v == trunc(v)) {
|
|
325
|
+
// INT64_MAX is not exactly representable as a double: it rounds up to
|
|
326
|
+
// 2^63, which would let 2^63 through and make the cast undefined.
|
|
327
|
+
if (v >= -0x1p63 && v < 0x1p63) {
|
|
328
|
+
put(arg, LL2NUM((LONG_LONG)v));
|
|
329
|
+
} else {
|
|
330
|
+
put(arg, rb_dbl2big(v));
|
|
331
|
+
}
|
|
326
332
|
} else {
|
|
327
333
|
put(arg, DBL2NUM(v));
|
|
328
334
|
}
|
|
@@ -364,19 +370,8 @@ static void des_bigint(void *arg, const void *p, size_t n, int sign)
|
|
|
364
370
|
if (t >> 63)
|
|
365
371
|
*a++ = 0; // suppress sign extension
|
|
366
372
|
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
|
-
}
|
|
373
|
+
if (sign < 0)
|
|
374
|
+
v = rb_funcall(v, rb_intern("-@"), 0);
|
|
380
375
|
put(c, v);
|
|
381
376
|
}
|
|
382
377
|
|
|
@@ -810,6 +805,7 @@ static void dispatch1(Context *c, const uint8_t *p, size_t n)
|
|
|
810
805
|
case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
|
|
811
806
|
case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
|
|
812
807
|
case 'H': return v8_heap_snapshot(c->pst);
|
|
808
|
+
case 'M': return v8_perform_microtask_checkpoint(c->pst);
|
|
813
809
|
case 'P': return v8_pump_message_loop(c->pst);
|
|
814
810
|
case 'S': return v8_heap_stats(c->pst);
|
|
815
811
|
case 'T': return v8_snapshot(c->pst, p+1, n-1);
|
|
@@ -936,6 +932,7 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
936
932
|
Context *c;
|
|
937
933
|
DesCtx d;
|
|
938
934
|
Buf *b;
|
|
935
|
+
long id;
|
|
939
936
|
|
|
940
937
|
a = (void *)arg;
|
|
941
938
|
b = a->res;
|
|
@@ -945,7 +942,12 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
945
942
|
DesCtx_init(&d);
|
|
946
943
|
args = deserialize1(&d, b->buf+1, b->len-1); // skip 'c' marker
|
|
947
944
|
func = rb_ary_pop(args); // callback id
|
|
948
|
-
|
|
945
|
+
if (!RB_INTEGER_TYPE_P(func))
|
|
946
|
+
rb_raise(runtime_error, "bad callback id");
|
|
947
|
+
id = NUM2LONG(func);
|
|
948
|
+
if (id < 0 || id >= RARRAY_LEN(c->procs))
|
|
949
|
+
rb_raise(runtime_error, "bad callback id");
|
|
950
|
+
func = rb_ary_entry(c->procs, id);
|
|
949
951
|
return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
|
|
950
952
|
}
|
|
951
953
|
|
|
@@ -981,9 +983,9 @@ fail:
|
|
|
981
983
|
ser_init0(&s); // ruby exception pending
|
|
982
984
|
w_byte(&s, 'e'); // send ruby error message to v8 thread
|
|
983
985
|
r = rb_funcall(c->exception, rb_intern("to_s"), 0);
|
|
984
|
-
err =
|
|
986
|
+
err = StringValuePtr(r);
|
|
985
987
|
if (err)
|
|
986
|
-
w(&s, err,
|
|
988
|
+
w(&s, err, RSTRING_LEN(r));
|
|
987
989
|
goto out;
|
|
988
990
|
}
|
|
989
991
|
|
|
@@ -1115,16 +1117,35 @@ static VALUE rendezvous(Context *c, Buf *req)
|
|
|
1115
1117
|
return rendezvous1(c, req, &d);
|
|
1116
1118
|
}
|
|
1117
1119
|
|
|
1120
|
+
static void raise_exception_with_message(VALUE klass, VALUE e)
|
|
1121
|
+
{
|
|
1122
|
+
long n;
|
|
1123
|
+
VALUE message;
|
|
1124
|
+
|
|
1125
|
+
if (NIL_P(e))
|
|
1126
|
+
return;
|
|
1127
|
+
e = StringValue(e);
|
|
1128
|
+
n = RSTRING_LEN(e);
|
|
1129
|
+
if (n == 0 || RSTRING_PTR(e)[0] == NO_ERROR)
|
|
1130
|
+
return;
|
|
1131
|
+
message = rb_str_subseq(e, 1, n - 1);
|
|
1132
|
+
rb_exc_raise(rb_exc_new_str(klass, message));
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1118
1135
|
static void handle_exception(VALUE e)
|
|
1119
1136
|
{
|
|
1120
1137
|
const char *s;
|
|
1121
1138
|
VALUE klass;
|
|
1139
|
+
long n;
|
|
1122
1140
|
|
|
1123
1141
|
if (NIL_P(e))
|
|
1124
1142
|
return;
|
|
1125
1143
|
e = StringValue(e);
|
|
1126
|
-
|
|
1127
|
-
|
|
1144
|
+
n = RSTRING_LEN(e);
|
|
1145
|
+
if (n == 0)
|
|
1146
|
+
return;
|
|
1147
|
+
s = RSTRING_PTR(e);
|
|
1148
|
+
switch ((unsigned char)*s) {
|
|
1128
1149
|
case NO_ERROR:
|
|
1129
1150
|
return;
|
|
1130
1151
|
case INTERNAL_ERROR:
|
|
@@ -1143,9 +1164,9 @@ static void handle_exception(VALUE e)
|
|
|
1143
1164
|
klass = terminated_error;
|
|
1144
1165
|
break;
|
|
1145
1166
|
default:
|
|
1146
|
-
rb_raise(internal_error, "bad error class %02x", *s);
|
|
1167
|
+
rb_raise(internal_error, "bad error class %02x", (unsigned char)*s);
|
|
1147
1168
|
}
|
|
1148
|
-
|
|
1169
|
+
raise_exception_with_message(klass, e);
|
|
1149
1170
|
}
|
|
1150
1171
|
|
|
1151
1172
|
static VALUE context_alloc(VALUE klass)
|
|
@@ -1317,13 +1338,17 @@ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
|
|
|
1317
1338
|
Context *c;
|
|
1318
1339
|
VALUE e;
|
|
1319
1340
|
Ser s;
|
|
1341
|
+
long id;
|
|
1320
1342
|
|
|
1321
1343
|
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1344
|
+
id = RARRAY_LEN(c->procs);
|
|
1345
|
+
if (id > INT32_MAX)
|
|
1346
|
+
rb_raise(runtime_error, "too many callbacks");
|
|
1322
1347
|
// request is (A)ttach, [name, id] array
|
|
1323
1348
|
ser_init1(&s, 'A');
|
|
1324
1349
|
ser_array_begin(&s, 2);
|
|
1325
1350
|
add_string(&s, name);
|
|
1326
|
-
ser_int(&s,
|
|
1351
|
+
ser_int(&s, id);
|
|
1327
1352
|
ser_array_end(&s, 2);
|
|
1328
1353
|
rb_ary_push(c->procs, proc);
|
|
1329
1354
|
// response is an exception or undefined
|
|
@@ -1457,6 +1482,20 @@ static VALUE context_heap_stats(VALUE self)
|
|
|
1457
1482
|
return h;
|
|
1458
1483
|
}
|
|
1459
1484
|
|
|
1485
|
+
static VALUE buf_reset_ensure(VALUE arg)
|
|
1486
|
+
{
|
|
1487
|
+
buf_reset((Buf *)arg);
|
|
1488
|
+
return Qnil;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
static VALUE heap_snapshot_to_str(VALUE arg)
|
|
1492
|
+
{
|
|
1493
|
+
Buf *res;
|
|
1494
|
+
|
|
1495
|
+
res = (Buf *)arg;
|
|
1496
|
+
return rb_utf8_str_new((char *)res->buf, res->len);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1460
1499
|
static VALUE context_heap_snapshot(VALUE self)
|
|
1461
1500
|
{
|
|
1462
1501
|
Buf req, res;
|
|
@@ -1466,7 +1505,19 @@ static VALUE context_heap_snapshot(VALUE self)
|
|
|
1466
1505
|
buf_init(&req);
|
|
1467
1506
|
buf_putc(&req, 'H'); // (H)eap snapshot, returns plain bytes
|
|
1468
1507
|
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1469
|
-
return
|
|
1508
|
+
return rb_ensure(heap_snapshot_to_str, (VALUE)&res,
|
|
1509
|
+
buf_reset_ensure, (VALUE)&res);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
static VALUE context_perform_microtask_checkpoint(VALUE self)
|
|
1513
|
+
{
|
|
1514
|
+
Context *c;
|
|
1515
|
+
Buf b;
|
|
1516
|
+
|
|
1517
|
+
TypedData_Get_Struct(self, Context, &context_type, c);
|
|
1518
|
+
buf_init(&b);
|
|
1519
|
+
buf_putc(&b, 'M'); // (M)icrotask checkpoint, returns nil
|
|
1520
|
+
return rendezvous(c, &b); // takes ownership of |b|
|
|
1470
1521
|
}
|
|
1471
1522
|
|
|
1472
1523
|
static VALUE context_pump_message_loop(VALUE self)
|
|
@@ -1489,12 +1540,14 @@ static VALUE context_low_memory_notification(VALUE self)
|
|
|
1489
1540
|
buf_init(&req);
|
|
1490
1541
|
buf_putc(&req, 'L'); // (L)ow memory notification, returns nothing
|
|
1491
1542
|
rendezvous_no_des(c, &req, &res); // takes ownership of |req|
|
|
1543
|
+
buf_reset(&res);
|
|
1492
1544
|
return Qnil;
|
|
1493
1545
|
}
|
|
1494
1546
|
|
|
1495
1547
|
static int platform_set_flag1(VALUE k, VALUE v)
|
|
1496
1548
|
{
|
|
1497
|
-
char *p, *q, buf[256];
|
|
1549
|
+
char *p, *q, *r, buf[256];
|
|
1550
|
+
long pn, vn, len;
|
|
1498
1551
|
int ok;
|
|
1499
1552
|
|
|
1500
1553
|
k = rb_funcall(k, rb_intern("to_s"), 0);
|
|
@@ -1504,12 +1557,40 @@ static int platform_set_flag1(VALUE k, VALUE v)
|
|
|
1504
1557
|
Check_Type(v, T_STRING);
|
|
1505
1558
|
}
|
|
1506
1559
|
p = RSTRING_PTR(k);
|
|
1507
|
-
|
|
1560
|
+
pn = RSTRING_LEN(k);
|
|
1561
|
+
if (memchr(p, '\0', pn))
|
|
1562
|
+
rb_raise(rb_eArgError, "flag contains NUL byte");
|
|
1563
|
+
if (pn >= 2 && p[0] == '-' && p[1] == '-') {
|
|
1508
1564
|
p += 2;
|
|
1565
|
+
pn -= 2;
|
|
1566
|
+
}
|
|
1509
1567
|
if (NIL_P(v)) {
|
|
1510
|
-
|
|
1568
|
+
len = 2 + pn;
|
|
1569
|
+
if (len >= (long)sizeof(buf))
|
|
1570
|
+
rb_raise(rb_eArgError, "flag too long");
|
|
1571
|
+
q = buf;
|
|
1572
|
+
*q++ = '-';
|
|
1573
|
+
*q++ = '-';
|
|
1574
|
+
memcpy(q, p, pn);
|
|
1575
|
+
q += pn;
|
|
1576
|
+
*q = '\0';
|
|
1511
1577
|
} else {
|
|
1512
|
-
|
|
1578
|
+
q = RSTRING_PTR(v);
|
|
1579
|
+
vn = RSTRING_LEN(v);
|
|
1580
|
+
if (memchr(q, '\0', vn))
|
|
1581
|
+
rb_raise(rb_eArgError, "flag contains NUL byte");
|
|
1582
|
+
len = 3 + pn + vn;
|
|
1583
|
+
if (len >= (long)sizeof(buf))
|
|
1584
|
+
rb_raise(rb_eArgError, "flag too long");
|
|
1585
|
+
r = buf;
|
|
1586
|
+
*r++ = '-';
|
|
1587
|
+
*r++ = '-';
|
|
1588
|
+
memcpy(r, p, pn);
|
|
1589
|
+
r += pn;
|
|
1590
|
+
*r++ = '=';
|
|
1591
|
+
memcpy(r, q, vn);
|
|
1592
|
+
r += vn;
|
|
1593
|
+
*r = '\0';
|
|
1513
1594
|
}
|
|
1514
1595
|
p = buf;
|
|
1515
1596
|
pthread_mutex_lock(&flags_mtx);
|
|
@@ -1722,8 +1803,7 @@ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
1722
1803
|
a = rendezvous1(c, &s.b, &d);
|
|
1723
1804
|
e = rb_ary_pop(a);
|
|
1724
1805
|
context_dispose(cv);
|
|
1725
|
-
|
|
1726
|
-
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
1806
|
+
raise_exception_with_message(snapshot_error, e);
|
|
1727
1807
|
ss->blob = rb_ary_pop(a);
|
|
1728
1808
|
return Qnil;
|
|
1729
1809
|
}
|
|
@@ -1753,8 +1833,7 @@ static VALUE snapshot_warmup(VALUE self, VALUE arg)
|
|
|
1753
1833
|
a = rendezvous1(c, &s.b, &d);
|
|
1754
1834
|
e = rb_ary_pop(a);
|
|
1755
1835
|
context_dispose(cv);
|
|
1756
|
-
|
|
1757
|
-
rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
|
|
1836
|
+
raise_exception_with_message(snapshot_error, e);
|
|
1758
1837
|
ss->blob = rb_ary_pop(a);
|
|
1759
1838
|
return self;
|
|
1760
1839
|
}
|
|
@@ -1824,6 +1903,7 @@ void Init_mini_racer_extension(void)
|
|
|
1824
1903
|
rb_define_method(c, "eval", context_eval, -1);
|
|
1825
1904
|
rb_define_method(c, "heap_stats", context_heap_stats, 0);
|
|
1826
1905
|
rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
|
|
1906
|
+
rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);
|
|
1827
1907
|
rb_define_method(c, "pump_message_loop", context_pump_message_loop, 0);
|
|
1828
1908
|
rb_define_method(c, "low_memory_notification", context_low_memory_notification, 0);
|
|
1829
1909
|
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.2
|
|
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.2/CHANGELOG
|
|
140
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.21.2
|
|
141
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.21.2
|
|
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
|
- - ">="
|