From a1cbb82fb64e29caed938083894a98f5582ba10b Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 5 Oct 2026 09:55:41 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=92=E5=BA=8F=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=AF=94=E8=BE=83=E6=94=B9=20UTF-16=20=E7=A0=81=E5=85=83?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 36 +++++++++---------- Cargo.toml | 2 +- crates/oxide_builtins/src/array/immutable.rs | 2 +- .../oxide_builtins/src/array/sort_iterator.rs | 9 +++-- crates/oxide_vm/tests/builtins_array_tests.rs | 18 ++++++++++ 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5b9ecd7..025f45b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.5" +version = "0.8.6" dependencies = [ "chrono", "fast-float", @@ -1120,7 +1120,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.5" +version = "0.8.6" dependencies = [ "num-bigint", "oxide_log", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.5" +version = "0.8.6" dependencies = [ "ansi_term", "bumpalo", @@ -1159,7 +1159,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.5" +version = "0.8.6" dependencies = [ "lru", "oxide_bytecode", @@ -1168,7 +1168,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.5" +version = "0.8.6" dependencies = [ "num-bigint", "oxc_allocator", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.5" +version = "0.8.6" dependencies = [ "bumpalo", "dashmap", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.5" +version = "0.8.6" dependencies = [ "tracing", "tracing-appender", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1292,7 +1292,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.5" +version = "0.8.6" dependencies = [ "num-bigint", "num-traits", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.5" +version = "0.8.6" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.5" +version = "0.8.6" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.5" +version = "0.8.6" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 3acd8e51..b5a7cfc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.5" +version = "0.8.6" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/array/immutable.rs b/crates/oxide_builtins/src/array/immutable.rs index e166a0ed..81b5f977 100644 --- a/crates/oxide_builtins/src/array/immutable.rs +++ b/crates/oxide_builtins/src/array/immutable.rs @@ -75,7 +75,7 @@ pub fn array_with(vm: &mut H, args: &[u8]) -> NativeResult { /// # 边界与前提 /// - 先校验 compareFn 可调用(非 undefined 且不可调用则抛 TypeError),再读取 /// this/length; -/// - 未提供比较回调时按 ToString 结果的字符串字典序排序;底层 `sort_by` 为稳定 +/// - 未提供比较回调时按 ToString 结果的 UTF-16 码元字典序排序;底层 `sort_by` 为稳定 /// 排序,比较结果相等的元素保持原有相对次序。 pub fn array_to_sorted(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.toSorted called with {} args", args.len()); diff --git a/crates/oxide_builtins/src/array/sort_iterator.rs b/crates/oxide_builtins/src/array/sort_iterator.rs index 3b2dc305..fc077f06 100644 --- a/crates/oxide_builtins/src/array/sort_iterator.rs +++ b/crates/oxide_builtins/src/array/sort_iterator.rs @@ -16,7 +16,7 @@ use super::common::{ /// 对元素向量执行 `Array.prototype.sort` 的比较语义(原地排序)。 /// -/// 默认按 ToString 结果的字符串字典序比较;提供比较回调时按其返回值 +/// 默认按 ToString 结果的 UTF-16 码元字典序比较;提供比较回调时按其返回值 ///(<0/=0/>0)决定次序,回调抛错则中止并把异常原样返回。 pub(crate) fn sort_values_inner( vm: &mut H, vals: &mut [JsValue], comparator: Option, @@ -48,9 +48,12 @@ pub(crate) fn sort_values_inner( } } } else { + // 规范 CompareArrayElements 对 ToString 结果做关系比较,字符串按 + // UTF-16 码元字典序;码元序列比较与码点序在补充平面字符处分歧, + // 此处以码元序为准。 let sa = oxide_runtime_api::to_string(*a); let sb = oxide_runtime_api::to_string(*b); - sa.cmp(&sb) + sa.encode_utf16().cmp(sb.encode_utf16()) } }); if let Some(err) = sort_error { @@ -72,7 +75,7 @@ pub(crate) fn parse_sort_comparator(vm: &mut H, candidate: JsValue) - /// 原地排序数组元素并返回原数组(`Array.prototype.sort`)。 /// -/// 未提供比较回调时按 ToString 结果的字符串字典序排序;底层 `sort_by` 为稳定 +/// 未提供比较回调时按 ToString 结果的 UTF-16 码元字典序排序;底层 `sort_by` 为稳定 /// 排序,比较结果相等(含 NaN)的元素保持原有相对次序。洞位不参与比较: /// present 值稳定排序后自索引 0 紧凑写回,尾部空位转为洞。 /// diff --git a/crates/oxide_vm/tests/builtins_array_tests.rs b/crates/oxide_vm/tests/builtins_array_tests.rs index 61fe4464..ac3aa2cb 100644 --- a/crates/oxide_vm/tests/builtins_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_tests.rs @@ -351,6 +351,24 @@ fn array_sort_propagates_comparator_exception() { assert_eq!(to_str(&vm, result), "cmp"); } +#[test] +fn array_sort_default_orders_by_utf16_code_units() { + // 默认排序按 UTF-16 码元字典序(规范 CompareArrayElements):补充平面字符 + // 以高代理(U+D800 及以上)参与比较,小于 U+E000..U+FFFF,与码点序分歧。 + // 前三例为判别例(码点序下结果相反),后两例为边界钉(两口径一致)。 + let cases = [ + ("['\\u{10000}','\\uFFFF'].sort().join('')", "\u{10000}\u{FFFF}"), + ("['\\uFFFF','\\u{10000}'].toSorted().join('')", "\u{10000}\u{FFFF}"), + ("['\\u{10000}','A','\\uFFFF'].sort().join('')", "A\u{10000}\u{FFFF}"), + ("['\\uD7FF','\\u{10000}'].sort().join('')", "\u{D7FF}\u{10000}"), + ("['\\u{12345}','\\u{10000}'].sort().join('')", "\u{10000}\u{12345}"), + ]; + for (source, expected) in cases { + let (vm, result) = eval(source).unwrap(); + assert_eq!(to_str(&vm, result), expected, "source: {source}"); + } +} + #[test] fn array_callback_type_errors_are_not_sentinels() { let (vm, result) = eval("try { [1].map(null) } catch (e) { e.name }").unwrap();