Skip to content

Commit 8513816

Browse files
authoredSep 4, 2024··
fix(es/minifier): Prevent removing side effects from accessing getter (#9530)
Closes #9500 Caused by https://github.com/swc-project/swc/blob/c7fdd6b69b129a11465125d4e11a898326b7e884/crates/swc_ecma_minifier/src/compress/pure/misc.rs#L1547. When the object with getters pass to `self.ignore_return_value`, https://github.com/swc-project/swc/blob/c7fdd6b69b129a11465125d4e11a898326b7e884/crates/swc_ecma_minifier/src/compress/pure/misc.rs#L966 converts the object to `0` because the object is side-effect-free according to https://github.com/swc-project/swc/blob/c7fdd6b69b129a11465125d4e11a898326b7e884/crates/swc_ecma_utils/src/lib.rs#L1496 We should skip this process to fix the issue. As is known only accessing getters and setters may cause side effect, we can safely do the transformation when none of them appears in the object. More precision is possible if comparing the lit prop names. I also collect computed keys of getters and setters in the object, is there any bad case? The reason why only numeric (string) key removes the statement is that string key (`Computed`) is converted to `Ident` in other phases, e.g. `{}['a']` => `{}.a`, which does not matching the pattern.
1 parent 84b0043 commit 8513816

File tree

6 files changed

+95
-48
lines changed

6 files changed

+95
-48
lines changed
 

‎.changeset/stale-poets-cover.md

+6
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): prevent removing side effects from accessing getter with numeric string key

‎crates/swc_ecma_minifier/src/compress/pure/misc.rs

+68-48
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,45 @@ fn can_compress_new_regexp(args: Option<&[ExprOrSpread]>) -> bool {
5656
}
5757
}
5858

59+
fn collect_exprs_from_object(obj: &mut ObjectLit) -> Vec<Box<Expr>> {
60+
let mut exprs = Vec::new();
61+
62+
for prop in obj.props.take() {
63+
if let PropOrSpread::Prop(p) = prop {
64+
match *p {
65+
Prop::Shorthand(p) => {
66+
exprs.push(p.into());
67+
}
68+
Prop::KeyValue(p) => {
69+
if let PropName::Computed(e) = p.key {
70+
exprs.push(e.expr);
71+
}
72+
73+
exprs.push(p.value);
74+
}
75+
Prop::Getter(p) => {
76+
if let PropName::Computed(e) = p.key {
77+
exprs.push(e.expr);
78+
}
79+
}
80+
Prop::Setter(p) => {
81+
if let PropName::Computed(e) = p.key {
82+
exprs.push(e.expr);
83+
}
84+
}
85+
Prop::Method(p) => {
86+
if let PropName::Computed(e) = p.key {
87+
exprs.push(e.expr);
88+
}
89+
}
90+
_ => {}
91+
}
92+
}
93+
}
94+
95+
exprs
96+
}
97+
5998
impl Pure<'_> {
6099
/// `foo(...[1, 2])`` => `foo(1, 2)`
61100
pub(super) fn eval_spread_array(&mut self, args: &mut Vec<ExprOrSpread>) {
@@ -1417,39 +1456,8 @@ impl Pure<'_> {
14171456
}
14181457

14191458
Expr::Object(obj) => {
1420-
if obj.props.iter().all(|p| match p {
1421-
PropOrSpread::Spread(_) => false,
1422-
PropOrSpread::Prop(p) => matches!(
1423-
&**p,
1424-
Prop::Shorthand(_) | Prop::KeyValue(_) | Prop::Method(..)
1425-
),
1426-
}) {
1427-
let mut exprs = Vec::new();
1428-
1429-
for prop in obj.props.take() {
1430-
if let PropOrSpread::Prop(p) = prop {
1431-
match *p {
1432-
Prop::Shorthand(p) => {
1433-
exprs.push(p.into());
1434-
}
1435-
Prop::KeyValue(p) => {
1436-
if let PropName::Computed(e) = p.key {
1437-
exprs.push(e.expr);
1438-
}
1439-
1440-
exprs.push(p.value);
1441-
}
1442-
Prop::Method(p) => {
1443-
if let PropName::Computed(e) = p.key {
1444-
exprs.push(e.expr);
1445-
}
1446-
}
1447-
1448-
_ => unreachable!(),
1449-
}
1450-
}
1451-
}
1452-
1459+
if obj.props.iter().all(|prop| !prop.is_spread()) {
1460+
let exprs = collect_exprs_from_object(obj);
14531461
*e = self
14541462
.make_ignored_expr(obj.span, exprs.into_iter())
14551463
.unwrap_or(Invalid { span: DUMMY_SP }.into());
@@ -1542,22 +1550,34 @@ impl Pure<'_> {
15421550
obj,
15431551
prop: MemberProp::Computed(prop),
15441552
..
1545-
}) => match &**obj {
1546-
Expr::Object(..) | Expr::Array(..) => {
1553+
}) => match obj.as_mut() {
1554+
Expr::Object(object) => {
1555+
// Accessing getters and setters may cause side effect
1556+
// More precision is possible if comparing the lit prop names
1557+
if object.props.iter().all(|p| match p {
1558+
PropOrSpread::Spread(..) => false,
1559+
PropOrSpread::Prop(p) => match &**p {
1560+
Prop::Getter(..) | Prop::Setter(..) => false,
1561+
_ => true,
1562+
},
1563+
}) {
1564+
let mut exprs = collect_exprs_from_object(object);
1565+
exprs.push(prop.expr.take());
1566+
*e = self
1567+
.make_ignored_expr(*span, exprs.into_iter())
1568+
.unwrap_or(Invalid { span: DUMMY_SP }.into());
1569+
return;
1570+
}
1571+
}
1572+
Expr::Array(..) => {
15471573
self.ignore_return_value(obj, opts);
1548-
1549-
match &**obj {
1550-
Expr::Object(..) => {}
1551-
_ => {
1552-
*e = self
1553-
.make_ignored_expr(
1554-
*span,
1555-
vec![obj.take(), prop.expr.take()].into_iter(),
1556-
)
1557-
.unwrap_or(Invalid { span: DUMMY_SP }.into());
1558-
return;
1559-
}
1560-
};
1574+
*e = self
1575+
.make_ignored_expr(
1576+
*span,
1577+
vec![obj.take(), prop.expr.take()].into_iter(),
1578+
)
1579+
.unwrap_or(Invalid { span: DUMMY_SP }.into());
1580+
return;
15611581
}
15621582
_ => {}
15631583
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
let foo = 1;
2+
const obj = {
3+
get 1() {
4+
// same with get "1"()
5+
foo = 2;
6+
return 40;
7+
},
8+
};
9+
obj["1"]; // same with obj[1]
10+
console.log(foo);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let foo = 1;
2+
({
3+
get 1 () {
4+
return(// same with get "1"()
5+
foo = 2, 40);
6+
}
7+
})["1"], console.log(foo);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
({
2+
a: 1,
3+
})[undetermined()];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
undetermined();

0 commit comments

Comments
 (0)
Please sign in to comment.