feat: String 盒迁移 boxed_value 载荷并物化固有属性面

This commit is contained in:
2026-09-22 16:40:01 +00:00
parent 1be90d90aa
commit 2252ed7bbc
13 changed files with 432 additions and 149 deletions
+5 -5
View File
@@ -139,12 +139,12 @@ pub(crate) fn arraylike_get<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize)
/// 字符串源的 array-like 单元序列;非字符串源返回 `None`。
///
/// String exotic 对象的 `length` 与索引属性在引擎内未物化,Array.from /
/// %TypedArray%.from 的 array-like 回退按 UTF-16 单元直接读:原始串取自身单元,
/// 装箱串取其 `[[StringData]]`(槽 0)。单元长度即 `length`,第 i 个索引为第 i 个码元。
/// Array.from / %TypedArray%.from 的 array-like 回退按 UTF-16 单元直接读:
/// 原始串取自身单元,装箱串取其 `[[StringData]]`(boxed_value 载荷)。
/// 单元长度即 `length`,第 i 个索引为第 i 个码元。
///
/// # 边界
/// 装箱串槽 0 非字符串(异常形态)按非字符串源处理;指针为空返回 `None`。
/// 装箱串载荷非字符串(异常形态)按非字符串源处理;指针为空返回 `None`。
pub(crate) fn string_arraylike_units<H: VmHost>(vm: &H, value: JsValue) -> Option<Vec<u16>> {
let raw = if value.is_string() {
value
@@ -158,7 +158,7 @@ pub(crate) fn string_arraylike_units<H: VmHost>(vm: &H, value: JsValue) -> Optio
if !obj.is_string_obj() {
return None;
}
let data = obj.get_prop_at(0);
let data = obj.boxed_value();
if !data.is_string() {
return None;
}
+3 -3
View File
@@ -1547,10 +1547,10 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<(JsValue
return Err(exc);
}
};
// 码元快速路径的 inner:原始串取 value 本身;装箱串取其内容(槽 0 恒为
// 原始串)——wrapper 码元步进按原始串产出,装箱串不直接作 inner。
// 码元快速路径的 inner:原始串取 value 本身;装箱串取其 boxed_value
// 载荷——wrapper 码元步进按原始串产出,装箱串不直接作 inner。
let inner = if value_is_string_box {
let raw = unsafe { (&*value.as_js_object_ptr()).get_prop_at(0) };
let raw = unsafe { (&*value.as_js_object_ptr()).boxed_value() };
if raw.is_string() {
raw
} else {
-6
View File
@@ -235,13 +235,7 @@ fn whitelist_element_name<H: VmHost>(vm: &mut H, elem: JsValue) -> Result<Option
return Ok(None);
}
let obj = unsafe { &*ptr };
// 装箱 String 的盒值存 prop 槽 0(boxed_value 槽不存)。
let boxed = obj.boxed_value();
let boxed = if boxed.is_undefined() && obj.is_string_obj() {
obj.get_prop_at(0)
} else {
boxed
};
if !boxed.is_string() && !boxed.is_int() && !boxed.is_double() {
return Ok(None);
}
+14 -103
View File
@@ -466,7 +466,8 @@ pub fn object_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
obj_ref.set_prop_at(0, val);
// 构造期物化:boxed_value 载荷与字符索引/length 固有属性同批落地。
oxide_runtime_api::materialize_string_box(vm, obj_ref, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_bool() {
@@ -638,10 +639,12 @@ pub fn object_assign<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let mut all_assignments: Vec<(u32, JsValue)> = Vec::new();
for &arg_reg in args.iter().skip(2) {
let source_val = vm.reg(arg_reg);
if !source_val.is_object() {
continue;
}
// null/undefined 源静默跳过(语料口径,非规范 ToObject 抛错);其余
// 基元经 ToObject 装箱后走同一 walk(string 源自此获字符属性拷贝)。
let source_val = match oxide_runtime_api::to_object(vm.reg(arg_reg), vm) {
Ok(v) => v,
Err(_) => continue,
};
let source_ptr = source_val.as_js_object_ptr();
if source_ptr.is_null() {
continue;
@@ -866,8 +869,8 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
/// # 边界与前提
/// - 仅处理可枚举自身属性;描述符值经 ordinary_get 读取(触发访问器 getter)
/// - 描述符值的非对象校验由 define_from_descriptor 承担(ToPropertyDescriptor)
/// - String 盒的自身键是字符下标 0..len-1(String exotic,各键值为对应
/// 字符),盒存储未落属性枚举面,此处按规范物化
/// - String 盒装箱后自身键为物化的字符下标(值为对应字符),走同一
/// walk 路径;字符值非对象,ToPropertyDescriptor 按规范抛 TypeError
///
/// # 副作用
/// - 修改 target 的 shape 链、属性表与 generation
@@ -878,18 +881,7 @@ fn define_all_from_properties<H: VmHost>(
// 前缀恢复后原类型抛出。
let props_obj = oxide_runtime_api::to_object(props_val, vm)?;
let props_ptr = props_obj.as_js_object_ptr();
let descriptors: Vec<(u32, JsValue)> = if unsafe { &*props_ptr }.is_string_obj() {
// String 盒:键为字符下标、值为字符本身;空串零键。
let props = unsafe { &*props_ptr };
let raw = props.boxed_value();
let raw = if raw.is_undefined() { props.get_prop_at(0) } else { raw };
let units = if raw.is_string() { vm.string_units(raw).to_vec() } else { Vec::new() };
units
.iter()
.enumerate()
.map(|(i, unit)| (make_int_key(i as u32), vm.new_string_units(std::slice::from_ref(unit))))
.collect()
} else {
let descriptors: Vec<(u32, JsValue)> = {
let props = unsafe { &*props_ptr };
walk_own_keys(vm, props)
.into_iter()
@@ -1031,15 +1023,14 @@ pub fn object_get_own_property_descriptor<H: VmHost>(vm: &mut H, args: &[u8]) ->
///
/// # 步骤
/// 1. ToObject 装箱接收者(null/undefined 抛 TypeError)
/// 2. String 盒:按规范枚举虚拟键(整数下标 0..len-1 + "length"),构造描述符
/// 3. 普通对象:按规范三段序枚举自身键(walk_own_keys + walk_own_symbol_keys)
/// 4. 逐键取描述符(缺失键跳过),以数据属性定义到结果对象
/// 2. 按规范三段序枚举自身键(walk_own_keys + walk_own_symbol_keys);
/// String 盒装箱后自身键为物化的字符下标 + length,走同一路径
/// 3. 逐键取描述符(缺失键跳过),以数据属性定义到结果对象
///
/// # 边界与前提
/// - 结果对象 proto = %Object.prototype%,各键属性恒 writable/enumerable/
/// configurable 全真(fresh 普通对象的 CreateDataPropertyOrThrow 形态)
/// - 描述符取值与 gOPD 同核;模块命名空间未初始化导出抛 ReferenceError 原值
/// - String 盒虚拟属性:字符下标 enumerable=true,length enumerable=false
///
/// # 副作用
/// - 分配结果对象与每个描述符对象
@@ -1049,11 +1040,6 @@ pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -
Err(err) => return NativeResult::Err(err),
};
// String 盒:自身键为虚拟属性(从被包字符串值派生),不走 shape 链枚举。
if unsafe { &*obj_ptr }.is_string_obj() {
return string_exotic_get_own_property_descriptors(vm, obj_ptr);
}
let obj = unsafe { &*obj_ptr };
// 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。
@@ -1080,81 +1066,6 @@ pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -
NativeResult::Ok(result_val)
}
/// String 盒的 `getOwnPropertyDescriptors`:枚举虚拟键(字符下标 + length)。
///
/// # 步骤
/// 1. 取被包字符串值(boxed_value 或 hash_props[0] fallback)
/// 2. 按 UTF-16 单元枚举:整数下标 0..len-1(enumerable=true)+ "length"(enumerable=false)
/// 3. 逐键构造描述符对象并写入结果
///
/// # 边界与前提
/// - 空串:仅返回 length 描述符
/// - 非字符串被包值:退化为零键
fn string_exotic_get_own_property_descriptors<H: VmHost>(vm: &mut H, obj_ptr: *mut JsObject) -> NativeResult {
let obj = unsafe { &*obj_ptr };
// 取被包字符串值:优先 boxed_value,回退 hash_props[0]。
let raw = obj.boxed_value();
let raw = if raw.is_undefined() { obj.get_prop_at(0) } else { raw };
let units = if raw.is_string() { vm.string_units(raw).to_vec() } else { Vec::new() };
let len = units.len();
// 结果对象:proto = %Object.prototype%。
let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto)));
let result_val = JsValue::from_js_object(result);
// 预计算 interned 键。
let intern = vm.kernel_core().perm_interner();
let value_si = intern.intern("value").0;
let writable_si = intern.intern("writable").0;
let enumerable_si = intern.intern("enumerable").0;
let configurable_si = intern.intern("configurable").0;
let length_si = intern.intern("length").0;
// 整数下标键 0..len-1:enumerable=true, writable=false, configurable=false。
for (i, unit) in units.iter().enumerate() {
let si = make_int_key(i as u32);
let char_val = vm.new_string_units(std::slice::from_ref(unit));
let desc = alloc_desc_object(vm);
let desc_obj = unsafe { &mut *desc };
let desc_obj_ptr = JsValue::from_js_object(desc);
// 构造描述符对象:value + writable/enumerable/configurable。
vm.define_data_property(desc_obj, value_si, char_val, PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, writable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, enumerable_si, JsValue::bool(true), PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, configurable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA)
.ok();
if let Err(err) =
vm.define_data_property(unsafe { &mut *result }, si, desc_obj_ptr, PropAttributes::DEFAULT_DATA)
{
return NativeResult::Err(crate::error::create_type_error(vm, &err));
}
}
// "length" 键:enumerable=false, writable=false, configurable=false。
let desc = alloc_desc_object(vm);
let desc_obj = unsafe { &mut *desc };
let desc_obj_ptr = JsValue::from_js_object(desc);
vm.define_data_property(desc_obj, value_si, JsValue::int(len as i32), PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, writable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, enumerable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA)
.ok();
vm.define_data_property(desc_obj, configurable_si, JsValue::bool(false), PropAttributes::DEFAULT_DATA)
.ok();
if let Err(err) =
vm.define_data_property(unsafe { &mut *result }, length_si, desc_obj_ptr, PropAttributes::DEFAULT_DATA)
{
return NativeResult::Err(crate::error::create_type_error(vm, &err));
}
NativeResult::Ok(result_val)
}
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
/// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。
fn own_field<H: VmHost>(vm: &mut H, desc: JsValue, prop_si: u32) -> Option<JsValue> {
+4 -3
View File
@@ -114,7 +114,7 @@ pub fn string_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_string_obj() {
return NativeResult::Ok(obj.get_prop_at(0));
return NativeResult::Ok(obj.boxed_value());
}
}
}
@@ -172,7 +172,8 @@ pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
obj.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
obj.push_prop(str_val);
// 构造期物化:boxed_value 载荷与字符索引/length 固有属性同批落地。
oxide_runtime_api::materialize_string_box(vm, obj, str_val);
NativeResult::Ok(this_val)
}
@@ -187,7 +188,7 @@ pub fn string_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_string_obj() {
return NativeResult::Ok(obj.get_prop_at(0));
return NativeResult::Ok(obj.boxed_value());
}
}
}
@@ -87,6 +87,26 @@ pub(crate) fn tag_number_proto(proto: &P<JsObject>) {
obj.set_boxed_value(JsValue::int(0));
}
/// 把 String.prototype 标成 String 对象本体:规范 22.7.3 规定其是
/// [[StringData]] = 空串的 String object。type_tag 是品牌表判据,boxed 值
/// 供 thisStringValue/valueOf 的通用分支读取;length 自身属性按构造期
/// 物化约定落地(writable:false / enumerable:false / configurable:false)。
pub(crate) fn tag_string_proto(proto: &P<JsObject>, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ptr = proto.as_ptr() as *mut JsObject;
// SAFETY: proto 是 make_named_pair 刚建的本进程对象,P 引用与裸指针同址。
let obj = unsafe { &mut *ptr };
obj.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
obj.set_boxed_value(JsValue::perm_string(crate::string_forge::empty_string_ptr()));
let length_si = string_forge.intern("length").0;
let shape_id = shape_forge.make_shape(obj.shape_id(), length_si);
obj.set_shape_id(shape_id);
// push_prop 返回绝对存储下标(尾部槽),meta 按同下标落位,
// 避免写死槽号覆盖既有属性(如 constructor)的元数据。
let length_pos = obj.push_prop(JsValue::int(0));
obj.set_data_meta(length_pos, PropAttributes::new(false, false, false));
obj.bump_generation();
}
pub(crate) fn make_error_subtypes(error_proto: &P<JsObject>) -> ErrorSubtypeProtos {
let error_proto_val = JsValue::from_js_object(error_proto.as_ptr() as *mut JsObject);
ErrorSubtypeProtos {
@@ -448,6 +468,7 @@ impl BuiltinWorld {
let (array_proto, array_constructor) = make_named_pair(string_forge, shape_forge, labels, "Array");
let (function_proto, function_constructor) = make_named_pair(string_forge, shape_forge, labels, "Function");
let (string_proto, string_constructor) = make_named_pair(string_forge, shape_forge, labels, "String");
tag_string_proto(&string_proto, string_forge, shape_forge);
let (number_proto, number_constructor) = make_named_pair(string_forge, shape_forge, labels, "Number");
tag_number_proto(&number_proto);
let (boolean_proto, boolean_constructor) = make_named_pair(string_forge, shape_forge, labels, "Boolean");
+6 -2
View File
@@ -7,7 +7,7 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use super::construct::{
builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_number_proto,
builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_number_proto, tag_string_proto,
wire_builtin_world_links, ErrorSubtypeProtos, TypedArrayFamily,
};
use super::BuiltinWorld;
@@ -178,7 +178,11 @@ impl BuiltinWorld {
(current.function_proto.clone(), current.function_constructor.clone())
};
let (string_proto, string_constructor) = if dirty.string {
make_named_pair(string_forge, shape_forge, labels, "String")
// 脏重建与全量构造同形:原型本体须带 String 对象 tag 与空串包值
// (+length 物化),漏此分支 full_reset 后 tag 翻回 "Object"。
let (proto, ctor) = make_named_pair(string_forge, shape_forge, labels, "String");
tag_string_proto(&proto, string_forge, shape_forge);
(proto, ctor)
} else {
(current.string_proto.clone(), current.string_constructor.clone())
};
+23
View File
@@ -399,6 +399,29 @@ pub fn single_char_ptr(ch: u8) -> *const JsString {
}
}
/// 空串永久 `JsString` 指针,首用惰性物化后恒复用同一地址。
/// 供 %StringPrototype% 的 `[[StringData]]` 载荷等零分配场景复用。
static EMPTY_STRING: OnceLock<StringPtr> = OnceLock::new();
/// 取空串的永久 `JsString` 指针,惰性物化一次后恒返回同一地址。
pub fn empty_string_ptr() -> *const JsString {
match EMPTY_STRING.get() {
Some(ptr) => ptr.0,
None => {
let ptr = Box::into_raw(Box::new(JsString::new(String::new())));
match EMPTY_STRING.set(StringPtr(ptr)) {
Ok(()) => ptr,
Err(_) => {
let existing = EMPTY_STRING.get().expect("set 失败时槽必已初始化").0;
// SAFETY: ptr 来自本线程的 Box::into_raw,无任何外部引用。
unsafe { drop(Box::from_raw(ptr)) };
existing
}
}
}
}
}
/// 小整数(0..=99)永久 `JsString` 指针表:`s += j` 等数字叶子拼接的高频命中路径,
/// 免每次 `to_string` + 登记 2 次分配。物化面严格有界:100 条目 ≈ 4KB,
/// 随进程生命周期有意保留(同 `SINGLE_CHAR_TABLE` 论证)。
+49 -5
View File
@@ -8,7 +8,8 @@ use std::cmp::Ordering;
use num_traits::{ToPrimitive, Zero};
use oxide_types::mem::P;
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::make_int_key;
use oxide_types::shape::EMPTY_SHAPE_ID;
use oxide_types::value::{JsType, JsValue};
@@ -744,11 +745,55 @@ pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool {
}
}
/// 把 String 盒的规范固有属性面物化为普通 shape 属性:被包串写入
/// `boxed_value` 载荷,字符索引 0..len-1(writable:false / enumerable:true /
/// configurable:false)与 `length`(writable:false / enumerable:false /
/// configurable:false)逐键 shape 转换后落属性区。
///
/// 物化后 String exotic 的 [[Get]]/[[Has]]/[[Set]]/[[DefineOwnProperty]]/
/// [[Delete]]/枚举语义由既有属性机制承担:写失败静默(sloppy)/抛错
/// (strict)、同值重定义放行与异值重定义抛 TypeError、delete 返 false,
/// 与 exotic 定义逐条同值,VM 属性派发面零特判。
///
/// # 步骤
/// 1. 整串单元序列落地(借用结束),`boxed_value` 记被包串
/// 2. 逐单元:整数键 shape 转换 → 单单元串落槽 → 索引属性 meta
/// 3. `length` 键 shape 转换 → 单元数落槽 → length 属性 meta
///
/// # 边界与前提
/// - 调用时对象须为刚分配的空盒(EMPTY_SHAPE_ID、无属性),构造点唯一入口
/// - 长串构造成本与单元数线性(每键一次 shape 转换 + 单单元串),
/// ASCII 单元命中 perm 静态表免 session 分配
///
/// # 副作用
/// - 修改对象 shape 链、属性区与 generation;单单元串/整串进 session 字符串区
pub fn materialize_string_box<H: VmHost>(host: &mut H, obj: &mut JsObject, str_val: JsValue) {
let units = host.string_units(str_val).into_owned();
let len = units.len();
obj.set_boxed_value(str_val);
for (i, &unit) in units.iter().enumerate() {
let shape_id = host
.kernel_core()
.shape_forge()
.make_shape(obj.shape_id(), make_int_key(i as u32));
obj.set_shape_id(shape_id);
let ch = host.single_unit(unit).unwrap_or_else(|| host.new_string_units(&[unit]));
obj.push_prop(ch);
obj.set_data_meta(i as u32, PropAttributes::new(false, true, false));
}
let length_si = host.kernel_core().perm_interner().intern("length").0;
let shape_id = host.kernel_core().shape_forge().make_shape(obj.shape_id(), length_si);
obj.set_shape_id(shape_id);
obj.push_prop(JsValue::int(len as i32));
obj.set_data_meta(len as u32, PropAttributes::new(false, false, false));
obj.bump_generation();
}
/// ToObject(ECMA-262 §7.1.13):null/undefined 抛 TypeError,其余原始值包装为对应包装对象。
///
/// 包装对象按类型选择原型(String/Number/Boolean 原型或默认 Object 原型);
/// 被包基元写入对象专属载荷字段,不占命名属性区。字符串盒暂仍预存属性区
/// 槽 0——其消费面(解盒读原始串)与规范固有属性面同批切换,两端不可分拆。
/// 被包基元写入对象专属载荷字段,不占命名属性区。字符串盒经
/// [`materialize_string_box`] 同步物化字符索引与 length 固有属性面。
pub fn to_object<H: VmHost>(val: JsValue, host: &mut H) -> Result<JsValue, String> {
if val.is_object() {
return Ok(val);
@@ -776,8 +821,7 @@ pub fn to_object<H: VmHost>(val: JsValue, host: &mut H) -> Result<JsValue, Strin
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = type_tag;
if val.is_string() {
obj_ref.ensure_hash_props().push(val);
obj_ref.set_prop_count(1);
materialize_string_box(host, obj_ref, val);
} else {
obj_ref.set_boxed_value(val);
}
+5 -5
View File
@@ -20,11 +20,11 @@ mod runtime_api_log;
pub use coercion::{
abstract_eq, bigint_data, bigint_to_f64, bigint_to_string, format_error_message, js_number_to_string,
push_to_string, push_units_to, relational_compare, same_value, same_value_zero, strict_equality, string_concat,
string_value_eq, to_bigint_full, to_boolean, to_int32, to_integer_or_infinity, to_length, to_number,
to_number_full, to_object, to_primitive, to_string, to_string_for_string_constructor, to_string_full,
to_string_value_full, to_uint32, to_units_full, well_known_symbol_id, well_known_symbol_name, write_number_into,
ToPrimitiveHint,
materialize_string_box, push_to_string, push_units_to, relational_compare, same_value, same_value_zero,
strict_equality, string_concat, string_value_eq, to_bigint_full, to_boolean, to_int32, to_integer_or_infinity,
to_length, to_number, to_number_full, to_object, to_primitive, to_string, to_string_for_string_constructor,
to_string_full, to_string_value_full, to_uint32, to_units_full, well_known_symbol_id, well_known_symbol_name,
write_number_into, ToPrimitiveHint,
};
pub use host::VmHost;
pub use native_result::NativeResult;
+1 -15
View File
@@ -920,23 +920,9 @@ impl Vm {
// 收集-提交:先取齐 (键, 值),取值阶段触发 getter(可能抛异常),再统一写
// 目标对象,避免提交写与取值互相交错。只复制可枚举自有属性(CopyDataProperties)。
// 字符串包装对象的索引字符是构造期物化的可枚举自有属性,走同一 walk 路径。
let mut assignments: Vec<(u32, JsValue)> = Vec::new();
// 字符串包装对象:索引字符是可枚举自有属性(ToObject("str") 的 0..len-1)。
if src_obj.type_tag == JsObject::OBJ_TYPE_STRING_OBJ {
let raw = src_obj.get_prop_at(0);
// SAFETY: 包装对象槽 0 恒为字符串值;单元序列展开(lone surrogate 保真)。
let code_units: Vec<u16> = unsafe { (*raw.as_string_ptr()).units().into_owned() };
for (i, unit) in code_units.iter().enumerate() {
let si = make_int_key(i as u32);
if excluded.contains(&si) {
continue;
}
let ch_val = self.unit_char_value(*unit);
assignments.push((si, ch_val));
}
}
// 自身属性:walk_own_keys 已合并数组元素区(hole 跳过)并返回绝对存储索引,
// 仅可枚举,跳过 pattern 已绑定的键。
let keys = oxide_builtins::object::walk_own_keys(self, src_obj);
@@ -0,0 +1,299 @@
//! 字符串盒(String exotic)构造期物化钉测:`boxed_value` 专属载荷与字符索引/
//! length 固有属性面。覆盖 test262 盒面红转绿的语义面——索引/length 读、in/
//! 枚举、描述符、seal/freeze、defineProperty 同值改写放行/异值与属性面变更
//! 拒绝、写保护(strict/sloppy)、delete 返 false、Object.assign/解构消费面、
//! %StringPrototype% 自身盒面、迭代器/Array.from 载荷读、@@toStringTag 覆盖。
//!
//! 语义对齐规范 String exotic:索引属性 writable:false / enumerable:true /
//! configurable:false,length writable:false / enumerable:false /
//! configurable:false;写失败 sloppy 返回被赋值(静默)、strict 抛 TypeError;
//! 同值重定义放行、异值或属性面变更抛 TypeError;delete 返 false;越界新键
//! (如 length 之后的下标)按普通数据属性定义成功。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&Arc::new(module))
}
#[test]
fn boxed_value_roundtrip() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); return s.valueOf() === 'ab' && s.toString() === 'ab' && Object.prototype.toString.call(s) === '[object String]'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn index_and_length_read() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); return s[0] === 'a' && s[1] === 'b' && s[2] === undefined && s.length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn in_operator() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); return '0' in s && '1' in s && !('2' in s) && 'length' in s; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn forin_enumerates_indexes_only() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const keys = []; for (const k in s) keys.push(k); return keys.length === 2 && keys[0] === '0' && keys[1] === '1'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn keys_and_own_keys() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const k = Object.keys(s).join(','); const o = Object.getOwnPropertyNames(s).sort().join(','); return k === '0,1' && o === '0,1,length'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn gopd_index_and_length() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const d = Object.getOwnPropertyDescriptor(s, '0'); const l = Object.getOwnPropertyDescriptor(s, 'length'); return d.value === 'a' && d.writable === false && d.enumerable === true && d.configurable === false && l.value === 2 && l.writable === false && l.enumerable === false && l.configurable === false; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn gopds_full_surface() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const d = Object.getOwnPropertyDescriptors(s); const ks = Object.keys(d).sort(); return ks.length === 3 && ks[0] === '0' && ks[1] === '1' && ks[2] === 'length' && d['0'].value === 'a' && d['1'].value === 'b' && d.length.value === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn seal_freeze_noop() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); Object.seal(s); Object.freeze(s); Object.preventExtensions(s); return s[0] === 'a' && s.length === 2 && s[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn defineproperty_same_value_ok() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const r = Object.defineProperty(s, '1', { value: 'b' }); return r === s && s[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn defineproperty_diff_value_throws() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); let e = null; try { Object.defineProperty(s, '1', { value: 'x' }); } catch (err) { e = err; } return e instanceof TypeError && s[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn defineproperty_attribute_change_throws() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); let e = null; try { Object.defineProperty(s, '1', { writable: true }); } catch (err) { e = err; } return e instanceof TypeError; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn defineproperty_new_key_ok() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const r = Object.defineProperty(s, '2', { value: 'z' }); return r === s && s[2] === 'z'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn set_sloppy_returns_value() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const r0 = s[0] = 'z'; const r1 = s.length = 9; return r0 === 'z' && r1 === 9 && s[0] === 'a' && s.length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn set_strict_throws() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"\"use strict\"; (function(){ const s = new String('ab'); let e1 = null; try { s[0] = 'z'; } catch (err) { e1 = err; } let e2 = null; try { s.length = 9; } catch (err) { e2 = err; } return e1 instanceof TypeError && e2 instanceof TypeError && s[0] === 'a' && s.length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn delete_returns_false() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('ab'); const d0 = delete s[0]; const dl = delete s.length; return d0 === false && dl === false && s[0] === 'a' && s.length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn object_assign_string_source() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const o1 = Object.assign({}, 'ab'); const o2 = Object.assign({}, new String('ab')); return o1[0] === 'a' && o1[1] === 'b' && o1.length === undefined && o2[0] === 'a' && o2[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn object_assign_skips_null_undefined() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const o = Object.assign({}, null, undefined, 'cd'); return o[0] === 'c' && o[1] === 'd' && Object.keys(o).length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn assign_target_string_box_throws() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const t = new String('ab'); let e = null; try { Object.assign(t, 'cd'); } catch (err) { e = err; } return e instanceof TypeError && t[0] === 'a' && t[1] === 'b' && t.length === 2; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn rest_destructure_string_box() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const { 0: a, ...rest } = new String('abc'); return a === 'a' && rest[1] === 'b' && rest[2] === 'c' && rest[0] === undefined; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn string_prototype_box() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const p = String.prototype; return Object.prototype.toString.call(p) === '[object String]' && p.length === 0 && p.valueOf() === ''; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn symbol_tag_via_defineproperty() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('x'); Object.defineProperty(s, Symbol.toStringTag, { value: 'T' }); return Object.prototype.toString.call(s) === '[object T]'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn iterator_over_box() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const arr = [...new String('ab')]; return arr.length === 2 && arr[0] === 'a' && arr[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn array_from_box() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const arr = Array.from(new String('ab')); return arr.length === 2 && arr[0] === 'a' && arr[1] === 'b'; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn empty_string_box() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const e = new String(''); return e.length === 0 && e.valueOf() === '' && Object.getOwnPropertyNames(e).length === 1 && e[0] === undefined; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn valueof_and_toststring_receiver() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const s = new String('z'); return String.prototype.valueOf.call(s) === 'z' && String.prototype.toString.call(s) === 'z'; })()",
)
.unwrap();
assert!(r.as_bool());
}
@@ -1396,8 +1396,8 @@ fn utf16_iterator_lone_surrogate_units() {
#[test]
fn utf16_spread_and_to_object_lone_surrogate() {
// 迭代/spread 按单元产出;rest 解构的 ToObject 字符串下标读得 1 单元串
// (非空串)。字符串包装对象的索引/length 读(new String(...)[0])另有
// 独立既有缺口,不在此钉。
// (非空串)。字符串包装对象索引/length 的构造期物化钉测见
// boxed_string_exotic_tests.rs。
let mut vm = Vm::new();
let r = eval(
&mut vm,