Skip to content

Commit ffd7b10

Browse files
committed
Convert big ruby integers to js bigints
Numbers outside the range that double-precision floating-point numbers can represent exactly are now converted to JS BigInts instead of being rejected outright. Said range corresponds to `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` on the JS side. Refs: #348
1 parent 31360da commit ffd7b10

File tree

3 files changed

+57
-29
lines changed

3 files changed

+57
-29
lines changed

ext/mini_racer_extension/mini_racer_extension.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,19 @@ static void des_bigint(void *arg, const void *p, size_t n, int sign)
327327
if (t >> 63)
328328
*a++ = 0; // suppress sign extension
329329
v = rb_big_unpack(limbs, a-limbs);
330-
if (sign < 0)
331-
v = rb_big_mul(v, LONG2FIX(-1));
330+
if (sign < 0) {
331+
// rb_big_unpack returns T_FIXNUM for smallish bignums
332+
switch (TYPE(v)) {
333+
case T_BIGNUM:
334+
v = rb_big_mul(v, LONG2FIX(-1));
335+
break;
336+
case T_FIXNUM:
337+
v = LONG2FIX(-1 * FIX2LONG(v));
338+
break;
339+
default:
340+
abort();
341+
}
342+
}
332343
put(c, v);
333344
}
334345

@@ -620,14 +631,16 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
620631
struct timeval tv = rb_time_timeval(v);
621632
ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
622633
} else {
623-
snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
624-
return -1;
634+
snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
635+
return -1;
625636
}
626637
break;
627638
default:
628639
snprintf(s->err, sizeof(s->err), "unsupported type %x", TYPE(v));
629640
return -1;
630641
}
642+
if (*s->err)
643+
return -1;
631644
return 0;
632645
}
633646

ext/mini_racer_extension/serde.c

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,44 @@ static void ser_num(Ser *s, double v)
239239
}
240240
}
241241

242+
// ser_bigint: |n| is in bytes, not quadwords
243+
static void ser_bigint(Ser *s, const uint64_t *p, size_t n, int sign)
244+
{
245+
if (*s->err)
246+
return;
247+
if (n % 8) {
248+
snprintf(s->err, sizeof(s->err), "bad bigint");
249+
return;
250+
}
251+
w_byte(s, 'Z');
252+
// chop off high all-zero words
253+
n /= 8;
254+
while (n--)
255+
if (p[n])
256+
break;
257+
if (n == (size_t)-1) {
258+
w_byte(s, 0); // normalized zero
259+
} else {
260+
n = 8*n + 8;
261+
w_varint(s, 2*n + (sign < 0));
262+
w(s, p, n);
263+
}
264+
}
265+
242266
static void ser_int(Ser *s, int64_t v)
243267
{
268+
uint64_t t;
269+
int sign;
270+
244271
if (*s->err)
245272
return;
246273
if (v < INT32_MIN || v > INT32_MAX) {
247274
if (v > INT64_MIN/1024)
248275
if (v <= INT64_MAX/1024)
249276
return ser_num(s, v);
250-
snprintf(s->err, sizeof(s->err), "out of range: %lld", (long long)v);
277+
t = v < 0 ? -v : v;
278+
sign = v < 0 ? -1 : 1;
279+
ser_bigint(s, &t, sizeof(t), sign);
251280
} else {
252281
w_byte(s, 'I');
253282
w_zigzag(s, v);
@@ -265,30 +294,6 @@ static void ser_date(Ser *s, double v)
265294
}
266295
}
267296

268-
// ser_bigint: |n| is in bytes, not quadwords
269-
static void ser_bigint(Ser *s, const uint64_t *p, size_t n, int sign)
270-
{
271-
if (*s->err)
272-
return;
273-
if (n % 8) {
274-
snprintf(s->err, sizeof(s->err), "bad bigint");
275-
return;
276-
}
277-
w_byte(s, 'Z');
278-
// chop off high all-zero words
279-
n /= 8;
280-
while (n--)
281-
if (p[n])
282-
break;
283-
if (n == (size_t)-1) {
284-
w_byte(s, 0); // normalized zero
285-
} else {
286-
n = 8*n + 8;
287-
w_varint(s, 2*n + (sign < 0));
288-
w(s, p, n);
289-
}
290-
}
291-
292297
// string must be utf8
293298
static void ser_string(Ser *s, const char *p, size_t n)
294299
{

test/mini_racer_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,5 +1153,15 @@ def test_large_integer
11531153
assert_equal(result.class, big_int.class)
11541154
assert_equal(result, big_int)
11551155
}
1156+
types = []
1157+
[2**63/1024-1, 2**63/1024, -2**63/1024+1, -2**63/1024].each { |big_int|
1158+
context = MiniRacer::Context.new
1159+
context.attach("test", proc { big_int })
1160+
context.attach("type", proc { |arg| types.push(arg) })
1161+
result = context.eval("const t = test(); type(typeof t); t")
1162+
assert_equal(result.class, big_int.class)
1163+
assert_equal(result, big_int)
1164+
}
1165+
assert_equal(types, %w[number bigint number bigint])
11561166
end
11571167
end

0 commit comments

Comments
 (0)