Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(es/minifier): Fix panic in bitwise logic and incorrect values #9258

Merged
merged 10 commits into from
Jul 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -633,13 +633,6 @@ impl SimplifyExpr {

// Bit shift operations
op!("<<") | op!(">>") | op!(">>>") => {
/// Uses a method for treating a double as 32bits that is
/// equivalent to how JavaScript would convert a
/// number before applying a bit operation.
fn js_convert_double_to_bits(d: f64) -> i32 {
((d.floor() as i64) & 0xffff_ffff) as i32
}

fn try_fold_shift(
ctx: &ExprCtx,
op: BinaryOp,
Expand All @@ -654,38 +647,12 @@ impl SimplifyExpr {
(Known(lv), Known(rv)) => (lv, rv),
_ => unreachable!(),
};

// only the lower 5 bits are used when shifting, so don't do anything
// if the shift amount is outside [0,32)
if !(0.0..32.0).contains(&rv) {
return Unknown;
}

let rv_int = rv as i32;
if rv_int as f64 != rv {
unimplemented!("error reporting: FRACTIONAL_BITWISE_OPERAND")
// report(FRACTIONAL_BITWISE_OPERAND, right.span());
// return n;
}

if lv.floor() != lv {
unimplemented!("error reporting: FRACTIONAL_BITWISE_OPERAND")
// report(FRACTIONAL_BITWISE_OPERAND, left.span());
// return n;
}

let bits = js_convert_double_to_bits(lv);
let (lv, rv) = (JsNumber::from(lv), JsNumber::from(rv));

Known(match op {
op!("<<") => (bits << rv_int) as f64,
op!(">>") => (bits >> rv_int) as f64,
op!(">>>") => {
let res = bits as u32 >> rv_int as u32;
// JavaScript always treats the result of >>> as unsigned.
// We must force Java to do the same here.
// unimplemented!(">>> (Zerofill rshift)")
res as f64
}
op!("<<") => *(lv << rv),
op!(">>") => *(lv >> rv),
op!(">>>") => *(lv.unsigned_shr(rv)),

_ => unreachable!("Unknown bit operator {:?}", op),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,27 @@ fn test_fold_bitwise_op2() {
fold("x = 12 | NaN", "x=12");
}

#[test]
fn test_issue_9256() {
// Returns -2 prior to fix (Number.MAX_VALUE)
fold("1.7976931348623157e+308 << 1", "0");

// Isn't changed prior to fix
fold("1.7976931348623157e+308 << 1.7976931348623157e+308", "0");
fold("1.7976931348623157e+308 >> 1.7976931348623157e+308", "0");

// Panics prior to fix (Number.MIN_VALUE)
fold("5e-324 >> 5e-324", "0");
fold("5e-324 << 5e-324", "0");
fold("5e-324 << 0", "0");
fold("0 << 5e-324", "0");

// Wasn't broken prior, used to ensure overflows are handled correctly
fold("1 << 31", "-2147483648");
fold("-8 >> 2", "-2");
fold("-8 >>> 2", "1073741822");
}

#[test]
#[ignore]
fn test_folding_mix_types_early() {
Expand Down Expand Up @@ -726,9 +747,9 @@ fn test_fold_bit_shifts() {

fold("x = 0xffffffff << 0", "x = -1");
fold("x = 0xffffffff << 4", "x = -16");
fold_same("1 << 32");
fold_same("1 << -1");
fold_same("1 >> 32");
fold("1 << 32", "1");
fold("1 << -1", "-2147483648");
fold("1 >> 32", "1");
}

#[test]
Expand Down
10 changes: 4 additions & 6 deletions crates/swc_ecma_utils/src/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ impl std::ops::Deref for JsNumber {
impl JsNumber {
// https://tc39.es/ecma262/#sec-toint32
fn as_int32(&self) -> i32 {
if !self.0.is_finite() {
return 0;
}

self.0.trunc() as i32
self.as_uint32() as i32
}

// https://tc39.es/ecma262/#sec-touint32
Expand All @@ -49,7 +45,8 @@ impl JsNumber {
return 0;
}

self.0.trunc() as u32
// pow(2, 32) = 4294967296
self.0.trunc().rem_euclid(4294967296.0) as u32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the implementation, and all the tests have passed.
Take a look to see if we need to add more test cases. @levi-nz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, maybe we could see if v8 or another project has some test cases? https://github.com/v8/v8/blob/main/src/numbers/conversions.h

I think the current test cases are (probably) fine, as long as we're testing for overflowing, the rest should work fine

}
}

Expand Down Expand Up @@ -211,6 +208,7 @@ mod test_js_number {
assert_eq!(JsNumber(-0.0).as_uint32(), 0);
assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0);
assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0);
assert_eq!(JsNumber(-8.0).as_uint32(), 4294967288);
}

#[test]
Expand Down
Loading