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
+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,