mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 落地 Proxy set 组四 trap 完整语义与不变量检查
This commit is contained in:
Generated
+21
-21
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1258,7 +1258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1300,7 +1300,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_fold"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_bytecode",
|
||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1335,7 +1335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1356,11 +1356,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_net"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1375,7 +1375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1391,7 +1391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1404,7 +1404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1424,7 +1424,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1437,7 +1437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.20.8"
|
||||
version = "0.20.9"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -142,6 +142,23 @@ pub fn key_si_to_js_value<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
|
||||
vm.new_string_units_owned(units)
|
||||
}
|
||||
|
||||
/// 键 si 构造为 JS 可见属性键值(Proxy trap 的 P 实参):Symbol 键还原为符号
|
||||
/// 值(键自含 realm 编号与局部下标),整数键反解数字串,字符串键还原单元序列。
|
||||
pub fn key_si_to_js_key<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
|
||||
if is_symbol_key(si) {
|
||||
return symbol_key_to_value(si);
|
||||
}
|
||||
key_si_to_js_value(vm, si)
|
||||
}
|
||||
|
||||
/// 读取对象自身属性描述符对象(`[[GetOwnProperty]]` 的 JS 可见形态):缺失返
|
||||
/// `Ok(None)`;deferred namespace 求值触发失败以异常值 `Err` 上抛(uncaught
|
||||
/// 侧通道已还原为原错误值)。Proxy 的 `[[GetOwnProperty]]` trap 缺失落穿时
|
||||
/// 经本函数取 target 的普通自身描述符。
|
||||
pub fn own_descriptor_value<H: VmHost>(vm: &mut H, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, JsValue> {
|
||||
own_descriptor_of(vm, obj, key_si)
|
||||
}
|
||||
|
||||
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
|
||||
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键。返回 `(属性键 si, 绝对存储索引)`:
|
||||
/// 槽位计数须含全部非空节点(含字符串键)并对数组加元素区偏移,与物理存储及
|
||||
@@ -183,6 +200,97 @@ fn symbol_key_to_value(key: u32) -> JsValue {
|
||||
JsValue::symbol_realm(realm_id, local_index)
|
||||
}
|
||||
|
||||
/// 代理感知的自身键收集(规范 [[OwnPropertyKeys]] 完整形态):普通对象经
|
||||
/// walk_own_keys 加 walk_own_symbol_keys(整数键升序、字符串键插入序、Symbol
|
||||
/// 键插入序);代理经 ownKeys trap——缺失落穿 target 的自身键,存在时以 trap
|
||||
/// 结果为主、target 键中不在 trap 的追加末尾(trap 序优先),target 上没有
|
||||
/// 的键丢弃(无存储槽可定位,描述符按缺失处理)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 非代理:walk_own_keys 加 walk_own_symbol_keys,存储对象即自身。
|
||||
/// 2. 代理:先递归落穿 target(target 自身为代理时再路由)取 target 键。
|
||||
/// 3. ownKeys trap 存在时 ToList 其结果(数组直读元素区、非数组按 length
|
||||
/// 读整数索引区),逐元素 ToPropertyKey 去重。
|
||||
/// 4. 合并:trap 键在前(存储下标按 target 键定位),target 键补尾。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `obj` 须为存活对象;代理的 target 由构造器校验为非空对象指针。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - trap 调用执行用户代码(含分配与 GC);revoked 抛 TypeError。
|
||||
///
|
||||
/// # 返回
|
||||
/// (键列表, 存储对象):消费方读 meta/值一律经存储对象,不读代理本体。
|
||||
pub type OwnKeys<'a> = (Vec<(u32, u32)>, &'a JsObject);
|
||||
|
||||
pub fn own_keys_proxy_aware<'a, H: VmHost>(vm: &mut H, obj: &'a JsObject) -> Result<OwnKeys<'a>, String> {
|
||||
if !obj.is_proxy_obj() {
|
||||
let mut keys = walk_own_keys(vm, obj);
|
||||
keys.extend(walk_own_symbol_keys(vm, obj));
|
||||
return Ok((keys, obj));
|
||||
}
|
||||
let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
// 落穿:target 自身为代理时递归再路由,普通对象取其自身键。
|
||||
let (target_keys, storage) = own_keys_proxy_aware(vm, target_obj)?;
|
||||
let trap_result = vm.proxy_call_trap(obj, vm.perm_intern("ownKeys"), &[target])?;
|
||||
let Some(result) = trap_result else {
|
||||
return Ok((target_keys, storage));
|
||||
};
|
||||
if !result.is_object() {
|
||||
return Err(oxide_runtime_api::format_error_message(
|
||||
"TypeError",
|
||||
"Proxy ownKeys trap returned a non-object",
|
||||
));
|
||||
}
|
||||
// SAFETY: result 是非空对象指针。
|
||||
let result_obj = unsafe { &*result.as_js_object_ptr() };
|
||||
// ToList:数组直读元素区(hole 视为不存在);非数组按 length 属性读整数
|
||||
// 索引区。
|
||||
let mut trap_keys: Vec<u32> = Vec::new();
|
||||
if result_obj.is_array() {
|
||||
for i in 0..result_obj.array_prop_count {
|
||||
if result_obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||
continue;
|
||||
}
|
||||
let si = vm.to_property_key_si(result_obj.get_prop_at(i))?;
|
||||
if !trap_keys.contains(&si) {
|
||||
trap_keys.push(si);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let length_val = vm.ordinary_get(result_obj, vm.perm_intern("length"), result)?;
|
||||
let len = oxide_runtime_api::to_length(length_val);
|
||||
for i in 0..len {
|
||||
// 大索引(2^30 以上)走字符串键形态(规范数字串)。
|
||||
let key_si = if i < INT_KEY_COUNT as u64 {
|
||||
make_int_key(i as u32)
|
||||
} else {
|
||||
vm.perm_intern(&i.to_string())
|
||||
};
|
||||
let val = vm.ordinary_get(result_obj, key_si, result)?;
|
||||
let si = vm.to_property_key_si(val)?;
|
||||
if !trap_keys.contains(&si) {
|
||||
trap_keys.push(si);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 合并:trap 序优先,target 键补尾;target 上没有的键丢弃。
|
||||
let mut keys: Vec<(u32, u32)> = Vec::with_capacity(trap_keys.len() + target_keys.len());
|
||||
for si in &trap_keys {
|
||||
if let Some(store) = target_keys.iter().find(|(k, _)| k == si).map(|(_, s)| *s) {
|
||||
keys.push((*si, store));
|
||||
}
|
||||
}
|
||||
for (si, store) in target_keys {
|
||||
if !trap_keys.contains(&si) {
|
||||
keys.push((si, store));
|
||||
}
|
||||
}
|
||||
Ok((keys, storage))
|
||||
}
|
||||
|
||||
/// 收集对象自身全部 Symbol 键并按插入序物化为 Symbol 值。
|
||||
///
|
||||
/// 供 `Object.getOwnPropertySymbols` 与 `Reflect.ownKeys` 的 Symbol 段共用;
|
||||
@@ -266,10 +374,16 @@ pub fn object_get_own_property_symbols<H: VmHost>(vm: &mut H, args: &[u8]) -> Na
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
|
||||
let symbols: Vec<JsValue> = {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
own_symbol_key_values(vm, obj)
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);本入口只取 Symbol 键。
|
||||
let (keys, _storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
let symbols: Vec<JsValue> = keys
|
||||
.into_iter()
|
||||
.filter(|(si, _)| is_symbol_key(*si))
|
||||
.map(|(si, _)| symbol_key_to_value(si))
|
||||
.collect();
|
||||
|
||||
let n = symbols.len();
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
@@ -342,6 +456,53 @@ pub enum DeleteOutcome {
|
||||
pub fn delete_own_property_outcome<H: VmHost>(
|
||||
vm: &mut H, obj: &mut JsObject, key_si: u32,
|
||||
) -> Result<DeleteOutcome, JsValue> {
|
||||
// Proxy [[Delete]]:deleteProperty trap 以(target、P)调用;trap 缺失落穿
|
||||
// target 的普通删除路径(target 自身为代理时经递归再路由);trap 报 false
|
||||
// 删除失败;trap 报 true 时 target 自身属性不可配置或 target 不可扩展抛
|
||||
// TypeError。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = key_si_to_js_key(vm, key_si);
|
||||
let trap_result = match vm.proxy_call_trap(obj, vm.perm_intern("deleteProperty"), &[target, key]) {
|
||||
Ok(r) => r,
|
||||
Err(msg) => {
|
||||
let exc = vm
|
||||
.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
|
||||
return Err(exc);
|
||||
}
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
let ok = vm
|
||||
.proxy_trap_bool_result(r, "deleteProperty")
|
||||
.map_err(|msg| crate::error::create_from_text(vm, &msg))?;
|
||||
if !ok {
|
||||
return Ok(DeleteOutcome::NonConfigurable);
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
// targetDesc 缺失(步 8)直接返 true,不做不可配置 / 不可扩展检查;
|
||||
// 存在时先查不可配置(步 9)再查不可扩展(步 11)。
|
||||
if let Some(pos) = vm.get_own_property_slot(target_obj, key_si) {
|
||||
let meta = target_obj.prop_meta_at(pos);
|
||||
if meta.is_some_and(|m| !m.attributes.configurable()) {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Proxy deleteProperty trap reported true for a non-configurable target property",
|
||||
));
|
||||
}
|
||||
if !target_obj.is_extensible() {
|
||||
return Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Proxy deleteProperty trap reported true for a non-extensible target",
|
||||
));
|
||||
}
|
||||
}
|
||||
return Ok(DeleteOutcome::Deleted);
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return delete_own_property_outcome(vm, unsafe { &mut *target.as_js_object_ptr() }, key_si);
|
||||
}
|
||||
// deferred namespace 求值触发([[Delete]],symbol-like 键内部 no-op):
|
||||
// 触发失败(cyclic / 失败缓存重抛)以异常值上抛,由调用边界恢复。
|
||||
// uncaught 侧通道已还原为原错误值,原值重抛保身份。
|
||||
@@ -631,10 +792,19 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj)
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);meta 读经存储对象。
|
||||
// Object.keys 只取字符串键(规范 EnumerableOwnPropertyNames 的 key 形态),
|
||||
// Symbol 键在收集器内与字符串键同序,须在此过滤。
|
||||
let (keys, storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
let owned_keys: Vec<(u32, u32)> = keys
|
||||
.into_iter()
|
||||
.filter(|(si, _)| !is_symbol_key(*si))
|
||||
.filter(|(_si, offset)| {
|
||||
obj.prop_meta_at(*offset)
|
||||
storage
|
||||
.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
})
|
||||
@@ -742,11 +912,15 @@ pub fn object_assign<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
let source_keys: Vec<(u32, u32)> = {
|
||||
let source = unsafe { &*source_ptr };
|
||||
walk_own_keys(vm, source)
|
||||
.into_iter()
|
||||
// 源为代理时经 ownKeys trap 路由(缺失落穿 target);meta 读经存储对象。
|
||||
let (keys, storage) = match own_keys_proxy_aware(vm, source) {
|
||||
Ok(k) => k,
|
||||
Err(err) => return NativeResult::Err(crate::error::create_from_text(vm, &err)),
|
||||
};
|
||||
keys.into_iter()
|
||||
// CopyDataProperties:只拷贝可枚举自身属性(无显式 meta 视为可枚举)。
|
||||
.filter(|(_si, offset)| {
|
||||
source
|
||||
storage
|
||||
.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
@@ -858,22 +1032,29 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
|
||||
let length_si = vm.perm_intern("length");
|
||||
let is_array_length = unsafe { &*obj_ptr }.is_array() && key_si == length_si;
|
||||
|
||||
// Proxy [[DefineOwnProperty]]:代理自身形状恒空,缺省回填读终极 target 的
|
||||
// 自身描述符(gOPD trap 由描述符读入口路由,本处读 target 普通存储)。
|
||||
let storage_obj = unsafe { &*obj_ptr };
|
||||
let storage_obj = if storage_obj.is_proxy_obj() {
|
||||
ultimate_target(storage_obj)
|
||||
} else {
|
||||
storage_obj
|
||||
};
|
||||
let existing_pos = if is_array_length {
|
||||
None
|
||||
} else {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
vm.get_own_property_slot(obj, key_si)
|
||||
vm.get_own_property_slot(storage_obj, key_si)
|
||||
};
|
||||
let existing_meta = if is_array_length {
|
||||
let writable = unsafe { &*obj_ptr }.is_length_writable();
|
||||
Some(oxide_types::object::PropMetaEntry::data(PropAttributes::new(writable, false, false)))
|
||||
} else {
|
||||
existing_pos.map(|pos| {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
// 已有属性无显式 meta(普通数据属性/数组元素)时按默认属性回填:
|
||||
// 描述符缺省字段保持现有值(writable/enumerable/configurable 均 true),
|
||||
// 而非按新属性处理为 false(Object.defineProperty 省略字段不改已有属性)。
|
||||
obj.prop_meta_at(pos)
|
||||
storage_obj
|
||||
.prop_meta_at(pos)
|
||||
.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA))
|
||||
})
|
||||
};
|
||||
@@ -919,10 +1100,7 @@ pub(crate) fn define_from_descriptor<H: VmHost>(
|
||||
let existing_value = if is_array_length {
|
||||
unsafe { &*obj_ptr }.logical_len_value()
|
||||
} else {
|
||||
existing_pos.map_or(JsValue::undefined(), |pos| {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
obj.get_prop_at(pos)
|
||||
})
|
||||
existing_pos.map_or(JsValue::undefined(), |pos| storage_obj.get_prop_at(pos))
|
||||
};
|
||||
|
||||
// 访问器描述符:解析 get/set(缺失字段回填现有属性)并做可调用性校验——
|
||||
@@ -1097,17 +1275,33 @@ fn define_all_from_properties<H: VmHost>(
|
||||
let props_ptr = props_obj.as_js_object_ptr();
|
||||
let descriptors: Vec<(u32, JsValue)> = {
|
||||
let props = unsafe { &*props_ptr };
|
||||
walk_own_keys(vm, props)
|
||||
// 描述符对象为代理时经 ownKeys trap 路由(缺失落穿 target);meta 读经
|
||||
// 存储对象。
|
||||
let (keys, storage) = match own_keys_proxy_aware(vm, props) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return Err(DefineDescFailure::Engine(msg)),
|
||||
};
|
||||
// 先物化可枚举自身键(meta 读经存储对象),再逐键经 ordinary_get 取
|
||||
// 描述符值(触发访问器 getter,异常原值传播)。
|
||||
let enumerable: Vec<u32> = keys
|
||||
.into_iter()
|
||||
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
|
||||
.filter(|(_si, offset)| !props.prop_meta_at(*offset).map(|m| !m.attributes.enumerable()).unwrap_or(false))
|
||||
.map(|(key_si, _offset)| {
|
||||
let props = unsafe { &*props_ptr };
|
||||
vm.ordinary_get(props, key_si, props_obj).map(|desc_val| (key_si, desc_val))
|
||||
.filter(|(_si, offset)| {
|
||||
!storage
|
||||
.prop_meta_at(*offset)
|
||||
.map(|m| !m.attributes.enumerable())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
// 描述符值 getter 抛出的异常原值传播。
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|msg| DefineDescFailure::User(crate::array::from_engine_error(vm, &msg)))?
|
||||
.map(|(si, _)| si)
|
||||
.collect();
|
||||
let mut out = Vec::with_capacity(enumerable.len());
|
||||
for key_si in enumerable {
|
||||
let props = unsafe { &*props_ptr };
|
||||
let desc_val = vm
|
||||
.ordinary_get(props, key_si, props_obj)
|
||||
.map_err(|msg| DefineDescFailure::User(crate::array::from_engine_error(vm, &msg)))?;
|
||||
out.push((key_si, desc_val));
|
||||
}
|
||||
out
|
||||
};
|
||||
for (key_si, desc_val) in descriptors {
|
||||
define_from_descriptor(vm, target_ptr, key_si, desc_val)?;
|
||||
@@ -1144,6 +1338,10 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
||||
if let Some(exc) = vm.take_pending_length_exception() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
// 定义期用户代码(Proxy trap)抛出的原始异常值原值重抛。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_define_failure(vm, &msg));
|
||||
}
|
||||
}
|
||||
@@ -1162,6 +1360,33 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
||||
/// # 副作用
|
||||
/// - 在 epoch 分配一个描述符对象
|
||||
fn own_descriptor_of<H: VmHost>(vm: &mut H, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, JsValue> {
|
||||
// Proxy [[GetOwnProperty]]:getOwnPropertyDescriptor trap 以(target、P)
|
||||
// 调用;trap 缺失落穿 target 的自身描述符(target 自身为代理时经递归再
|
||||
// 路由);trap 返 undefined 表示无属性;返非对象按规范抛 TypeError。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = key_si_to_js_key(vm, key_si);
|
||||
let trap_result = match vm.proxy_call_trap(obj, vm.perm_intern("getOwnPropertyDescriptor"), &[target, key]) {
|
||||
Ok(r) => r,
|
||||
Err(msg) => {
|
||||
let exc = vm
|
||||
.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_from_text(vm, &msg));
|
||||
return Err(exc);
|
||||
}
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if r.is_undefined() {
|
||||
return Ok(None);
|
||||
}
|
||||
if !r.is_object() {
|
||||
return Err(crate::error::create_type_error(vm, "getOwnPropertyDescriptor trap returned a non-object"));
|
||||
}
|
||||
return Ok(Some(r));
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return own_descriptor_of(vm, unsafe { &*target.as_js_object_ptr() }, key_si);
|
||||
}
|
||||
// deferred namespace 求值触发([[GetOwnProperty]],symbol-like 键内部 no-op)。
|
||||
// 触发失败时 uncaught 侧通道已还原为原错误值,原值重抛保身份。
|
||||
if let Err(msg) = vm.ensure_deferred_ns_evaluation(obj, Some(key_si)) {
|
||||
@@ -1311,9 +1536,12 @@ pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
|
||||
// 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。
|
||||
let mut keys = walk_own_keys(vm, obj);
|
||||
keys.extend(walk_own_symbol_keys(vm, obj));
|
||||
// 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序;
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target)。
|
||||
let (keys, _storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
|
||||
// 结果对象:proto = %Object.prototype%。
|
||||
let desc_proto = vm.builtin_proto(ProtoKind::ObjectProto);
|
||||
@@ -1335,6 +1563,17 @@ pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -
|
||||
NativeResult::Ok(result_val)
|
||||
}
|
||||
|
||||
/// 代理链的终极 target(沿 ProxyState 递归,非代理对象原样返回)。
|
||||
fn ultimate_target(obj: &JsObject) -> &JsObject {
|
||||
let mut cur = obj;
|
||||
while cur.is_proxy_obj() {
|
||||
let target = crate::proxy::proxy_state_ref(cur).expect("状态盒").target;
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
cur = unsafe { &*target.as_js_object_ptr() };
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
|
||||
/// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。
|
||||
/// 用户代码(accessor getter)抛出的异常原值传播。
|
||||
@@ -1650,7 +1889,14 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
|
||||
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);本入口只读键名,不读存储。
|
||||
// getOwnPropertyNames 只取字符串键(规范过滤 Symbol 键),Symbol 键归
|
||||
// getOwnPropertySymbols,须在此过滤。
|
||||
let (keys, _storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
let keys = keys.into_iter().filter(|(si, _)| !is_symbol_key(*si)).collect::<Vec<_>>();
|
||||
let n = keys.len();
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n));
|
||||
@@ -1690,6 +1936,10 @@ pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
|
||||
if let Some(exc) = vm.take_pending_length_exception() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
// 定义期用户代码(Proxy trap)抛出的原始异常值原值重抛。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_define_failure(vm, &msg));
|
||||
}
|
||||
}
|
||||
@@ -2275,11 +2525,17 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);meta 与值读均经存储对象
|
||||
// (accessor 的 this 指向存储对象)。
|
||||
let (keys, storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
let owned_keys: Vec<(u32, u32)> = keys
|
||||
.into_iter()
|
||||
.filter(|(_si, offset)| {
|
||||
obj.prop_meta_at(*offset)
|
||||
storage
|
||||
.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
})
|
||||
@@ -2287,11 +2543,16 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = owned_keys.len();
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n));
|
||||
let obj_val = JsValue::from_js_object(obj_ptr);
|
||||
let storage_val = if obj.is_proxy_obj() {
|
||||
crate::proxy::proxy_state_ref(obj).expect("状态盒").target
|
||||
} else {
|
||||
JsValue::from_js_object(obj_ptr)
|
||||
};
|
||||
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
// Symbol 键还原为符号值(entries 的 key 为符号本身,非空串)。
|
||||
let key_val = key_si_to_js_key(vm, *si);
|
||||
// EnumerableOwnProperties 的 "key+value":accessor 触发 getter,异常传播原值。
|
||||
let val = match own_property_value(vm, obj, obj_val, *si, *offset) {
|
||||
let val = match own_property_value(vm, storage, storage_val, *si, *offset) {
|
||||
Ok(value) => value,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
@@ -2454,11 +2715,17 @@ pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if let Err(exc) = ensure_deferred_own_keys(vm, obj) {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);meta 与值读均经存储对象
|
||||
// (accessor 的 this 指向存储对象)。
|
||||
let (keys, storage) = match own_keys_proxy_aware(vm, obj) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
let owned_keys: Vec<(u32, u32)> = keys
|
||||
.into_iter()
|
||||
.filter(|(_si, offset)| {
|
||||
obj.prop_meta_at(*offset)
|
||||
storage
|
||||
.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
})
|
||||
@@ -2466,10 +2733,14 @@ pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = owned_keys.len();
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n));
|
||||
let obj_val = JsValue::from_js_object(obj_ptr);
|
||||
let storage_val = if obj.is_proxy_obj() {
|
||||
crate::proxy::proxy_state_ref(obj).expect("状态盒").target
|
||||
} else {
|
||||
JsValue::from_js_object(obj_ptr)
|
||||
};
|
||||
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
||||
// EnumerableOwnProperties 的 "value":accessor 触发 getter,异常传播原值。
|
||||
let val = match own_property_value(vm, obj, obj_val, *si, *offset) {
|
||||
let val = match own_property_value(vm, storage, storage_val, *si, *offset) {
|
||||
Ok(value) => value,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ use oxide_types::private_key::is_int_key;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::array::{arraylike_get, from_engine_error, is_constructor_value};
|
||||
use crate::object::{delete_own_property, key_si_to_js_value, own_symbol_key_values, walk_own_keys};
|
||||
use crate::object::delete_own_property;
|
||||
|
||||
use oxide_runtime_api::{to_length, NativeResult, ProtoKind, VmHost};
|
||||
|
||||
@@ -136,6 +136,10 @@ pub fn reflect_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
if let Some(exc) = vm.take_pending_length_exception() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
// 定义期用户代码(Proxy trap)抛出的原始异常值原值重抛。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
match crate::error::split_kinded(&msg) {
|
||||
Some((kind, rest)) => NativeResult::Err(crate::error::create_kind_error(vm, kind, rest)),
|
||||
None => NativeResult::Ok(JsValue::bool(false)),
|
||||
@@ -256,34 +260,33 @@ pub fn reflect_own_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
|
||||
// Symbol 键分流在消费端追加:底层字符串枚举源(Object.keys / JSON / for-in)
|
||||
// 语义不变,不泄漏 Symbol 键。
|
||||
let mut keys = walk_own_keys(vm, target);
|
||||
// 代理经 ownKeys trap 路由(缺失落穿 target);Symbol 键在收集器内与
|
||||
// 字符串键同序(整数键升序、字符串键插入序、Symbol 键插入序)。
|
||||
let (mut keys, storage) = match crate::object::own_keys_proxy_aware(vm, target) {
|
||||
Ok(k) => k,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)),
|
||||
};
|
||||
// 数组 length 是虚拟自身属性(无 shape 槽、不在形状链):注入字符串组首
|
||||
// (创建序第一枚字符串键),存储下标哨兵 u32::MAX = 无槽位(消费端只读
|
||||
// si,不读存储下标)。
|
||||
if target.is_array() {
|
||||
// si,不读存储下标)。存储对象为数组时注入(trap 已含 length 时不重复)。
|
||||
if storage.is_array() {
|
||||
let length_si = vm.perm_intern("length");
|
||||
let insert_at = keys.iter().position(|(si, _)| !is_int_key(*si)).unwrap_or(keys.len());
|
||||
keys.insert(insert_at, (length_si, u32::MAX));
|
||||
if keys.iter().all(|(si, _)| *si != length_si) {
|
||||
let insert_at = keys.iter().position(|(si, _)| !is_int_key(*si)).unwrap_or(keys.len());
|
||||
keys.insert(insert_at, (length_si, u32::MAX));
|
||||
}
|
||||
}
|
||||
let symbols = own_symbol_key_values(vm, target);
|
||||
let n = keys.len() + symbols.len();
|
||||
let n = keys.len();
|
||||
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n));
|
||||
for (i, (si, _)) in keys.iter().enumerate() {
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
// Symbol 键还原为符号值(ownKeys 返回符号本身,非空串)。
|
||||
let key_val = crate::object::key_si_to_js_key(vm, *si);
|
||||
unsafe {
|
||||
(*arr).set_prop_at(i, key_val);
|
||||
}
|
||||
}
|
||||
let base = keys.len();
|
||||
for (j, sym) in symbols.iter().enumerate() {
|
||||
unsafe {
|
||||
(*arr).set_prop_at(base + j, *sym);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
|
||||
@@ -313,6 +313,20 @@ pub trait VmHost {
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// Proxy trap 调用:从 handler 读 trap 值并同步调用(this 为 handler)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `proxy` 须为带状态盒的 Proxy 对象(调用方以 `is_proxy_obj` 保证入口)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 执行用户 trap 代码(含分配与 GC);trap 值 undefined/null 返 `None`
|
||||
/// (落穿普通行为),revoked 抛 TypeError。
|
||||
fn proxy_call_trap(
|
||||
&mut self, proxy: &JsObject, trap_name_si: u32, args: &[JsValue],
|
||||
) -> Result<Option<JsValue>, String>;
|
||||
/// Proxy trap 返回值布尔投影:非布尔返回抛 TypeError。
|
||||
fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String>;
|
||||
|
||||
// 属性定义
|
||||
fn define_data_property(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
|
||||
|
||||
@@ -492,6 +492,14 @@ impl Vm {
|
||||
let receiver = self.regs[rd];
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
|
||||
// 代理对象恒走完整写路径:IC 命中 / 快路径直写会绕过 set trap
|
||||
// (含 revoked 检查与不变量校验)。
|
||||
if obj.is_proxy_obj() {
|
||||
let prop_name_si = self.property_key_si(self.regs[b])?;
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver, self.current_strict())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 命中路径零键零拦截:__proto__ 赋值从不写 IC,缓存槽必为数据槽。
|
||||
if !obj.has_prop_meta()
|
||||
&& (ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value)
|
||||
|
||||
@@ -94,18 +94,10 @@ pub(crate) fn call_trap(
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
/// 布尔 trap 返回值校验:trap 返回值投影为布尔,非布尔返回抛 TypeError。
|
||||
///
|
||||
/// 错误文本为 `TypeError: ...` 前缀形态,调用边界经 `create_from_text`
|
||||
/// 恢复为 TypeError 异常对象。
|
||||
pub(crate) fn trap_bool_result(result: JsValue, trap_name: &str) -> Result<bool, String> {
|
||||
if result.is_bool() {
|
||||
return Ok(result.as_bool());
|
||||
}
|
||||
Err(oxide_runtime_api::format_error_message(
|
||||
"TypeError",
|
||||
&format!("Proxy {trap_name} trap must return a boolean"),
|
||||
))
|
||||
/// 布尔 trap 返回值投影(规范 ToBoolean 形态):trap 返回值经 ToBoolean
|
||||
/// 投影为布尔,任意值均可投影(0/null/undefined/空串等 falsy 值为 false)。
|
||||
pub(crate) fn trap_bool_result(result: JsValue, _trap_name: &str) -> Result<bool, String> {
|
||||
Ok(oxide_runtime_api::to_boolean(result))
|
||||
}
|
||||
|
||||
/// 把实参切片物化为 JS 数组(CreateArrayFromList 形态:Array.prototype
|
||||
@@ -179,20 +171,46 @@ pub(crate) fn proxy_construct_trap(
|
||||
}
|
||||
}
|
||||
|
||||
/// 构造 defineProperty trap 的属性描述符对象(value 加三标志位)。
|
||||
///
|
||||
/// 描述符属性均为不可写、不可枚举、不可配置(与内建对象属性口径一致);
|
||||
/// 对象原型链落 Object.prototype。
|
||||
pub(crate) fn define_property_descriptor(vm: &mut Vm, val: JsValue, attributes: PropAttributes) -> JsValue {
|
||||
/// 构造 `[[Set]]` 落 receiver 的 defineProperty trap 的部分描述符对象
|
||||
/// (`{ [[Value]]: V }` 形态):仅携 value 字段,writable/enumerable/
|
||||
/// configurable 由 trap 侧(或其委托的 target 定义)按 targetDesc 补全。
|
||||
/// 描述符属性均为可写、可枚举、可配置;对象原型链落 Object.prototype。
|
||||
pub(crate) fn set_partial_trap_desc(vm: &mut Vm, val: JsValue) -> JsValue {
|
||||
let proto = vm.builtin_proto(oxide_runtime_api::ProtoKind::ObjectProto);
|
||||
let obj = vm.alloc_object(JsObject::new_empty(
|
||||
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto),
|
||||
));
|
||||
let attrs = PropAttributes::new(false, false, true);
|
||||
let attrs = PropAttributes::DEFAULT_DATA;
|
||||
let obj_ref = unsafe { &mut *obj };
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("value"), val, attrs);
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("writable"), JsValue::bool(attributes.writable()), attrs);
|
||||
JsValue::from_js_object(obj)
|
||||
}
|
||||
|
||||
/// 构造 defineProperty trap 的属性描述符对象(FromPropertyDescriptor 形态):
|
||||
/// 数据形态为 value 加 writable/enumerable/configurable,访问器形态为 get/set 加
|
||||
/// enumerable/configurable;描述符属性均为可写、可枚举、可配置(trap 侧以
|
||||
/// Object.keys 枚举字段数)。对象原型链落 Object.prototype。
|
||||
pub(crate) fn define_trap_desc(
|
||||
vm: &mut Vm, data: Option<(JsValue, bool)>, get: JsValue, set: JsValue, attributes: PropAttributes,
|
||||
) -> JsValue {
|
||||
let proto = vm.builtin_proto(oxide_runtime_api::ProtoKind::ObjectProto);
|
||||
let obj = vm.alloc_object(JsObject::new_empty(
|
||||
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto),
|
||||
));
|
||||
let attrs = PropAttributes::DEFAULT_DATA;
|
||||
let obj_ref = unsafe { &mut *obj };
|
||||
match data {
|
||||
Some((val, writable)) => {
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("value"), val, attrs);
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("writable"), JsValue::bool(writable), attrs);
|
||||
}
|
||||
None => {
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("get"), get, attrs);
|
||||
let _ = vm.define_data_property(obj_ref, vm.perm_intern("set"), set, attrs);
|
||||
}
|
||||
}
|
||||
let _ =
|
||||
vm.define_data_property(obj_ref, vm.perm_intern("enumerable"), JsValue::bool(attributes.enumerable()), attrs);
|
||||
let _ = vm.define_data_property(
|
||||
|
||||
@@ -223,27 +223,58 @@ impl Vm {
|
||||
/// (`in` 运算符逐级走 HasProperty;非 deferred 对象触发为 no-op),触发失败
|
||||
/// (cyclic 守卫抛 TypeError / 失败缓存原值重抛)走可捕获异常展开。
|
||||
pub(crate) fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result<bool, String> {
|
||||
// Proxy [[HasProperty]]:has trap 以(target、P)调用;trap 报 true 即
|
||||
// 存在;报 false 落穿 target 的普通存在性判定(不变量检查由后续卡补全)。
|
||||
// Proxy [[HasProperty]]:has trap 以(target、P)调用;trap 缺失落穿
|
||||
// target 的普通存在性判定(target 自身为代理时经递归再路由);trap
|
||||
// 报 true 即存在;报 false 时 target 自身属性不可配置或 target 不可
|
||||
// 扩展抛 TypeError。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si);
|
||||
if let Some(r) = crate::proxy::call_trap(self, obj, self.perm_intern("has"), &[target, key])? {
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let trap_result = match crate::proxy::call_trap(self, obj, self.perm_intern("has"), &[target, key]) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e).map(|_| false),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "has")? {
|
||||
return Ok(true);
|
||||
}
|
||||
// target 即本代理(自环)时保守判不存在,避免无限递归。
|
||||
if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
if let Some(pos) = self.get_own_property_slot(target_obj, prop_name_si) {
|
||||
let meta = target_obj.prop_meta_at(pos);
|
||||
if meta.is_some_and(|m| !m.attributes.configurable()) {
|
||||
self.raise_call_error(&self.error_message_text(
|
||||
"TypeError",
|
||||
"Proxy has trap reported false for a non-configurable target property",
|
||||
))?;
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
// target 不可扩展:不变量检查在 targetDesc 判定之外恒执行
|
||||
// (规范步 11,targetDesc 缺失同样抛错)。
|
||||
if !target_obj.is_extensible() {
|
||||
self.raise_call_error(
|
||||
&self.error_message_text(
|
||||
"TypeError",
|
||||
"Proxy has trap reported false for a non-extensible target",
|
||||
),
|
||||
)?;
|
||||
return Ok(false);
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.has_property(unsafe { &*target.as_js_object_ptr() }, prop_name_si);
|
||||
return Ok(false);
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.has_property(unsafe { &*target.as_js_object_ptr() }, prop_name_si);
|
||||
}
|
||||
let length_si = self.length_si;
|
||||
let mut current = Some(obj);
|
||||
let mut depth = 0usize;
|
||||
while let Some(obj) = current {
|
||||
// 原型链上的代理层:HasProperty 逐层判定,代理层调自身
|
||||
// [[HasProperty]](经入口守卫路由 trap)。
|
||||
if obj.is_proxy_obj() {
|
||||
return self.has_property(obj, prop_name_si);
|
||||
}
|
||||
// deferred namespace 求值触发(symbol-like 键内部 no-op):链上每层
|
||||
// 独立判定,`key in obj`(obj 原型为 deferred ns)在 ns 层触发。
|
||||
if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) {
|
||||
|
||||
@@ -254,6 +254,14 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
// builtin 调用边界:写失败返回格式化 Err,不就地展开(见 ordinary_set_builtin)。
|
||||
self.ordinary_set_builtin(obj, prop_name_si, val, receiver, strict)
|
||||
}
|
||||
fn proxy_call_trap(
|
||||
&mut self, proxy: &JsObject, trap_name_si: u32, args: &[JsValue],
|
||||
) -> Result<Option<JsValue>, String> {
|
||||
crate::proxy::call_trap(self, proxy, trap_name_si, args)
|
||||
}
|
||||
fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result<bool, String> {
|
||||
crate::proxy::trap_bool_result(result, trap_name)
|
||||
}
|
||||
fn define_data_property(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String> {
|
||||
|
||||
+617
-83
@@ -308,8 +308,18 @@ impl Vm {
|
||||
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
|
||||
depth += 1;
|
||||
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
|
||||
if let Some(pos) = self.get_own_property_slot(proto_obj, prop_name_si) {
|
||||
return proto_obj.prop_meta_at(pos);
|
||||
// 代理层:无 getOwnPropertyDescriptor trap 时 [[GetOwnProperty]]
|
||||
// 落穿 target 的自身属性,故按 target 的自身槽位判定;链上续层取
|
||||
// 代理自身的原型([[GetPrototypeOf]] 同样落穿 target)。
|
||||
let effective = if proto_obj.is_proxy_obj() {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(proto_obj).expect("状态盒").target;
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
unsafe { &*target.as_js_object_ptr() }
|
||||
} else {
|
||||
proto_obj
|
||||
};
|
||||
if let Some(pos) = self.get_own_property_slot(effective, prop_name_si) {
|
||||
return effective.prop_meta_at(pos);
|
||||
}
|
||||
proto = proto_obj.proto();
|
||||
}
|
||||
@@ -418,7 +428,7 @@ impl Vm {
|
||||
pub(crate) fn ordinary_set(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::bytecode(strict))
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::bytecode(strict), 0)
|
||||
}
|
||||
|
||||
/// builtin 内部写入口:失败一律返回格式化 `Err` 由调用边界恢复为异常对象,
|
||||
@@ -427,18 +437,19 @@ impl Vm {
|
||||
pub(crate) fn ordinary_set_builtin(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::builtin(strict))
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::builtin(strict), 0)
|
||||
}
|
||||
|
||||
/// 分发期入口:值直接落位。
|
||||
pub(crate) fn ordinary_set_dispatch(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, strict: bool,
|
||||
) -> Result<(), String> {
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::dispatch(strict))
|
||||
self.ordinary_set_inner(obj, prop_name_si, val, receiver, SetContract::dispatch(strict), 0)
|
||||
}
|
||||
|
||||
fn ordinary_set_inner(
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, contract: SetContract,
|
||||
depth: usize,
|
||||
) -> Result<(), String> {
|
||||
let strict = contract.strict;
|
||||
let builtin = contract.builtin;
|
||||
@@ -449,29 +460,37 @@ impl Vm {
|
||||
prop_name_si,
|
||||
use_frame_push
|
||||
);
|
||||
// Proxy [[Set]]:set trap 以(target、P、Value、Receiver)调用;trap 报
|
||||
// true 写成功;报 false 落穿 target 的普通写路径(不变量检查由后续卡补全)。
|
||||
// Proxy [[Set]]:set trap 以(target、P、Value、Receiver)调用;trap
|
||||
// 缺失落穿 target 的普通写路径(target 自身为代理时经递归再路由);
|
||||
// trap 返回经 ToBoolean 投影:false 时 [[Set]] 返 false(严格抛错 /
|
||||
// sloppy 静默由调用方分派);true 时检查 target 自身属性不变量。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si);
|
||||
if let Some(r) = crate::proxy::call_trap(self, obj, self.perm_intern("set"), &[target, key, val, receiver])?
|
||||
{
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let trap_result =
|
||||
match crate::proxy::call_trap(self, obj, self.perm_intern("set"), &[target, key, val, receiver]) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "set")? {
|
||||
return self.proxy_set_invariant_check(target, prop_name_si, val, builtin);
|
||||
}
|
||||
// [[Set]] 返 false:严格抛 TypeError,sloppy 静默 no-op。
|
||||
if !strict {
|
||||
return Ok(());
|
||||
}
|
||||
// target 即本代理(自环)时按写失败处理,避免无限递归。
|
||||
if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) {
|
||||
return self.write_protection_failure(builtin, "Proxy set trap reported false");
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.ordinary_set_inner(
|
||||
unsafe { &mut *target.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
val,
|
||||
receiver,
|
||||
contract,
|
||||
);
|
||||
return self.write_protection_failure(builtin, "Proxy set trap reported false");
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.ordinary_set_inner(
|
||||
unsafe { &mut *target.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
val,
|
||||
receiver,
|
||||
contract,
|
||||
depth + 1,
|
||||
);
|
||||
}
|
||||
// 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8,deferred 形态同口径
|
||||
// 不触发求值):严格抛 TypeError,sloppy 静默 no-op;Reflect.set 以
|
||||
@@ -586,47 +605,156 @@ impl Vm {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// own 可写数据属性:receiver 为其他对象时按 receiver 自身属性
|
||||
// 判定(规范步 2.c–h)——访问器 / 不可写数据使 [[Set]] 返 false;
|
||||
// 缺失走 CreateDataProperty(extensible 检查 + 数组 length 增长
|
||||
// 检查),可写数据走 receiver 部分定义;receiver 为代理时落其
|
||||
// getOwnPropertyDescriptor / defineProperty trap。receiver 即基
|
||||
// 对象时直写等价,走原路径。
|
||||
let receiver_ptr = receiver.as_js_object_ptr();
|
||||
if !receiver_ptr.is_null() && !std::ptr::eq(receiver_ptr, obj as *mut JsObject) {
|
||||
// SAFETY: receiver_ptr 为 receiver 值携带的非空对象指针,对象
|
||||
// 在本会话内存活;写路径不移动对象。
|
||||
let receiver_obj = unsafe { &*receiver_ptr };
|
||||
if receiver_obj.is_proxy_obj() {
|
||||
let ok = self.proxy_set_via_receiver(receiver_obj, prop_name_si, val, strict, builtin)?;
|
||||
if !ok {
|
||||
// [[Set]] 返 false:严格抛 TypeError,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "Proxy set failed");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// TypedArray receiver:写入路由到 receiver 的 [[Set]](界内
|
||||
// 元素写 / 数字无效强转后丢弃),不写基对象。
|
||||
if receiver_obj.is_typed_array_obj() {
|
||||
return oxide_builtins::typed_array::typed_array_receiver_set(
|
||||
self,
|
||||
unsafe { &mut *receiver_ptr },
|
||||
prop_name_si,
|
||||
val,
|
||||
);
|
||||
}
|
||||
let existing_meta = self
|
||||
.get_own_property_slot(receiver_obj, prop_name_si)
|
||||
.and_then(|p| receiver_obj.prop_meta_at(p));
|
||||
if let Some(meta) = existing_meta {
|
||||
if meta.is_accessor {
|
||||
// receiver 自身访问器:[[Set]] 返 false(setter 不调用)。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "receiver property is an accessor");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if !meta.attributes.writable() {
|
||||
// receiver 自身不可写数据:[[Set]] 返 false。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "cannot assign to read-only property");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// 可写数据:receiver 部分定义(属性在场,无 extensible 检查)。
|
||||
self.set_or_create_prop_value(unsafe { &mut *receiver_ptr }, prop_name_si, val);
|
||||
return Ok(());
|
||||
}
|
||||
// 缺失:CreateDataProperty(receiver, P, V)——receiver 须可扩展。
|
||||
if !receiver_obj.is_extensible() {
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "object is not extensible");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// 数组索引增长(index >= 当前 length)过 length 可写性检查。
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
if receiver_obj.is_array()
|
||||
&& index >= receiver_obj.logical_len()
|
||||
&& !receiver_obj.is_length_writable()
|
||||
{
|
||||
return self.array_length_write_failure(
|
||||
strict,
|
||||
builtin,
|
||||
"Cannot add property, array length is not writable",
|
||||
);
|
||||
}
|
||||
}
|
||||
self.set_or_create_prop_value(unsafe { &mut *receiver_ptr }, prop_name_si, val);
|
||||
return Ok(());
|
||||
}
|
||||
// pos 是存储索引(get_own_property_slot 对数组已加元素区偏移)。
|
||||
obj.set_prop_storage(pos as usize, val);
|
||||
self.sync_global_builtin_mirror(obj, prop_name_si, val);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(meta) = self.inherited_property_meta(obj, prop_name_si) {
|
||||
if meta.is_accessor {
|
||||
if meta.set.is_undefined() {
|
||||
// 继承无 setter:严格抛错,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "property has no setter");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
|
||||
}
|
||||
if !meta.attributes.writable() {
|
||||
// 继承只读数据属性:严格抛错,sloppy 静默 no-op(不遮蔽)。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "cannot assign to read-only property");
|
||||
}
|
||||
return Ok(());
|
||||
// ownDesc 缺失(OrdinarySetWithOwnDescriptor 步 1):原型存在时路由
|
||||
// parent.[[Set]](P, V, Receiver)——继承访问器 / 只读属性 / 原型链上的
|
||||
// 代理层全部由父层自身 [[Set]] 判定(receiver 原样透传,setter 的 this
|
||||
// 即 receiver);原型链走完(或深度到界)时 CreateDataProperty(Receiver,
|
||||
// P, V)。
|
||||
if depth < MAX_PROTO_CHAIN_DEPTH {
|
||||
let parent = obj.proto();
|
||||
if parent.is_object() {
|
||||
// SAFETY: parent 为值携带的非空对象指针,对象在本会话内存活;
|
||||
// 写路径不移动对象。
|
||||
return self.ordinary_set_inner(
|
||||
unsafe { &mut *parent.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
val,
|
||||
receiver,
|
||||
contract,
|
||||
depth + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 新属性(自身与原型链均无同名):须对象可扩展(OrdinarySet 的 extensible
|
||||
// 检查先于 length 增长判定),不可扩展时赋值失败(严格抛错,sloppy 静默 no-op)。
|
||||
if !obj.is_extensible() {
|
||||
// CreateDataProperty:receiver 须为对象且可扩展。
|
||||
if !receiver.is_object() {
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "receiver is not an object");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let receiver_ptr = receiver.as_js_object_ptr();
|
||||
// SAFETY: receiver_ptr 为 receiver 值携带的非空对象指针,对象在本会话
|
||||
// 内存活;写路径不移动对象。
|
||||
let receiver_obj = unsafe { &*receiver_ptr };
|
||||
if !receiver_obj.is_extensible() {
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "object is not extensible");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// receiver 为代理:经 receiver 的 [[GetOwnProperty]](gOPD trap)判既有
|
||||
// 描述符——缺失走 CreateDataProperty(完整描述符),可写数据走
|
||||
// Receiver[[DefineOwnProperty]](P, {value: V})(部分描述符)。
|
||||
if receiver_obj.is_proxy_obj() {
|
||||
let ok = self.proxy_set_via_receiver(receiver_obj, prop_name_si, val, strict, builtin)?;
|
||||
if !ok {
|
||||
// [[Set]] 返 false:严格抛 TypeError,sloppy 静默 no-op。
|
||||
if strict {
|
||||
return self.write_protection_failure(builtin, "Proxy set failed");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// receiver 为 TypedArray 且非基对象:写入路由到 receiver 的 [[Set]]
|
||||
// (数值键:界内元素写 / 数字无效强转后丢弃;非数字键:落 receiver
|
||||
// 属性),不在基对象上建影子属性。
|
||||
if !std::ptr::eq(receiver_ptr, obj as *mut JsObject) && receiver_obj.is_typed_array_obj() {
|
||||
return oxide_builtins::typed_array::typed_array_receiver_set(
|
||||
self,
|
||||
unsafe { &mut *receiver_ptr },
|
||||
prop_name_si,
|
||||
val,
|
||||
);
|
||||
}
|
||||
// 数组索引增长(index >= 当前 length)过 length 可写性检查:与 define 侧
|
||||
// `define_array_index_element` 同序;length 不可写时严格抛 TypeError、
|
||||
// sloppy 静默。索引小于 length 的已有元素写入在自身槽位命中即返回,
|
||||
// 不达此处。
|
||||
// sloppy 静默。
|
||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||
if obj.is_array() && index >= obj.logical_len() && !obj.is_length_writable() {
|
||||
if receiver_obj.is_array() && index >= receiver_obj.logical_len() && !receiver_obj.is_length_writable() {
|
||||
return self.array_length_write_failure(
|
||||
strict,
|
||||
builtin,
|
||||
@@ -634,23 +762,7 @@ impl Vm {
|
||||
);
|
||||
}
|
||||
}
|
||||
// receiver 非基对象且为 TypedArray:写入路由到 receiver 的 [[Set]]
|
||||
// (数值键:界内元素写 / 数字无效强转后丢弃;非数字键:落 receiver
|
||||
// 属性),不在基对象上建影子属性。
|
||||
let receiver_ptr = receiver.as_js_object_ptr();
|
||||
if !receiver_ptr.is_null() && !std::ptr::eq(receiver_ptr, obj as *mut JsObject) {
|
||||
// SAFETY: receiver_ptr 为 receiver 值携带的非空对象指针,对象在
|
||||
// 本会话内存活;写路径不移动对象。
|
||||
if unsafe { &*receiver_ptr }.is_typed_array_obj() {
|
||||
return oxide_builtins::typed_array::typed_array_receiver_set(
|
||||
self,
|
||||
unsafe { &mut *receiver_ptr },
|
||||
prop_name_si,
|
||||
val,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.set_or_create_prop_value(obj, prop_name_si, val);
|
||||
self.set_or_create_prop_value(unsafe { &mut *receiver_ptr }, prop_name_si, val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -741,6 +853,382 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy trap 调用失败(revoked / 非可调用 / trap 抛异常)的两态出口:
|
||||
/// 顶层字节码上下文(`native_call_depth == 0`)就地物化原异常值并展开到
|
||||
/// 外围 try/catch(可捕获);builtin 内部原样返回格式化 `Err`,uncaught
|
||||
/// 侧通道保留原异常值,由调用边界 `take_uncaught_value` 原值重抛。
|
||||
pub(crate) fn proxy_trap_call_failure(&mut self, err: String) -> Result<(), String> {
|
||||
if self.native_call_depth == 0 {
|
||||
self.raise_call_error(&err).map(|_| ())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// 读 target 自身属性描述符(Proxy trap 不变量检查共用):返回
|
||||
/// (value、是否访问器、attributes、get、set);属性缺失返 `None`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 只查 target 自身(不经原型链),口径与规范 `[[GetOwnProperty]]` 一致。
|
||||
fn proxy_target_desc(
|
||||
&self, target: JsValue, prop_name_si: u32,
|
||||
) -> Option<(JsValue, bool, PropAttributes, JsValue, JsValue)> {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
let pos = self.get_own_property_slot(target_obj, prop_name_si)?;
|
||||
let meta = target_obj.prop_meta_at(pos)?;
|
||||
Some((target_obj.get_prop_at(pos), meta.is_accessor, meta.attributes, meta.get, meta.set))
|
||||
}
|
||||
|
||||
/// Proxy [[Set]] trap 报 true 的不变量检查(规范 [[Set]] 步 8 形态):
|
||||
/// target 自身属性不可配置时,不可写数据属性要求 SameValue(V,
|
||||
/// targetDesc.value)(值不等抛 TypeError;可写数据属性值不等不抛),
|
||||
/// 访问器属性要求有 setter;违反抛 TypeError(builtin 上下文物化原值入
|
||||
/// uncaught 侧通道,调用边界原值重抛,不得投影为写失败 false)。
|
||||
fn proxy_set_invariant_check(
|
||||
&mut self, target: JsValue, prop_name_si: u32, val: JsValue, builtin: bool,
|
||||
) -> Result<(), String> {
|
||||
let Some((target_val, is_accessor, attrs, _, set)) = self.proxy_target_desc(target, prop_name_si) else {
|
||||
return Ok(());
|
||||
};
|
||||
if !attrs.configurable() {
|
||||
if is_accessor {
|
||||
if set.is_undefined() {
|
||||
return self.proxy_invariant_failure(
|
||||
builtin,
|
||||
"Proxy set trap reported true for a non-configurable target accessor without setter",
|
||||
);
|
||||
}
|
||||
} else if !attrs.writable() && !oxide_runtime_api::same_value(val, target_val) {
|
||||
return self.proxy_invariant_failure(
|
||||
builtin,
|
||||
"Proxy set trap reported true for a non-writable target property",
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Proxy trap 不变量检查违反的两态出口:builtin 上下文物化 TypeError
|
||||
/// 写入 uncaught 侧通道(调用边界 `take_uncaught_value` 原值重抛),
|
||||
/// 字节码上下文就地抛可捕获异常并展开。
|
||||
fn proxy_invariant_failure(&mut self, builtin: bool, msg: &str) -> Result<(), String> {
|
||||
if builtin {
|
||||
let exc = oxide_builtins::error::create_type_error(self, msg);
|
||||
self.last_uncaught_value = Some(exc);
|
||||
Err(self.error_message_text("TypeError", msg))
|
||||
} else {
|
||||
self.raise_type_error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy [[DefineOwnProperty]] trap 报 true 的不变量检查:target 自身属性
|
||||
/// 缺失时 target 须可扩展且描述符不得不可配置;存在时逐项做兼容检查
|
||||
/// (可配置性 / 枚举性 / 数据与访问器形态互斥 / 值与 get-set 一致性),
|
||||
/// 不可配置 target 不得定义不可配置描述符,不可配置可写数据属性不得
|
||||
/// 重定义为不可写。违反以 `TypeError: ` 前缀 `Err` 返回(调用边界按
|
||||
/// kind 前缀恢复为 TypeError 异常对象,Reflect.defineProperty 不得投影
|
||||
/// 为 false)。
|
||||
fn proxy_define_invariant_check(
|
||||
&mut self, target: JsValue, prop_name_si: u32, attributes: PropAttributes, data: Option<(JsValue, bool)>,
|
||||
accessor: Option<(JsValue, JsValue)>,
|
||||
) -> Result<(), String> {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
let extensible = target_obj.is_extensible();
|
||||
let desc_configurable = attributes.configurable();
|
||||
let desc_enumerable = attributes.enumerable();
|
||||
let fail = |msg: &str| Err(self.error_message_text("TypeError", msg));
|
||||
match self.proxy_target_desc(target, prop_name_si) {
|
||||
None => {
|
||||
if !extensible {
|
||||
return fail("Proxy defineProperty trap reported true for a non-extensible target");
|
||||
}
|
||||
if !desc_configurable {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-configurable descriptor on a missing target property",
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Some((cur_value, cur_accessor, cur_attrs, cur_get, cur_set)) => {
|
||||
if !cur_attrs.configurable() {
|
||||
if desc_configurable {
|
||||
return fail("Proxy defineProperty trap reported true for a non-configurable target property");
|
||||
}
|
||||
if cur_attrs.enumerable() != desc_enumerable {
|
||||
return fail("Proxy defineProperty trap reported true for a non-configurable target property");
|
||||
}
|
||||
match (cur_accessor, data.is_some()) {
|
||||
(false, true) => {
|
||||
let (desc_value, desc_writable) = data.unwrap();
|
||||
if !cur_attrs.writable() && desc_writable {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-writable target property",
|
||||
);
|
||||
}
|
||||
if !oxide_runtime_api::same_value(cur_value, desc_value) {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-writable target property",
|
||||
);
|
||||
}
|
||||
}
|
||||
(false, false) | (true, true) => {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-configurable target property",
|
||||
);
|
||||
}
|
||||
(true, false) => {
|
||||
let (get, set) = accessor.expect("访问器形态");
|
||||
if !oxide_runtime_api::same_value(cur_get, get) {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-configurable target property",
|
||||
);
|
||||
}
|
||||
if !oxide_runtime_api::same_value(cur_set, set) {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-configurable target property",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !desc_configurable && cur_attrs.configurable() {
|
||||
return fail("Proxy defineProperty trap reported true for a configurable target property");
|
||||
}
|
||||
if !cur_accessor && !cur_attrs.configurable() && cur_attrs.writable() {
|
||||
if let Some((_, desc_writable)) = data {
|
||||
if !desc_writable {
|
||||
return fail(
|
||||
"Proxy defineProperty trap reported true for a non-configurable writable target property",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy receiver 的 `[[GetOwnProperty]]`(getOwnPropertyDescriptor trap):
|
||||
/// trap 以(target、P)调用,返回值原样透传(描述符对象);trap 缺失落穿
|
||||
/// target 的普通自身描述符(target 自身为代理时经递归再路由),缺失返 None。
|
||||
fn proxy_receiver_own_descriptor(
|
||||
&mut self, receiver_proxy: &JsObject, prop_name_si: u32,
|
||||
) -> Result<Option<JsValue>, String> {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(receiver_proxy).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let trap_result = match crate::proxy::call_trap(
|
||||
self,
|
||||
receiver_proxy,
|
||||
self.perm_intern("getOwnPropertyDescriptor"),
|
||||
&[target, key],
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e).map(|_| None),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
// gOPD trap 返 undefined 表示无属性(规范 [[GetOwnProperty]] 语义);
|
||||
// 非对象结果同按缺失处理。
|
||||
return if r.is_object() { Ok(Some(r)) } else { Ok(None) };
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
if target_obj.is_proxy_obj() {
|
||||
return self.proxy_receiver_own_descriptor(target_obj, prop_name_si);
|
||||
}
|
||||
// 普通 target:读自身描述符(缺失返 None);deferred namespace 求值
|
||||
// 触发失败以异常值上抛,原值入 uncaught 侧通道由调用边界原值重抛。
|
||||
match oxide_builtins::object::own_descriptor_value(self, target_obj, prop_name_si) {
|
||||
Ok(desc) => Ok(desc),
|
||||
Err(exc) => {
|
||||
self.last_uncaught_value = Some(exc);
|
||||
Err(self.error_message_text("Error", "deferred namespace evaluation failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 读描述符对象的形态与可写性:(是否访问器、是否可写)。访问器形态以
|
||||
/// get/set 字段在场判定;数据形态以 writable 字段(缺省视为可写)。
|
||||
fn read_desc_form(&self, desc: JsValue) -> (bool, bool) {
|
||||
// SAFETY: desc 为值携带的非空对象指针。
|
||||
let desc_obj = unsafe { &*desc.as_js_object_ptr() };
|
||||
let is_accessor = self.resolve_property(desc_obj, self.perm_intern("get")).is_some()
|
||||
|| self.resolve_property(desc_obj, self.perm_intern("set")).is_some();
|
||||
let writable = if is_accessor {
|
||||
true
|
||||
} else {
|
||||
self.resolve_property(desc_obj, self.perm_intern("writable"))
|
||||
.map(oxide_runtime_api::to_boolean)
|
||||
.unwrap_or(true)
|
||||
};
|
||||
(is_accessor, writable)
|
||||
}
|
||||
|
||||
/// `[[IsExtensible]]`(代理转发):非代理对象读 header 位;代理对象先
|
||||
/// consult isExtensible trap(布尔投影),trap 缺失递归 target——代理自身
|
||||
/// header 位无扩展性语义。
|
||||
fn is_extensible_through_proxy(&mut self, obj: &JsObject) -> Result<bool, String> {
|
||||
if !obj.is_proxy_obj() {
|
||||
return Ok(obj.is_extensible());
|
||||
}
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
match crate::proxy::call_trap(self, obj, self.perm_intern("isExtensible"), &[target]) {
|
||||
Ok(Some(r)) => Ok(oxide_runtime_api::to_boolean(r)),
|
||||
Ok(None) => {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
self.is_extensible_through_proxy(target_obj)
|
||||
}
|
||||
Err(e) => self.proxy_trap_call_failure(e).map(|_| obj.is_extensible()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 代理链的终极 target(沿 [[ProxyTarget]] 递归)。
|
||||
fn ultimate_proxy_target(obj: &JsObject) -> &JsObject {
|
||||
let mut cur = obj;
|
||||
while cur.is_proxy_obj() {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(cur).expect("状态盒").target;
|
||||
cur = unsafe { &*target.as_js_object_ptr() };
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
/// target 自身定义并投影为布尔:不可扩展对象的新属性定义返 `Ok(false)`
|
||||
/// (`[[DefineOwnProperty]]` 语义),其余失败原样上抛。target 为代理时
|
||||
/// 扩展性按 `[[IsExtensible]]`(isExtensible trap → target 位)判定,
|
||||
/// 「新属性」按终极 target 自身存储判定(代理自身存储恒空,直读会把
|
||||
/// 既有属性误判为新属性)。
|
||||
fn define_on_target_bool(
|
||||
&mut self, target: JsValue, prop_name_si: u32, val: JsValue, attrs: PropAttributes,
|
||||
) -> Result<bool, String> {
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||||
match self.define_data_property(unsafe { &mut *target.as_js_object_ptr() }, prop_name_si, val, attrs) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(e) => {
|
||||
let ultimate = Self::ultimate_proxy_target(target_obj);
|
||||
let is_new = self.get_own_property_slot(ultimate, prop_name_si).is_none();
|
||||
if !self.is_extensible_through_proxy(target_obj)? && is_new {
|
||||
return Ok(false);
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy receiver 的 `[[DefineOwnProperty]]`(defineProperty trap):trap
|
||||
/// 以(target、P、部分描述符 {value: V})调用;报 true 时读 target 自身
|
||||
/// 描述符补全部分描述符(数据形态取 target 的 writable/enumerable/
|
||||
/// configurable)、做不变量检查后写 target;报 false 或 target 不可扩展
|
||||
/// 的新属性定义返 `Ok(false)`(`[[Set]]` 投影为 false)。
|
||||
fn proxy_define_on_receiver(
|
||||
&mut self, receiver_proxy: &JsObject, prop_name_si: u32, val: JsValue,
|
||||
) -> Result<bool, String> {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(receiver_proxy).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let desc = crate::proxy::set_partial_trap_desc(self, val);
|
||||
let trap_result = match crate::proxy::call_trap(
|
||||
self,
|
||||
receiver_proxy,
|
||||
self.perm_intern("defineProperty"),
|
||||
&[target, key, desc],
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e).map(|_| false),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "defineProperty")? {
|
||||
// 补全部分描述符:数据 targetDesc 取其 writable/enumerable/
|
||||
// configurable;targetDesc 缺失按新建数据属性(全真)。
|
||||
let completed = self
|
||||
.proxy_target_desc(target, prop_name_si)
|
||||
.map(|(_, _, attrs, _, _)| attrs)
|
||||
.unwrap_or(oxide_types::object::PropAttributes::DEFAULT_DATA);
|
||||
self.proxy_define_invariant_check(
|
||||
target,
|
||||
prop_name_si,
|
||||
completed,
|
||||
Some((val, completed.writable())),
|
||||
None,
|
||||
)?;
|
||||
return self.define_on_target_bool(target, prop_name_si, val, completed);
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// trap 缺失:落穿 target 的普通定义(完整数据描述符)。
|
||||
let full = oxide_types::object::PropAttributes::DEFAULT_DATA;
|
||||
self.define_on_target_bool(target, prop_name_si, val, full)
|
||||
}
|
||||
|
||||
/// CreateDataProperty(Receiver, P, V)(Receiver 为代理):defineProperty
|
||||
/// trap 携完整数据描述符 {value: V, writable/enumerable/configurable 全真};
|
||||
/// 报 true 做不变量检查后写 target,报 false 或 target 不可扩展的新属性
|
||||
/// 定义返 `Ok(false)`。
|
||||
fn proxy_create_data_property(
|
||||
&mut self, receiver_proxy: &JsObject, prop_name_si: u32, val: JsValue,
|
||||
) -> Result<bool, String> {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(receiver_proxy).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let full = oxide_types::object::PropAttributes::DEFAULT_DATA;
|
||||
let desc =
|
||||
crate::proxy::define_trap_desc(self, Some((val, true)), JsValue::undefined(), JsValue::undefined(), full);
|
||||
let trap_result = match crate::proxy::call_trap(
|
||||
self,
|
||||
receiver_proxy,
|
||||
self.perm_intern("defineProperty"),
|
||||
&[target, key, desc],
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e).map(|_| false),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "defineProperty")? {
|
||||
self.proxy_define_invariant_check(target, prop_name_si, full, Some((val, true)), None)?;
|
||||
return self.define_on_target_bool(target, prop_name_si, val, full);
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
// trap 缺失:落穿 target 的普通定义。
|
||||
self.define_on_target_bool(target, prop_name_si, val, full)
|
||||
}
|
||||
|
||||
/// Proxy receiver 的 `[[Set]]` 数据分支 consult(OrdinarySetWithOwnDescriptor
|
||||
/// 步 2.c–h):先经 receiver 的 `[[GetOwnProperty]]`(gOPD trap)判既有
|
||||
/// 描述符——缺失走 CreateDataProperty(完整描述符);访问器 / 不可写数据
|
||||
/// 使 `[[Set]]` 返 false(严格抛错 / sloppy 静默);可写数据走
|
||||
/// Receiver[[DefineOwnProperty]](P, {value: V})(部分描述符)。返 `Ok(false)`
|
||||
/// 表示 `[[Set]]` 应返 false。
|
||||
fn proxy_set_via_receiver(
|
||||
&mut self, receiver_proxy: &JsObject, prop_name_si: u32, val: JsValue, strict: bool, builtin: bool,
|
||||
) -> Result<bool, String> {
|
||||
let existing = self.proxy_receiver_own_descriptor(receiver_proxy, prop_name_si)?;
|
||||
match existing {
|
||||
None => self.proxy_create_data_property(receiver_proxy, prop_name_si, val),
|
||||
Some(desc) => {
|
||||
let (is_accessor, writable) = self.read_desc_form(desc);
|
||||
if is_accessor {
|
||||
if strict {
|
||||
return self
|
||||
.write_protection_failure(builtin, "receiver property is an accessor")
|
||||
.map(|_| false);
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
if !writable {
|
||||
if strict {
|
||||
return self
|
||||
.write_protection_failure(builtin, "cannot assign to read-only property")
|
||||
.map(|_| false);
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
self.proxy_define_on_receiver(receiver_proxy, prop_name_si, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TypedArray 界内规范键在 `receiver` ≠ TA 时的 [[Set]] 语义:
|
||||
///
|
||||
/// # 步骤
|
||||
@@ -927,6 +1415,12 @@ impl Vm {
|
||||
ic_trace!("set_member_prop: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
||||
// 写方即当前执行函数(赋值语义),strict/sloppy 判定取当前上下文。
|
||||
let strict = self.current_strict();
|
||||
// 代理对象恒走完整写路径:IC / meta / 快路径直写会绕过 set trap
|
||||
// (含 revoked 检查与不变量校验)。
|
||||
if obj.is_proxy_obj() {
|
||||
self.ordinary_set(obj, prop_name_si, val, receiver, strict)?;
|
||||
return Ok(());
|
||||
}
|
||||
if obj.has_prop_meta() {
|
||||
self.ordinary_set(obj, prop_name_si, val, receiver, strict)?;
|
||||
return Ok(());
|
||||
@@ -1143,30 +1637,43 @@ impl Vm {
|
||||
) -> Result<(), String> {
|
||||
vm_trace!("define_data_property: shape={} prop_si={}", obj.shape_id(), prop_name_si);
|
||||
// Proxy [[DefineOwnProperty]]:defineProperty trap 以(target、P、Desc)
|
||||
// 调用;trap 报 true 定义成功;报 false 落穿 target 的普通定义路径
|
||||
// (不变量检查由后续卡补全)。
|
||||
// 调用;trap 缺失落穿 target 的普通定义路径(target 自身为代理时经
|
||||
// 递归再路由);trap 报 false 定义失败;trap 报 true 时检查 target
|
||||
// 自身属性不变量。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si);
|
||||
let desc = crate::proxy::define_property_descriptor(self, val, attributes);
|
||||
if let Some(r) =
|
||||
crate::proxy::call_trap(self, obj, self.perm_intern("defineProperty"), &[target, key, desc])?
|
||||
{
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let desc = crate::proxy::define_trap_desc(
|
||||
self,
|
||||
Some((val, attributes.writable())),
|
||||
JsValue::undefined(),
|
||||
JsValue::undefined(),
|
||||
attributes,
|
||||
);
|
||||
let trap_result =
|
||||
match crate::proxy::call_trap(self, obj, self.perm_intern("defineProperty"), &[target, key, desc]) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "defineProperty")? {
|
||||
return Ok(());
|
||||
return self.proxy_define_invariant_check(
|
||||
target,
|
||||
prop_name_si,
|
||||
attributes,
|
||||
Some((val, attributes.writable())),
|
||||
None,
|
||||
);
|
||||
}
|
||||
// target 即本代理(自环)时按定义失败处理,避免无限递归。
|
||||
if std::ptr::eq(target.as_js_object_ptr(), obj as *const JsObject) {
|
||||
return Err("Proxy defineProperty trap reported false".to_string());
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.define_data_property(
|
||||
unsafe { &mut *target.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
val,
|
||||
attributes,
|
||||
);
|
||||
return Err("Proxy defineProperty trap reported false".to_string());
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.define_data_property(
|
||||
unsafe { &mut *target.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
val,
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
// TypedArray 统一数值键门:界内规范键走元素定义(界内写 buffer);
|
||||
// 规范越界键(含 detach:live 长 0)拒绝定义;非规范数值串
|
||||
@@ -1288,6 +1795,33 @@ impl Vm {
|
||||
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
|
||||
) -> Result<(), String> {
|
||||
vm_trace!("define_accessor_property: shape={} prop_si={}", obj.shape_id(), prop_name_si);
|
||||
// Proxy [[DefineOwnProperty]]:访问器形态同 `define_data_property` 的
|
||||
// trap 路由(trap 缺失落穿 target,报 false 定义失败,报 true 做不变量
|
||||
// 检查)。
|
||||
if obj.is_proxy_obj() {
|
||||
let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target;
|
||||
let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si);
|
||||
let desc = crate::proxy::define_trap_desc(self, None, get, set, attributes);
|
||||
let trap_result =
|
||||
match crate::proxy::call_trap(self, obj, self.perm_intern("defineProperty"), &[target, key, desc]) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return self.proxy_trap_call_failure(e),
|
||||
};
|
||||
if let Some(r) = trap_result {
|
||||
if crate::proxy::trap_bool_result(r, "defineProperty")? {
|
||||
return self.proxy_define_invariant_check(target, prop_name_si, attributes, None, Some((get, set)));
|
||||
}
|
||||
return Err("Proxy defineProperty trap reported false".to_string());
|
||||
}
|
||||
// SAFETY: target 由构造器校验为非空对象指针。
|
||||
return self.define_accessor_property(
|
||||
unsafe { &mut *target.as_js_object_ptr() },
|
||||
prop_name_si,
|
||||
get,
|
||||
set,
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
// TA 数值索引臂:界内 / 数字无效索引不接受 accessor(false);非规范数字
|
||||
// 串 / symbol 键落真实 accessor 属性。
|
||||
if obj.is_typed_array_obj() {
|
||||
|
||||
@@ -367,6 +367,9 @@ fn object_set_prototype_of_negative_paths() {
|
||||
|
||||
#[test]
|
||||
fn entries_values_with_interleaved_symbol_key() {
|
||||
// Object.entries / Object.values 经 EnumerableOwnProperties(.both) 含 Symbol
|
||||
// 键(字符串键在前、Symbol 键末段);entries 的 key 为符号值本身,
|
||||
// JSON.stringify 将 Symbol 值序列化为 null。
|
||||
let (_vm, result) = eval(
|
||||
"var s = Symbol(); var o = {a: 1, [s]: 2, b: 3}; [JSON.stringify(Object.entries(o)), JSON.stringify(Object.values(o))]",
|
||||
)
|
||||
@@ -374,8 +377,8 @@ fn entries_values_with_interleaved_symbol_key() {
|
||||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||
let s0 = unsafe { &*obj.get_prop_at(0).as_string_ptr() }.to_owned_string();
|
||||
let s1 = unsafe { &*obj.get_prop_at(1).as_string_ptr() }.to_owned_string();
|
||||
assert_eq!(s0, "[[\"a\",1],[\"b\",3]]");
|
||||
assert_eq!(s1, "[1,3]");
|
||||
assert_eq!(s0, "[[\"a\",1],[\"b\",3],[null,2]]");
|
||||
assert_eq!(s1, "[1,3,2]");
|
||||
}
|
||||
|
||||
// -- Object.getOwnPropertyDescriptors --
|
||||
|
||||
@@ -1893,7 +1893,7 @@ fn ta_detached_empty_enum() {
|
||||
if (ks.length !== 2 || ks[0] !== 'foo' || ks[1] !== s) return false; \
|
||||
if (Object.keys(ta).join(',') !== 'foo') return false; \
|
||||
var vs = Object.values(ta); \
|
||||
if (vs.length !== 1 || vs[0] !== 42) return false; \
|
||||
if (vs.length !== 2 || vs[0] !== 42 || vs[1] !== 5) return false; \
|
||||
var kf = []; for (var k in ta) kf.push(k); \
|
||||
return kf.length === 1 && kf[0] === 'foo' && ta.foo === 42; })()",
|
||||
)
|
||||
|
||||
@@ -190,32 +190,35 @@ fn super_call_native_constructor_still_works() {
|
||||
assert!(result.is_bool() && result.as_bool(), "super() 后实例应为 Object 实例");
|
||||
}
|
||||
|
||||
// 对象方法 super.x 写(静态成员):写经 proto 链落基(super base),receiver 为方法接收者。
|
||||
// 对象方法 super.x 写(静态成员):2026 规范 OrdinarySetWithOwnDescriptor
|
||||
// 数据分支——receiver 无同名自身属性时写落 receiver 自身(CreateDataProperty),
|
||||
// 基对象不变。
|
||||
#[test]
|
||||
fn object_method_super_put_static_member() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var base = { x: 'a' }; \
|
||||
var obj = { __proto__: base, method() { super.x = 'written'; return base.x; } }; \
|
||||
var obj = { __proto__: base, method() { super.x = 'written'; return base.x + '|' + obj.x + '|' + Object.prototype.hasOwnProperty.call(obj, 'x'); } }; \
|
||||
obj.method()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "written");
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "a|written|true");
|
||||
}
|
||||
|
||||
// 对象方法 super['x'] 写(计算成员):键运行期求值,写经 proto 链落基。
|
||||
// 对象方法 super['x'] 写(计算成员):键运行期求值,写落 receiver 自身属性,
|
||||
// 基对象不变。
|
||||
#[test]
|
||||
fn object_method_super_put_computed_member() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var base = { x: 'a' }; \
|
||||
var obj = { __proto__: base, method() { super['x'] = 'written'; return base.x; } }; \
|
||||
var obj = { __proto__: base, method() { super['x'] = 'written'; return base.x + '|' + obj.x; } }; \
|
||||
obj.method()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "written");
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "a|written");
|
||||
}
|
||||
|
||||
// 严格模式 super 写失败(基对象冻结)抛 TypeError。
|
||||
@@ -247,7 +250,8 @@ fn object_method_super_put_non_strict_frozen_silent() {
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "done");
|
||||
}
|
||||
|
||||
// 键对象 toString 副作用换 proto:GetSuperBase 先于 ToPropertyKey,写旧 proto。
|
||||
// 键对象 toString 副作用换 proto:GetSuperBase 先于 ToPropertyKey,写旧 proto 的
|
||||
// [[Set]];2026 规范数据分支写落 receiver 自身属性,新旧 proto 基均不变。
|
||||
#[test]
|
||||
fn object_method_super_put_getsuperbase_before_topropertykey() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -256,10 +260,10 @@ fn object_method_super_put_getsuperbase_before_topropertykey() {
|
||||
"var proto = { p: 1 }; var proto2 = { p: -1 }; \
|
||||
var obj = { __proto__: proto, m() { super[key] = 10; } }; \
|
||||
var key = { toString() { Object.setPrototypeOf(obj, proto2); return 'p'; } }; \
|
||||
obj.m(); proto.p + '|' + proto2.p",
|
||||
obj.m(); proto.p + '|' + proto2.p + '|' + obj.p",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "10|-1");
|
||||
assert_eq!(vm.lookup_str(result).unwrap(), "1|-1|10");
|
||||
}
|
||||
|
||||
// 复合赋值 GetSuperBase 先于 ToPropertyKey:读旧值、运算、写回均用旧 proto 基。
|
||||
|
||||
Reference in New Issue
Block a user