emit/kernel/vm/builtins: 常量池键编码 + 键物化真实串值面 + 16 回归钉——emit 侧字面量/键/模板统一改存无标志键编码:lone_surrogates 字面量与模板 cooked 按 oxc marker 解码还原真单元序列(FFFD+hex4≠fffd→surrogate 单元、FFFD+fffd→FFFD、FFFD+非 hex 防御臂),无标志串 encode_key(encode_utf16()) 入池(BMP 无 FFFD 键恒等 = 现值,池条目零漂移),string_pool 集中收口 9 处入池点;interner string_ptr 由单元物化(含孤立→FlatU16、良形→Flat);键物化面改真实 JS 串值(key_si_to_js_value 经 decode_key,Object.keys/ownKeys/entries/Reflect.ownKeys/json reviver 键同源;整数键反解数字串);批内修复三处子集 A/B 差分撞出回归:replace $n 展开按 spec GetSubstitution 重写(digitCount 2/1、越界回退 1 位、越界含 $0 原文输出,node 13 用例对照钉住)、String 构造器 Symbol 路径按 new.target 分流(String(sym)→描述串、new String(sym)→TypeError,恢复 to_string_for_string_constructor)、padStart/padEnd 未给 fill 时按单空格补(原 lossy 串取 'un' 杂质,pre-existing 一并归零);迭代器 dispose 钉改回真值断言(String(Symbol.dispose) 现合法返回描述串);property-escapes 613/0/0(B167 基线 582/31/0,31 归零)、String 子集 963/250/10 vs BASE 949/264/10(14 修 0 回归)、for-of +1(string-astral-truncated)、五 crate 113 套件 2680/0、clippy/fmt 零新增(JSON 面与全量 test262 收尾在 131.2 后续 commit 闭合)

This commit is contained in:
2026-09-14 03:04:03 +00:00
parent 3acac721b0
commit 0c750fa74c
20 changed files with 398 additions and 129 deletions
+2 -3
View File
@@ -91,9 +91,8 @@ fn walk_reviver<H: VmHost>(
}
}
// 对当前值调用 reviver,用返回值覆盖属性槽。
let key_str = key_si_to_string(vm, key_si);
let key_val = vm.new_string(&key_str);
// 对当前值调用 reviver,用返回值覆盖属性槽(键物化为 JS 可见串值,含孤立 surrogate 单元)。
let key_val = crate::object::key_si_to_js_value(vm, key_si);
let holder_val = JsValue::from_js_object(holder_ptr);
match vm.call_function_sync(reviver, holder_val, &[key_val, val]) {
Ok(new_val) => {
+39 -30
View File
@@ -88,6 +88,9 @@ fn int_or_string_index<H: VmHost>(vm: &H, si: u32) -> Option<u32> {
}
/// 键 si 物化为字符串文本:整数键反解数字串,其余查 interner。
///
/// 注意返回的是**键空间文本**(孤立 surrogate 为 FFFD+hex4 转义形态),仅可用于
/// 键比较/数字判定等键空间用途;产 JS 可见串值用 `key_si_to_js_value`。
pub fn key_si_to_string<H: VmHost>(vm: &H, si: u32) -> String {
if is_int_key(si) {
int_key_value(si).to_string()
@@ -96,6 +99,22 @@ pub fn key_si_to_string<H: VmHost>(vm: &H, si: u32) -> String {
}
}
/// 键 si 物化为 JS 可见字符串值(Object.keys / ownKeys / entries 族):
/// 整数键反解数字串,字符串键经 `decode_key` 还原单元序列(孤立 surrogate
/// 物化为 FlatU16,良形键与旧行为逐位一致)。
pub fn key_si_to_js_value<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
if is_int_key(si) {
return vm.new_string(&int_key_value(si).to_string());
}
let units = vm
.kernel_core()
.perm_interner()
.lookup(si)
.map(oxide_kernel::string_forge::decode_key)
.unwrap_or_default();
vm.new_string_units_owned(units)
}
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。
fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
@@ -377,21 +396,16 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Err(err) => return NativeResult::Err(err),
};
let key_names: Vec<String>;
{
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let owned_keys: Vec<(u32, u32)> = keys
.into_iter()
.filter(|(_si, offset)| {
obj.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
key_names = owned_keys.iter().map(|(si, _offset)| key_si_to_string(vm, *si)).collect();
}
let n = key_names.len();
let obj = unsafe { &*obj_ptr };
let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj)
.into_iter()
.filter(|(_si, offset)| {
obj.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
let n = owned_keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
@@ -399,10 +413,10 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
n,
vm.epoch().bump(),
));
for (i, k) in key_names.iter().enumerate() {
let str_val = vm.new_string(k);
for (i, (si, _offset)) in owned_keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
unsafe {
(*arr).set_prop_at(i, str_val);
(*arr).set_prop_at(i, key_val);
}
}
unsafe {
@@ -1009,13 +1023,9 @@ pub fn object_is_extensible<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
let key_names: Vec<String> = {
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
keys.iter().map(|(si, _)| key_si_to_string(vm, *si)).collect()
};
let n = key_names.len();
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let n = keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
@@ -1023,10 +1033,10 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
n,
vm.epoch().bump(),
));
for (i, k) in key_names.iter().enumerate() {
let str_val = vm.new_string(k);
for (i, (si, _)) in keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
unsafe {
(*arr).set_prop_at(i, str_val);
(*arr).set_prop_at(i, key_val);
}
}
unsafe {
@@ -1333,8 +1343,7 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
vm.epoch().bump(),
));
for (i, (si, offset)) in owned_keys.iter().enumerate() {
let key_str = key_si_to_string(vm, *si);
let key_val = vm.new_string(&key_str);
let key_val = key_si_to_js_value(vm, *si);
let val = obj.get_prop_at(*offset);
let pair = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
+20 -22
View File
@@ -3,7 +3,7 @@ use oxide_types::mem::P;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
use crate::object::{delete_own_property, key_si_to_string, walk_own_keys};
use crate::object::{delete_own_property, key_si_to_js_value, walk_own_keys};
use oxide_runtime_api::{NativeResult, VmHost};
@@ -191,11 +191,25 @@ pub fn reflect_own_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return type_error(vm, "Reflect.ownKeys target is not an object");
};
let target = unsafe { &*target_ptr };
let key_names: Vec<String> = walk_own_keys(vm, target)
.into_iter()
.map(|(si, _)| key_si_to_string(vm, si))
.collect();
NativeResult::Ok(make_string_array(vm, &key_names))
let keys = walk_own_keys(vm, target);
let n = keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, (si, _)) in keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
unsafe {
(*arr).set_prop_at(i, key_val);
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `Reflect.preventExtensions(target)`:阻止扩展,返回 true。
@@ -273,19 +287,3 @@ fn own_field<H: VmHost>(vm: &mut H, desc: JsValue, prop_si: u32) -> Option<JsVal
vm.resolve_property(obj, prop_si)?;
vm.ordinary_get(obj, prop_si, desc).ok()
}
fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(proto),
parts.len(),
vm.epoch().bump(),
));
for (idx, part) in parts.iter().enumerate() {
let value = vm.new_string(part);
unsafe { &mut *arr }.set_prop_at(idx, value);
}
unsafe { &mut *arr }.set_prop_count(parts.len());
JsValue::from_js_object(arr)
}
+57 -34
View File
@@ -409,22 +409,33 @@ fn expand_dollar_units(text: &[u16], m: &regress::Match, replacement: &[u16]) ->
} else if rest.starts_with(&[0x24, 0x27]) {
out.extend_from_slice(&text[range.end..]);
i += 2;
} else {
// $n(1-2 位数字)→ 捕获组;未匹配/越界 → 空串。
let mut j = 1;
while i + j < replacement.len() && (0x30..=0x39).contains(&replacement[i + j]) {
j += 1;
}
if j == 0 {
out.push(0x24);
i += 1;
} else if i + 1 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 1]) {
// $digits:digitCount 取 2(后续尚有数字)否则 1;两位数值越界回退
// 一位(次位留给后续按字面处理);仍越界(含 $0)→ 整段 ref 字面。
let digit_count = if i + 2 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 2]) {
2
} else {
let n: u32 = (0..j).fold(0, |acc, k| acc * 10 + (replacement[i + k] - 0x30) as u32);
if let Some(g) = m.group(n as usize) {
1
};
let d1 = (replacement[i + 1] - 0x30) as u32;
let mut index = if digit_count == 2 { d1 * 10 + (replacement[i + 2] - 0x30) as u32 } else { d1 };
let mut ref_len = 1 + digit_count;
let capture_len = (m.groups().count() - 1) as u32;
if index > capture_len && digit_count == 2 {
index = d1;
ref_len = 2;
}
if (1..=capture_len).contains(&index) {
if let Some(g) = m.group(index as usize) {
out.extend_from_slice(&text[g.start..g.end]);
}
i += 1 + j;
} else {
out.extend_from_slice(&replacement[i..i + ref_len]);
}
i += ref_len;
} else {
out.push(0x24);
i += 1;
}
}
out
@@ -692,22 +703,6 @@ pub fn string_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// JS `String()` 构造逻辑:把参数转成字符串(单元保真);new 语义返回
/// `[[StringData]]` 包装对象。
pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let str_val = if args.len() > 1 {
// 对象参数须经 ToPrimitive/ToString 完整转换(字符串结果按载荷形态保留)。
match oxide_runtime_api::to_string_value_full(vm.reg(args[1]), vm) {
Ok(v) => v,
Err(_) => {
// ToString on an object may throw via toString/valueOf; propagate the original exception.
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
}
} else {
vm.new_string("")
};
let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let is_ctor = if this_val.is_object() {
@@ -722,6 +717,30 @@ pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
false
};
let str_val = if args.len() > 1 {
let v = vm.reg(args[1]);
if is_ctor && v.is_symbol() {
// new String(Symbol):构造器路径走 ToString(规范步骤 3b),
// 对 Symbol 抛 TypeError——与函数调用路径(3a 描述串)相异。
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
}
// 对象参数须经 ToPrimitive/ToString 完整转换;函数调用路径的 Symbol
// 走 SymbolDescriptiveString(规范步骤 3a,不经 ToString)。
let v = oxide_runtime_api::to_string_for_string_constructor(v, vm);
match v {
Ok(s) => vm.new_string_owned(s),
Err(_) => {
// ToString on an object may throw via toString/valueOf; propagate the original exception.
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
}
} else {
vm.new_string("")
};
if !is_ctor {
return NativeResult::Ok(str_val);
}
@@ -1155,10 +1174,12 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
None
};
let pad: Vec<u16> = if args.len() > 2 {
try_string!(as_units(vm, vm.reg(args[2]))).into_owned()
} else {
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
let pad: Vec<u16> = if pad_val.is_undefined() {
vec![0x20]
} else {
try_string!(as_units(vm, pad_val)).into_owned()
};
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
let s_len = code_point_count(&s);
@@ -1194,10 +1215,12 @@ pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else {
None
};
let pad: Vec<u16> = if args.len() > 2 {
try_string!(as_units(vm, vm.reg(args[2]))).into_owned()
} else {
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
let pad: Vec<u16> = if pad_val.is_undefined() {
vec![0x20]
} else {
try_string!(as_units(vm, pad_val)).into_owned()
};
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
let s_len = code_point_count(&s);