Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ mod to_numeric;
mod to_primitive;
mod to_string;

// other
mod to_integer_index;

pub mod constant_evaluation;
pub mod is_global_reference;
pub mod side_effects;
Expand All @@ -44,6 +47,7 @@ pub use self::{
to_big_int::ToBigInt,
to_boolean::ToBoolean,
to_int_32::ToInt32,
to_integer_index::ToIntegerIndex,
to_number::ToNumber,
to_primitive::ToPrimitive,
to_string::ToJsString,
Expand Down
54 changes: 52 additions & 2 deletions crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use oxc_ast::ast::*;

use crate::{
constant_evaluation::DetermineValueType, is_global_reference::IsGlobalReference,
to_numeric::ToNumeric, to_primitive::ToPrimitive,
ToBigInt, ToIntegerIndex, constant_evaluation::DetermineValueType,
is_global_reference::IsGlobalReference, to_numeric::ToNumeric, to_primitive::ToPrimitive,
};

/// Returns true if subtree changes application state.
Expand Down Expand Up @@ -448,6 +448,27 @@ impl MayHaveSideEffects for ComputedMemberExpression<'_> {
is_global_reference,
)
}
Expression::NumericLiteral(n) => !n.value.to_integer_index().is_some_and(|n| {
!integer_index_property_access_may_have_side_effects(
&self.object,
n,
is_global_reference,
)
}),
Expression::BigIntLiteral(b) => {
if b.is_negative() {
return true;
}
!b.to_big_int(is_global_reference)
.and_then(ToIntegerIndex::to_integer_index)
.is_some_and(|b| {
!integer_index_property_access_may_have_side_effects(
&self.object,
b,
is_global_reference,
)
})
}
_ => true,
}
}
Expand All @@ -467,6 +488,35 @@ fn property_access_may_have_side_effects(
}
}

fn integer_index_property_access_may_have_side_effects(
object: &Expression,
property: u32,
is_global_reference: &impl IsGlobalReference,
) -> bool {
if object.may_have_side_effects(is_global_reference) {
return true;
}
match object {
Expression::StringLiteral(s) => property as usize >= s.value.encode_utf16().count(),
Expression::ArrayExpression(arr) => property as usize >= get_array_minimum_length(arr),
_ => true,
}
}

fn get_array_minimum_length(arr: &ArrayExpression) -> usize {
arr.elements
.iter()
.map(|e| match e {
ArrayExpressionElement::SpreadElement(spread) => match &spread.argument {
Expression::ArrayExpression(arr) => get_array_minimum_length(arr),
Expression::StringLiteral(str) => str.value.chars().count(),
_ => 0,
},
_ => 1,
})
.sum()
}

impl MayHaveSideEffects for CallExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
if self.pure {
Expand Down
31 changes: 31 additions & 0 deletions crates/oxc_ecmascript/src/to_integer_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use num_bigint::BigInt;
use num_traits::{ToPrimitive, Zero};

/// Convert a value to u32 if the value can be an integer index and can be represented with u32.
///
/// Note that the max value of u32 is `2^32 - 1`, which is smaller than the max value of
/// safe integer `2^53 - 1`.
///
/// If the value is a non-negative safe integer, it can be an integer index.
/// <https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-object-type>
pub trait ToIntegerIndex {
fn to_integer_index(self) -> Option<u32>;
}

impl ToIntegerIndex for f64 {
fn to_integer_index(self) -> Option<u32> {
if self.fract() != 0.0 || self < 0.0 {
return None;
}
self.to_u32()
}
}

impl ToIntegerIndex for BigInt {
fn to_integer_index(self) -> Option<u32> {
if self < BigInt::zero() {
return None;
}
self.to_u32()
}
}
26 changes: 26 additions & 0 deletions crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,32 @@ fn test_property_access() {
test("[][`length`]", false);

test("a[0]", true);
test("''[-1]", true); // String.prototype[-1] may be overridden
test("''[0.3]", true); // String.prototype[0.3] may be overridden
test("''[0]", true); // String.prototype[0] may be overridden
test("'a'[0]", false);
test("'a'[0n]", false);
test("'a'[1]", true); // String.prototype[1] may be overridden
test("'あ'[0]", false);
test("'あ'[1]", true); // the length of "あ" is 1
test("'😀'[0]", false);
test("'😀'[1]", false);
test("'😀'[2]", true); // the length of "😀" is 2

test("[][-1]", true); // Array.prototype[-1] may be overridden
test("[][0.3]", true); // Array.prototype[0.3] may be overridden
test("[][0]", true); // Array.prototype[0] may be overridden
test("[1][0]", false);
test("[1][0n]", false);
test("[1][1]", true); // Array.prototype[1] may be overridden
test("[,][0]", false);
test("[...[], 1][0]", false);
test("[...[1]][0]", false);
test("[...'a'][0]", false);
test("[...'a'][1]", true);
test("[...'😀'][0]", false);
test("[...'😀'][1]", true);
test("[...a, 1][0]", true); // "...a" may have a sideeffect
}

#[test]
Expand Down
Loading