mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: String 盒迁移 boxed_value 载荷并物化固有属性面
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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())
|
||||
};
|
||||
|
||||
@@ -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` 论证)。
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user