Skip to content

Commit a47c70e

Browse files
committed
fix(minifier): fix remaining runtime bugs (oxc-project#6855)
1 parent a366fae commit a47c70e

File tree

9 files changed

+92
-127
lines changed

9 files changed

+92
-127
lines changed

crates/oxc_ast/src/ast_impl/js.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -271,24 +271,6 @@ impl<'a> Expression<'a> {
271271
}
272272
}
273273

274-
#[allow(missing_docs)]
275-
pub fn is_immutable_value(&self) -> bool {
276-
match self {
277-
Self::BooleanLiteral(_)
278-
| Self::NullLiteral(_)
279-
| Self::NumericLiteral(_)
280-
| Self::BigIntLiteral(_)
281-
| Self::RegExpLiteral(_)
282-
| Self::StringLiteral(_) => true,
283-
Self::TemplateLiteral(lit) if lit.is_no_substitution_template() => true,
284-
Self::UnaryExpression(unary_expr) => unary_expr.argument.is_immutable_value(),
285-
Self::Identifier(ident) => {
286-
matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
287-
}
288-
_ => false,
289-
}
290-
}
291-
292274
#[allow(missing_docs)]
293275
pub fn is_require_call(&self) -> bool {
294276
if let Self::CallExpression(call_expr) = self {

crates/oxc_ecmascript/src/constant_evaluation/is_litral_value.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ pub trait IsLiteralValue<'a, 'b> {
1717
fn is_literal_value(&self, include_functions: bool) -> bool;
1818
}
1919

20+
pub fn is_immutable_value(expr: &Expression<'_>) -> bool {
21+
match expr {
22+
Expression::BooleanLiteral(_)
23+
| Expression::NullLiteral(_)
24+
| Expression::NumericLiteral(_)
25+
| Expression::BigIntLiteral(_)
26+
| Expression::RegExpLiteral(_)
27+
| Expression::StringLiteral(_) => true,
28+
Expression::TemplateLiteral(lit) if lit.is_no_substitution_template() => true,
29+
Expression::Identifier(ident) => {
30+
matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
31+
}
32+
_ => false,
33+
}
34+
}
35+
2036
impl<'a, 'b> IsLiteralValue<'a, 'b> for Expression<'a> {
2137
fn is_literal_value(&self, include_functions: bool) -> bool {
2238
match self {
@@ -27,7 +43,7 @@ impl<'a, 'b> IsLiteralValue<'a, 'b> for Expression<'a> {
2743
Self::ObjectExpression(expr) => {
2844
expr.properties.iter().all(|property| property.is_literal_value(include_functions))
2945
}
30-
_ => self.is_immutable_value(),
46+
_ => is_immutable_value(self),
3147
}
3248
}
3349
}

crates/oxc_ecmascript/src/constant_evaluation/mod.rs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -222,19 +222,7 @@ pub trait ConstantEvaluation<'a> {
222222
let rval = self.eval_to_number(right)?;
223223
let val = match e.operator {
224224
BinaryOperator::Subtraction => lval - rval,
225-
BinaryOperator::Division => {
226-
if rval.is_zero() {
227-
if lval.is_zero() || lval.is_nan() || lval.is_infinite() {
228-
f64::NAN
229-
} else if lval.is_sign_positive() {
230-
f64::INFINITY
231-
} else {
232-
f64::NEG_INFINITY
233-
}
234-
} else {
235-
lval / rval
236-
}
237-
}
225+
BinaryOperator::Division => lval / rval,
238226
BinaryOperator::Remainder => {
239227
if rval.is_zero() {
240228
f64::NAN
@@ -332,17 +320,17 @@ pub trait ConstantEvaluation<'a> {
332320
fn eval_unary_expression(&self, expr: &UnaryExpression<'a>) -> Option<ConstantValue<'a>> {
333321
match expr.operator {
334322
UnaryOperator::Typeof => {
335-
if !expr.argument.is_literal_value(true) {
336-
return None;
337-
}
338323
let s = match &expr.argument {
324+
Expression::ObjectExpression(_) | Expression::ArrayExpression(_)
325+
if expr.argument.is_literal_value(true) =>
326+
{
327+
"object"
328+
}
339329
Expression::FunctionExpression(_) => "function",
340330
Expression::StringLiteral(_) => "string",
341331
Expression::NumericLiteral(_) => "number",
342332
Expression::BooleanLiteral(_) => "boolean",
343-
Expression::NullLiteral(_)
344-
| Expression::ObjectExpression(_)
345-
| Expression::ArrayExpression(_) => "object",
333+
Expression::NullLiteral(_) => "object",
346334
Expression::UnaryExpression(e) if e.operator == UnaryOperator::Void => {
347335
"undefined"
348336
}

crates/oxc_ecmascript/src/string_to_number.rs

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,26 @@ pub trait StringToNumber {
77
/// <https://tc39.es/ecma262/#sec-stringtonumber>
88
impl StringToNumber for &str {
99
fn string_to_number(&self) -> f64 {
10-
let s = self.trim_matches(is_trimmable_whitespace);
11-
10+
let s = *self;
1211
match s {
1312
"" => return 0.0,
1413
"-Infinity" => return f64::NEG_INFINITY,
1514
"Infinity" | "+Infinity" => return f64::INFINITY,
16-
// Make sure that no further variants of "infinity" are parsed.
17-
"inf" | "-inf" | "+inf" => return f64::NAN,
18-
_ => {}
15+
_ => {
16+
// Make sure that no further variants of "infinity" are parsed by `f64::parse`.
17+
// Note that alphabetical characters are not case-sensitive.
18+
// <https://doc.rust-lang.org/std/primitive.f64.html#method.from_str>
19+
let mut bytes = s.trim_start_matches(['-', '+']).bytes();
20+
if bytes
21+
.next()
22+
.filter(|c| c.to_ascii_lowercase() == b'i')
23+
.and_then(|_| bytes.next().filter(|c| c.to_ascii_lowercase() == b'n'))
24+
.and_then(|_| bytes.next().filter(|c| c.to_ascii_lowercase() == b'f'))
25+
.is_some()
26+
{
27+
return f64::NAN;
28+
}
29+
}
1930
}
2031

2132
let mut bytes = s.bytes();
@@ -52,24 +63,3 @@ impl StringToNumber for &str {
5263
s.parse::<f64>().unwrap_or(f64::NAN)
5364
}
5465
}
55-
56-
// <https://github.com/boa-dev/boa/blob/94d08fe4e68791ceca3c4b7d94ccc5f3588feeb3/core/string/src/lib.rs#L55>
57-
/// Helper function to check if a `char` is trimmable.
58-
pub(crate) const fn is_trimmable_whitespace(c: char) -> bool {
59-
// The rust implementation of `trim` does not regard the same characters whitespace as ecma standard does
60-
//
61-
// Rust uses \p{White_Space} by default, which also includes:
62-
// `\u{0085}' (next line)
63-
// And does not include:
64-
// '\u{FEFF}' (zero width non-breaking space)
65-
// Explicit whitespace: https://tc39.es/ecma262/#sec-white-space
66-
matches!(
67-
c,
68-
'\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' |
69-
// Unicode Space_Separator category
70-
'\u{1680}' | '\u{2000}'
71-
..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' |
72-
// Line terminators: https://tc39.es/ecma262/#sec-line-terminators
73-
'\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}'
74-
)
75-
}

crates/oxc_linter/src/rules/eslint/prefer_numeric_literals.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ impl Rule for PreferNumericLiterals {
100100
}
101101

102102
fn is_string_type(arg: &Argument) -> bool {
103-
matches!(arg, Argument::StringLiteral(_) | Argument::TemplateLiteral(_))
103+
match arg {
104+
Argument::StringLiteral(_) => true,
105+
Argument::TemplateLiteral(t) => t.is_no_substitution_template(),
106+
_ => false,
107+
}
104108
}
105109

106110
fn is_parse_int_call(
@@ -123,7 +127,7 @@ fn check_arguments<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) {
123127
}
124128

125129
let string_arg = &call_expr.arguments[0];
126-
if !is_string_type(string_arg) || !string_arg.to_expression().is_immutable_value() {
130+
if !is_string_type(string_arg) {
127131
return;
128132
}
129133

crates/oxc_linter/src/rules/import/no_dynamic_require.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ impl Rule for NoDynamicRequire {
8787
}
8888

8989
fn is_static_value(expr: &Expression) -> bool {
90-
expr.is_string_literal() && expr.is_immutable_value()
90+
match expr {
91+
Expression::StringLiteral(_) => true,
92+
Expression::TemplateLiteral(t) => t.is_no_substitution_template(),
93+
_ => false,
94+
}
9195
}
9296

9397
#[test]

crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,20 @@ mod test {
12061206

12071207
test("x = Infinity / Infinity", "x = NaN");
12081208
test("x = Infinity % Infinity", "x = NaN");
1209-
test("x = Infinity / 0", "x = NaN");
1209+
test("x = Infinity / 0", "x = Infinity");
12101210
test("x = Infinity % 0", "x = NaN");
12111211
}
1212+
1213+
#[test]
1214+
fn test_to_number() {
1215+
test("x = +''", "x = 0");
1216+
test("x = +'+Infinity'", "x = Infinity");
1217+
test("x = +'-Infinity'", "x = -Infinity");
1218+
1219+
for op in ["", "+", "-"] {
1220+
for s in ["inf", "infinity", "INFINITY", "InFiNiTy"] {
1221+
test(&format!("x = +'{op}{s}'"), "x = NaN");
1222+
}
1223+
}
1224+
}
12121225
}

crates/oxc_minifier/src/ast_passes/peephole_remove_dead_code.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,11 @@ impl<'a, 'b> PeepholeRemoveDeadCode {
204204
) -> Option<Statement<'a>> {
205205
let test_boolean = for_stmt.test.as_ref().and_then(|test| ctx.get_boolean_value(test));
206206
match test_boolean {
207-
Some(false) => {
208-
// Remove the entire `for` statement.
209-
// Check vars in statement
210-
let mut keep_var = KeepVar::new(ctx.ast);
211-
keep_var.visit_statement(&for_stmt.body);
212-
213-
let mut var_decl = keep_var.get_variable_declaration();
214-
215-
if let Some(ForStatementInit::VariableDeclaration(var_init)) = &mut for_stmt.init {
207+
Some(false) => match &mut for_stmt.init {
208+
Some(ForStatementInit::VariableDeclaration(var_init)) => {
209+
let mut keep_var = KeepVar::new(ctx.ast);
210+
keep_var.visit_statement(&for_stmt.body);
211+
let mut var_decl = keep_var.get_variable_declaration();
216212
if var_init.kind.is_var() {
217213
if let Some(var_decl) = &mut var_decl {
218214
var_decl
@@ -222,14 +218,27 @@ impl<'a, 'b> PeepholeRemoveDeadCode {
222218
var_decl = Some(ctx.ast.move_variable_declaration(var_init));
223219
}
224220
}
221+
Some(var_decl.map_or_else(
222+
|| ctx.ast.statement_empty(SPAN),
223+
|var_decl| {
224+
ctx.ast
225+
.statement_declaration(ctx.ast.declaration_from_variable(var_decl))
226+
},
227+
))
225228
}
226-
227-
var_decl
228-
.map(|var_decl| {
229-
ctx.ast.statement_declaration(ctx.ast.declaration_from_variable(var_decl))
230-
})
231-
.or_else(|| Some(ctx.ast.statement_empty(SPAN)))
232-
}
229+
None => {
230+
let mut keep_var = KeepVar::new(ctx.ast);
231+
keep_var.visit_statement(&for_stmt.body);
232+
Some(keep_var.get_variable_declaration().map_or_else(
233+
|| ctx.ast.statement_empty(SPAN),
234+
|var_decl| {
235+
ctx.ast
236+
.statement_declaration(ctx.ast.declaration_from_variable(var_decl))
237+
},
238+
))
239+
}
240+
_ => None,
241+
},
233242
Some(true) => {
234243
// Remove the test expression.
235244
for_stmt.test = None;
@@ -508,6 +517,7 @@ mod test {
508517
fold("for (var se = [1, 2]; false;);", "var se = [1, 2];");
509518
fold("for (var se = [1, 2]; false;) { var a = 0; }", "var se = [1, 2], a;");
510519

520+
fold("for (foo = bar; false;) {}", "for (foo = bar; false;);");
511521
// fold("l1:for(;false;) { }", "");
512522
}
513523

tasks/coverage/snapshots/runtime.snap

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ commit: 06454619
22

33
runtime Summary:
44
AST Parsed : 18444/18444 (100.00%)
5-
Positive Passed: 18273/18444 (99.07%)
5+
Positive Passed: 18287/18444 (99.15%)
66
tasks/coverage/test262/test/annexB/language/function-code/if-decl-else-decl-a-func-existing-block-fn-no-init.js
77
minify error: Test262Error: Expected SameValue(«function f(){}», «undefined») to be true
88

@@ -318,9 +318,6 @@ minify error: SyntaxError: Identifier 'f' has already been declared
318318
tasks/coverage/test262/test/annexB/language/global-code/if-stmt-else-decl-global-skip-early-err.js
319319
minify error: SyntaxError: Identifier 'f' has already been declared
320320

321-
tasks/coverage/test262/test/language/expressions/addition/S11.6.1_A4_T5.js
322-
minify error: Test262Error: #1.1: -0 + -0 === - 0. Actual: +0
323-
324321
tasks/coverage/test262/test/language/expressions/array/spread-obj-getter-init.js
325322
transform error: Test262Error: Expected SameValuetrue», «false») to be true
326323

@@ -330,12 +327,6 @@ codegen error: Test262Error: descriptor value should be ; object value should be
330327
tasks/coverage/test262/test/language/expressions/call/spread-obj-getter-init.js
331328
transform error: Test262Error: Expected SameValuetrue», «false») to be true
332329

333-
tasks/coverage/test262/test/language/expressions/conditional/in-branch-1.js
334-
minify error: Test262Error: Expected SameValue0», «1») to be true
335-
336-
tasks/coverage/test262/test/language/expressions/division/S11.5.2_A4_T8.js
337-
minify error: Test262Error: #1.2: -0 / 1 === - 0. Actual: +0
338-
339330
tasks/coverage/test262/test/language/expressions/does-not-equals/bigint-and-object.js
340331
minify error: Test262Error: The result of (0n != {valueOf: function() {return 0n;}}) is false Expected SameValuetrue», «false») to be true
341332

@@ -345,9 +336,6 @@ codegen error: Test262Error: p1 constructor is %Promise% Expected SameValue(«fu
345336
tasks/coverage/test262/test/language/expressions/dynamic-import/eval-self-once-module.js
346337
codegen error: Test262Error: global property was defined and incremented only once Expected SameValue2», «1») to be true
347338

348-
tasks/coverage/test262/test/language/expressions/dynamic-import/import-attributes/2nd-param-in.js
349-
minify error: TypeError: Cannot read properties of undefined (reading 'then')
350-
351339
tasks/coverage/test262/test/language/expressions/dynamic-import/imported-self-update.js
352340
codegen error: Test262Error: updated value, direct binding Expected SameValue0», «1») to be true
353341

@@ -381,24 +369,6 @@ minify error: Test262Error: The result of (0n == {valueOf: function() {return 0n
381369
tasks/coverage/test262/test/language/expressions/exponentiation/bigint-negative-exponent-throws.js
382370
transform error: Test262Error: (-1n) ** -1n throws RangeError Expected a RangeError but got a TypeError
383371

384-
tasks/coverage/test262/test/language/expressions/logical-and/S11.11.1_A3_T2.js
385-
minify error: Test262Error: #1.2: (-0 && -1) === -0
386-
387-
tasks/coverage/test262/test/language/expressions/logical-and/S11.11.1_A4_T2.js
388-
minify error: Test262Error: #1.2: (-1 && -0) === -0
389-
390-
tasks/coverage/test262/test/language/expressions/logical-or/S11.11.2_A3_T2.js
391-
minify error: Test262Error: #1.2: (0 || -0) === -0
392-
393-
tasks/coverage/test262/test/language/expressions/modulus/S11.5.3_A4_T2.js
394-
minify error: Test262Error: #2.2: -1 % -1 === - 0. Actual: +0
395-
396-
tasks/coverage/test262/test/language/expressions/modulus/S11.5.3_A4_T6.js
397-
minify error: Test262Error: #3.2: -0 % 1 === - 0. Actual: +0
398-
399-
tasks/coverage/test262/test/language/expressions/multiplication/S11.5.1_A4_T2.js
400-
minify error: Test262Error: #6.2: 0 * -0 === - 0. Actual: +0
401-
402372
tasks/coverage/test262/test/language/expressions/new/spread-obj-getter-init.js
403373
transform error: Test262Error: Expected SameValuetrue», «false») to be true
404374

@@ -414,21 +384,9 @@ transform error: TypeError: Cannot read properties of undefined (reading 'enumer
414384
tasks/coverage/test262/test/language/expressions/object/object-spread-proxy-ownkeys-returned-keys-order.js
415385
transform error: TypeError: Cannot read properties of undefined (reading 'enumerable')
416386

417-
tasks/coverage/test262/test/language/expressions/subtraction/S11.6.2_A4_T5.js
418-
minify error: Test262Error: #3.2: -0 - 0 === - 0. Actual: +0
419-
420387
tasks/coverage/test262/test/language/expressions/super/call-spread-obj-getter-init.js
421388
transform error: Test262Error: Expected SameValuetrue», «false») to be true
422389

423-
tasks/coverage/test262/test/language/expressions/unary-plus/S11.4.6_A3_T3.js
424-
minify error: Test262Error: #4: +"INFINITY" === Not-a-Number. Actual: Infinity
425-
426-
tasks/coverage/test262/test/language/expressions/unary-plus/S9.3_A4.2_T2.js
427-
minify error: Test262Error: #3.2: +(-0) === -0. Actual: +0
428-
429-
tasks/coverage/test262/test/language/expressions/unary-plus/bigint-throws.js
430-
minify error: Test262Error: +0n throws TypeError Expected a TypeError to be thrown but no exception was thrown at all
431-
432390
tasks/coverage/test262/test/language/global-code/decl-func.js
433391
codegen error: Test262Error: descriptor should not be configurable
434392

0 commit comments

Comments
 (0)