Avoid UB when checking if float fits in int32

This commit is contained in:
Ben Noordhuis 2023-11-08 19:03:53 +01:00
parent 2f51cbc4e6
commit 0068db8a11
3 changed files with 48 additions and 23 deletions

View file

@ -10797,6 +10797,7 @@ static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val)
ret = v << ((e - 1023) - 52); ret = v << ((e - 1023) - 52);
/* take the sign into account */ /* take the sign into account */
if (u.u64 >> 63) if (u.u64 >> 63)
if (ret != INT64_MIN)
ret = -ret; ret = -ret;
} else { } else {
ret = 0; /* also handles NaN and +inf */ ret = 0; /* also handles NaN and +inf */
@ -10872,6 +10873,7 @@ static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val)
ret = v >> 32; ret = v >> 32;
/* take the sign into account */ /* take the sign into account */
if (u.u64 >> 63) if (u.u64 >> 63)
if (ret != INT32_MIN)
ret = -ret; ret = -ret;
} else { } else {
ret = 0; /* also handles NaN and +inf */ ret = 0; /* also handles NaN and +inf */
@ -11968,6 +11970,45 @@ static double js_pow(double a, double b)
} }
} }
// Special care is taken to not invoke UB when checking if the result fits
// in an int32_t. Leans on the fact that the input is integral if the lower
// 52 bits of the equation 2**e * (f + 2**52) are zero.
static BOOL float_is_int32(double d)
{
uint64_t u, m, e, f;
JSFloat64Union t;
t.d = d;
u = t.u64;
// special case -0
m = 1ull << 63;
if (u == m)
return FALSE;
e = (u >> 52) & 0x7FF;
if (e > 0)
e -= 1023;
// too large, nan or inf?
if (e > 30)
return FALSE;
// fractional or subnormal if low bits are non-zero
f = 0xFFFFFFFFFFFFFull & u;
m = 0xFFFFFFFFFFFFFull >> e;
return 0 == (f & m);
}
JSValue JS_NewFloat64(JSContext *ctx, double d)
{
if (float_is_int32(d)) {
return JS_MKVAL(JS_TAG_INT, (int32_t)d);
} else {
return __JS_NewFloat64(ctx, d);
}
}
#ifdef CONFIG_BIGNUM #ifdef CONFIG_BIGNUM
JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v)

View file

@ -539,30 +539,10 @@ static js_force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val)
return v; return v;
} }
JSValue JS_NewFloat64(JSContext *ctx, double d);
JSValue JS_NewBigInt64(JSContext *ctx, int64_t v); JSValue JS_NewBigInt64(JSContext *ctx, int64_t v);
JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v); JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v);
static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double d)
{
JSValue v;
int32_t val;
union {
double d;
uint64_t u;
} u, t;
u.d = d;
val = (int32_t)d;
t.d = val;
/* -0 cannot be represented as integer, so we compare the bit
representation */
if (u.u == t.u) {
v = JS_MKVAL(JS_TAG_INT, val);
} else {
v = __JS_NewFloat64(ctx, d);
}
return v;
}
static inline JS_BOOL JS_IsNumber(JSValueConst v) static inline JS_BOOL JS_IsNumber(JSValueConst v)
{ {
int tag = JS_VALUE_GET_TAG(v); int tag = JS_VALUE_GET_TAG(v);

View file

@ -332,6 +332,10 @@ function test_number()
assert(+" 123 ", 123); assert(+" 123 ", 123);
assert(+"0b111", 7); assert(+"0b111", 7);
assert(+"0o123", 83); assert(+"0o123", 83);
assert(parseFloat("2147483647"), 2147483647);
assert(parseFloat("2147483648"), 2147483648);
assert(parseFloat("-2147483647"), -2147483647);
assert(parseFloat("-2147483648"), -2147483648);
assert(parseFloat("0x1234"), 0); assert(parseFloat("0x1234"), 0);
assert(parseFloat("Infinity"), Infinity); assert(parseFloat("Infinity"), Infinity);
assert(parseFloat("-Infinity"), -Infinity); assert(parseFloat("-Infinity"), -Infinity);