Skip to content

Commit 506a0ea

Browse files
authored
fix(es/minifier): avoid inexact number radix folding (#12057)
**Description:** `Number#toString(radix)` folding currently converts integer values through `u128`. Unsafe integers can already have rounded representations, so this conversion can produce digits that do not match JavaScript for non-power-of-two radices. This PR restricts radix folding to safe integers, with a separate exact path for supported power-of-two radices below the `u128` bound. Values outside those limits remain for runtime evaluation. **Related issue (if exists):** - #12043 - #12047 - #12050 - #12048 - #12051 - #12052 - #12053 - #12054 - #12055 - #12056 - **#12057** (current) - #12058 - #12049 - #12059 - #12060
1 parent aa49e36 commit 506a0ea

6 files changed

Lines changed: 63 additions & 15 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: patch
3+
swc_ecma_minifier: patch
4+
---
5+
6+
fix(es/minifier): Avoid inexact number radix folding

crates/swc_ecma_minifier/src/compress/pure/evaluate.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ use crate::{
1919
util::ValueExt,
2020
};
2121

22+
/// Formats an integer with `radix_fmt` only where its exact integer output is
23+
/// also a valid ECMAScript `Number::toString` result.
24+
fn format_integer_in_radix(value: f64, radix: u8) -> Option<String> {
25+
const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991_f64;
26+
27+
let magnitude = value.abs();
28+
let is_safe_integer = magnitude <= MAX_SAFE_INTEGER;
29+
let is_supported_binary_radix = radix.is_power_of_two() && magnitude < u128::MAX as f64;
30+
31+
if !is_safe_integer && !is_supported_binary_radix {
32+
return None;
33+
}
34+
35+
let digits = Radix::new(magnitude as u128, radix).to_string();
36+
if value >= 0_f64 {
37+
return Some(digits);
38+
}
39+
40+
let mut output = String::with_capacity(digits.len() + 1);
41+
output.push('-');
42+
output.push_str(&digits);
43+
Some(output)
44+
}
45+
2246
impl Pure<'_> {
2347
///
2448
/// - `1 == 1` => `true`
@@ -668,26 +692,17 @@ impl Pure<'_> {
668692

669693
if num.value.fract() == 0.0 && (2.0..=36.0).contains(&base) && base.fract() == 0.0 {
670694
let base = base.floor() as u8;
671-
672-
self.changed = true;
673-
674-
let value = {
675-
let x = num.value;
676-
if x < 0. {
677-
// I don't know if u128 is really needed, but it works.
678-
format!("-{}", Radix::new(-x as u128, base))
679-
} else {
680-
Radix::new(x as u128, base).to_string()
681-
}
682-
}
683-
.into();
695+
let Some(value) = format_integer_in_radix(num.value, base) else {
696+
return;
697+
};
684698

685699
*e = Lit::Str(Str {
686700
span: e.span(),
687701
raw: None,
688-
value,
702+
value: value.into(),
689703
})
690-
.into()
704+
.into();
705+
self.changed = true;
691706
}
692707
}
693708
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"defaults": true,
3+
"passes": 2
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1121202011211211122211100012101111,1121202011211211122211100012101111,-1121202011211211122211100012101111,l3r41ifs0p800,-l3r41ifs0p800,80000000000000000000000000000000,-80000000000000000000000000000000,100000000000000000000000000000000,-100000000000000000000000000000000
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
console.log([
2+
(9007199254740991).toString(3),
3+
(9007199254740994).toString(3),
4+
(-9007199254740994).toString(3),
5+
(1e20).toString(36),
6+
(-1e20).toString(36),
7+
(2 ** 127).toString(16),
8+
(-(2 ** 127)).toString(16),
9+
(2 ** 128).toString(16),
10+
(-(2 ** 128)).toString(16),
11+
].join(","));
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
console.log([
2+
"1121202011211211122211100012101111",
3+
9007199254740994..toString(3),
4+
(-9007199254740994).toString(3),
5+
1e20.toString(36),
6+
(-100000000000000000000).toString(36),
7+
"80000000000000000000000000000000",
8+
"-80000000000000000000000000000000",
9+
3.402823669209385e+38.toString(16),
10+
(-3.402823669209385e+38).toString(16)
11+
].join(","));

0 commit comments

Comments
 (0)