Description
JS_ToBigInt64
handles number larger than 2^64 incorrectly.
Divive into the function, it calls JS_ToBigInt64Free
then JS_ToBigInt64Free
:
static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
{
bf_t a_s, *a;
a = JS_ToBigIntFree(ctx, &a_s, val);
if (!a) {
*pres = 0;
return -1;
}
bf_get_int64(pres, a, BF_GET_INT_MOD);
JS_FreeBigInt(ctx, a, &a_s);
return 0;
}
For numbers larger than 2^64, a
has a valid value so it goes to bf_get_int64
where the problem occurs.
/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there
is an overflow and 0 otherwise. */
int bf_get_int64(int64_t *pres, const bf_t *a, int flags)
{
uint64_t v;
int ret;
if (a->expn >= BF_EXP_INF) {
ret = BF_ST_INVALID_OP;
if (flags & BF_GET_INT_MOD) {
v = 0;
} else if (a->expn == BF_EXP_INF) {
v = (uint64_t)INT64_MAX + a->sign;
} else {
v = INT64_MAX;
}
} else if (a->expn <= 0) {
v = 0;
ret = 0;
} else if (a->expn <= 63) {
#if LIMB_BITS == 32
if (a->expn <= 32)
v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn);
else
v = (((uint64_t)a->tab[a->len - 1] << 32) |
get_limbz(a, a->len - 2)) >> (64 - a->expn);
#else
v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn);
#endif
if (a->sign)
v = -v;
ret = 0;
} else if (!(flags & BF_GET_INT_MOD)) {
ret = BF_ST_INVALID_OP;
if (a->sign) {
uint64_t v1;
v = (uint64_t)INT64_MAX + 1;
if (a->expn == 64) {
v1 = a->tab[a->len - 1];
#if LIMB_BITS == 32
v1 = (v1 << 32) | get_limbz(a, a->len - 2);
#endif
if (v1 == v)
ret = 0;
}
} else {
v = INT64_MAX;
}
} else {
slimb_t bit_pos = a->len * LIMB_BITS - a->expn;
v = get_bits(a->tab, a->len, bit_pos);
#if LIMB_BITS == 32
v |= (uint64_t)get_bits(a->tab, a->len, bit_pos + 32) << 32;
#endif
if (a->sign)
v = -v;
ret = 0;
}
*pres = v;
return ret;
}
For example, the number 2^64 + 2
, its a->expn
is 64
. While using BF_GET_INT_MOD
flag, it goes to the final else
block.
As BF_GET_INT_MOD
implies that modulo 2^n instead of saturation. NaN and infinity return 0
, finally we got the v
as 2
which is from (2^64 + 2) % 2^64
, and ret=0
which means returns a right result
.
Then everything mess up.
That is to say that we cannot judge the return value from JS_ToBigInt64Free
(as well as JS_ToBigInt64
) whether it is the actual result or the actual result mods 2^64
It seems to be a problem that has been around for a long time, and a solution has already been suggested: https://github.com/theduke/quickjs-rs/blob/master/libquickjs-sys/embed/patches/js-tobigint64-overflow.patch
I've tried to apply the patch and the problem goes fixed.
So I think maybe it should be adopted?