fix: 排序默认比较改 UTF-16 码元序

This commit is contained in:
2026-10-05 09:55:41 +00:00
parent 88107f45c5
commit a1cbb82fb6
5 changed files with 44 additions and 23 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.5" version = "0.8.6"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+1 -1
View File
@@ -75,7 +75,7 @@ pub fn array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// # 边界与前提 /// # 边界与前提
/// - 先校验 compareFn 可调用(非 undefined 且不可调用则抛 TypeError),再读取 /// - 先校验 compareFn 可调用(非 undefined 且不可调用则抛 TypeError),再读取
/// this/length; /// this/length;
/// - 未提供比较回调时按 ToString 结果的字符串字典序排序;底层 `sort_by` 为稳定 /// - 未提供比较回调时按 ToString 结果的 UTF-16 码元字典序排序;底层 `sort_by` 为稳定
/// 排序,比较结果相等的元素保持原有相对次序。 /// 排序,比较结果相等的元素保持原有相对次序。
pub fn array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("Array.prototype.toSorted called with {} args", args.len()); builtins_debug!("Array.prototype.toSorted called with {} args", args.len());
@@ -16,7 +16,7 @@ use super::common::{
/// 对元素向量执行 `Array.prototype.sort` 的比较语义(原地排序)。 /// 对元素向量执行 `Array.prototype.sort` 的比较语义(原地排序)。
/// ///
/// 默认按 ToString 结果的字符串字典序比较;提供比较回调时按其返回值 /// 默认按 ToString 结果的 UTF-16 码元字典序比较;提供比较回调时按其返回值
///(<0/=0/>0)决定次序,回调抛错则中止并把异常原样返回。 ///(<0/=0/>0)决定次序,回调抛错则中止并把异常原样返回。
pub(crate) fn sort_values_inner<H: VmHost>( pub(crate) fn sort_values_inner<H: VmHost>(
vm: &mut H, vals: &mut [JsValue], comparator: Option<JsValue>, vm: &mut H, vals: &mut [JsValue], comparator: Option<JsValue>,
@@ -48,9 +48,12 @@ pub(crate) fn sort_values_inner<H: VmHost>(
} }
} }
} else { } else {
// 规范 CompareArrayElements 对 ToString 结果做关系比较,字符串按
// UTF-16 码元字典序;码元序列比较与码点序在补充平面字符处分歧,
// 此处以码元序为准。
let sa = oxide_runtime_api::to_string(*a); let sa = oxide_runtime_api::to_string(*a);
let sb = oxide_runtime_api::to_string(*b); let sb = oxide_runtime_api::to_string(*b);
sa.cmp(&sb) sa.encode_utf16().cmp(sb.encode_utf16())
} }
}); });
if let Some(err) = sort_error { if let Some(err) = sort_error {
@@ -72,7 +75,7 @@ pub(crate) fn parse_sort_comparator<H: VmHost>(vm: &mut H, candidate: JsValue) -
/// 原地排序数组元素并返回原数组(`Array.prototype.sort`)。 /// 原地排序数组元素并返回原数组(`Array.prototype.sort`)。
/// ///
/// 未提供比较回调时按 ToString 结果的字符串字典序排序;底层 `sort_by` 为稳定 /// 未提供比较回调时按 ToString 结果的 UTF-16 码元字典序排序;底层 `sort_by` 为稳定
/// 排序,比较结果相等(含 NaN)的元素保持原有相对次序。洞位不参与比较: /// 排序,比较结果相等(含 NaN)的元素保持原有相对次序。洞位不参与比较:
/// present 值稳定排序后自索引 0 紧凑写回,尾部空位转为洞。 /// present 值稳定排序后自索引 0 紧凑写回,尾部空位转为洞。
/// ///
@@ -351,6 +351,24 @@ fn array_sort_propagates_comparator_exception() {
assert_eq!(to_str(&vm, result), "cmp"); 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] #[test]
fn array_callback_type_errors_are_not_sentinels() { fn array_callback_type_errors_are_not_sentinels() {
let (vm, result) = eval("try { [1].map(null) } catch (e) { e.name }").unwrap(); let (vm, result) = eval("try { [1].map(null) } catch (e) { e.name }").unwrap();