mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(minifier): fold out-of-bounds string indices to void 0 (#28004)
`"a"[1]`, `"a"["1"]`, `"é"[1]` and `"😀"[2]` read an index past the end of the string, which finds no own property and falls through to `String.prototype`. Under the [no-monkey-patching assumption](https://github.com/oxc-project/oxc/blob/main/crates/oxc_minifier/docs/ASSUMPTIONS.md#no-monkey-patching-built-ins) that prototype has no index properties, so the access is `undefined` and now folds to `void 0`. Later folds can then use the value: `"a"[1] === void 0` becomes `!0`. The operand of `delete` and call targets keep the access. Non-canonical keys such as `"a"["01"]` and negative indices are not index reads, so they stay as they are. esbuild keeps `"abc"[3]`, and the ported esbuild test is updated with a note. Closes #27992. Implemented with AI assistance.
This commit is contained in:
1 parent
eacffcccdf
commit
dd6cb2be00
3 files changed
+17
-7
No files matched your search
@@ -729,7 +729,11 @@ impl<'a> PeepholeOptimizations {
|
||||
let unit = match s.value.as_str() {
|
||||
Some(value) => value.encode_utf16().nth(index),
|
||||
None => s.value.encode_utf16().nth(index),
|
||||
}?;
|
||||
};
|
||||
// An index past the end reads `String.prototype`, which has no index properties.
|
||||
let Some(unit) = unit else {
|
||||
return Some(Expression::new_void_0(span, ctx));
|
||||
};
|
||||
let mut builder = JSStrBuilder::new_in(ctx);
|
||||
builder.push_code_unit(unit);
|
||||
s.span = span;
|
||||
|
||||
@@ -371,7 +371,8 @@ fn js_parser_test() {
|
||||
test("a = 'abc'[-0]", "a = 'a';");
|
||||
test("a = 'abc'[0]", "a = 'a';");
|
||||
test("a = 'abc'[2]", "a = 'c';");
|
||||
test("a = 'abc'[3]", "a = 'abc'[3];");
|
||||
// esbuild keeps this; oxc assumes `String.prototype` has no index properties.
|
||||
test("a = 'abc'[3]", "a = void 0;");
|
||||
test("a = 'abc'[NaN]", "a = 'abc'[NaN];");
|
||||
test("a = 'abc'[-1e100]", "a = 'abc'[-1e100];");
|
||||
test("a = 'abc'[1e100]", "a = 'abc'[1e100];");
|
||||
|
||||
@@ -1193,16 +1193,20 @@ fn test_number_constants() {
|
||||
|
||||
#[test]
|
||||
fn test_fold_integer_index_access() {
|
||||
test_same("v = ''[0]");
|
||||
// An index past the end reads `String.prototype`, which has no index properties.
|
||||
test("v = ''[0]", "v = void 0");
|
||||
test_same("v = 'a'[-1]");
|
||||
test_same("v = 'a'[0.3]");
|
||||
test("v = 'a'[0]", "v = 'a'");
|
||||
test_same("v = 'a'[1]");
|
||||
test("v = 'a'[1]", "v = void 0");
|
||||
test("v = 'a'['1']", "v = void 0");
|
||||
test_same("v = 'a'['01']");
|
||||
test("v = 'あ'[0]", "v = 'あ'");
|
||||
test_same("v = 'あ'[1]");
|
||||
test("v = 'あ'[1]", "v = void 0");
|
||||
test("v = 'é'[1]", "v = void 0");
|
||||
test("v = '😀'[0]", "v = '\\uD83D'");
|
||||
test("v = '😀'[1]", "v = '\\uDE00'");
|
||||
test_same("v = '😀'[2]");
|
||||
test("v = '😀'[2]", "v = void 0");
|
||||
test("v = '😀x'[2]", "v = 'x'");
|
||||
test_same("v = '😀'[0.5]");
|
||||
test_same("v = '😀'['01']");
|
||||
@@ -1214,7 +1218,8 @@ fn test_fold_integer_index_access() {
|
||||
test("v = '\\uD800😀'[2]", "v = '\\uDE00'");
|
||||
test("v = '😀\\uD800'[2]", "v = '\\uD800'");
|
||||
test("v = '\\uDC00'[0]", "v = '\\uDC00'");
|
||||
test_same("v = 'a\\uD800b'[3]");
|
||||
test("v = 'a\\uD800b'[3]", "v = void 0");
|
||||
test_same("v = 'a'[1]()");
|
||||
test_same("v = (foo(), 'a')[1]"); // can be fold into `v = (foo(), 'a')`
|
||||
|
||||
test_same("v = [][0]");
|
||||
|
||||
Reference in new issue
Block a user