Skip to content

Commit 9ae7b92

Browse files
authored
fix(es/minifier): apply ToUint16 in fromCharCode (#12055)
**Description:** `String.fromCharCode` applies ECMAScript `ToUint16` to every argument. The current floor-and-cast folding path does not match that behavior for negative, wrapping, or non-finite inputs. This PR adds `JsNumber::to_uint16` and uses it before folding ASCII `String.fromCharCode` results. Boundary coverage keeps the optimization limited to values whose output can be produced safely. **Related issue (if exists):** - #12043 - #12047 - #12050 - #12048 - #12051 - #12052 - #12053 - #12054 - **#12055** (current) - #12056 - #12057 - #12058 - #12049 - #12059 - #12060
1 parent 850230b commit 9ae7b92

7 files changed

Lines changed: 60 additions & 3 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
swc_core: minor
3+
swc_ecma_minifier: patch
4+
swc_ecma_utils: minor
5+
---
6+
7+
fix(es/minifier): Apply `ToUint16` when folding `String.fromCharCode`.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::num::FpCategory;
33
use swc_atoms::atom;
44
use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP};
55
use swc_ecma_ast::*;
6-
use swc_ecma_utils::{ExprExt, Value::Known};
6+
use swc_ecma_utils::{number::JsNumber, ExprExt, Value::Known};
77

88
use super::{BitCtx, Optimizer};
99
use crate::{
@@ -334,15 +334,15 @@ impl Optimizer<'_> {
334334
}
335335

336336
if let Known(char_code) = args[0].expr.as_pure_number(self.ctx.expr_ctx) {
337-
let v = char_code.floor() as u32;
337+
let v = u32::from(JsNumber::from(char_code).to_uint16());
338338

339339
if let Some(v) = char::from_u32(v) {
340340
if !v.is_ascii() {
341341
return;
342342
}
343343
self.changed = true;
344344
report_change!(
345-
"evaluate: Evaluated `String.charCodeAt({})` as `{}`",
345+
"evaluate: Evaluated `String.fromCharCode({})` as `{}`",
346346
char_code,
347347
v
348348
);
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+
65,65535,1,0,1,0,0,0,65,65
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
console.log([
2+
String.fromCharCode(65.9),
3+
String.fromCharCode(-1),
4+
String.fromCharCode(-65535.9),
5+
String.fromCharCode(65536),
6+
String.fromCharCode(65537),
7+
String.fromCharCode(NaN),
8+
String.fromCharCode(Infinity),
9+
String.fromCharCode(-Infinity),
10+
String.fromCharCode(4294967361),
11+
String.fromCharCode(-4294967231),
12+
].map((value) => value.charCodeAt(0)).join(","));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
console.log([
2+
"A",
3+
String.fromCharCode(-1),
4+
"\x01",
5+
"\0",
6+
"\x01",
7+
"\0",
8+
String.fromCharCode(1 / 0),
9+
String.fromCharCode(-1 / 0),
10+
"A",
11+
"A"
12+
].map((value)=>value.charCodeAt(0)).join(","));

crates/swc_ecma_utils/src/number.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ impl std::ops::Deref for JsNumber {
5353
}
5454

5555
impl JsNumber {
56+
/// Applies ECMAScript `ToUint16` conversion.
57+
///
58+
/// <https://tc39.es/ecma262/#sec-touint16>
59+
pub fn to_uint16(self) -> u16 {
60+
self.as_uint32() as u16
61+
}
62+
5663
// https://tc39.es/ecma262/#sec-toint32
5764
fn as_int32(&self) -> i32 {
5865
self.as_uint32() as i32
@@ -313,6 +320,20 @@ mod test_js_number {
313320
assert_eq!(JsNumber(-8.0).as_uint32(), 4294967288);
314321
}
315322

323+
#[test]
324+
fn test_to_uint16() {
325+
assert_eq!(JsNumber(65.9).to_uint16(), 65);
326+
assert_eq!(JsNumber(-1.0).to_uint16(), 65535);
327+
assert_eq!(JsNumber(-65535.9).to_uint16(), 1);
328+
assert_eq!(JsNumber(65536.0).to_uint16(), 0);
329+
assert_eq!(JsNumber(65537.0).to_uint16(), 1);
330+
assert_eq!(JsNumber(f64::NAN).to_uint16(), 0);
331+
assert_eq!(JsNumber(f64::INFINITY).to_uint16(), 0);
332+
assert_eq!(JsNumber(f64::NEG_INFINITY).to_uint16(), 0);
333+
assert_eq!(JsNumber(4294967361.0).to_uint16(), 65);
334+
assert_eq!(JsNumber(-4294967231.0).to_uint16(), 65);
335+
}
336+
316337
#[test]
317338
fn test_add() {
318339
assert_eq!(JsNumber(1.0) + JsNumber(2.0), JsNumber(3.0));

0 commit comments

Comments
 (0)