mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 内置属性读取触发访问器并透传原始异常值
This commit is contained in:
@@ -211,7 +211,13 @@ fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<
|
|||||||
let key_val = vm.new_string_units_owned(key.to_vec());
|
let key_val = vm.new_string_units_owned(key.to_vec());
|
||||||
match vm.call_function_sync(fn_val, obj_val, &[key_val]) {
|
match vm.call_function_sync(fn_val, obj_val, &[key_val]) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(msg) => Err(crate::error::create_type_error(vm, &msg)),
|
Err(msg) => {
|
||||||
|
// 用户抛出的原始值原样传播(toJSON 抛非 Error 值不降级为 TypeError)。
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
Err(exc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(obj_val)
|
Ok(obj_val)
|
||||||
@@ -221,6 +227,27 @@ fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 序列化族单自身属性值读(SerializeJSONProperty 步 2 的 Get):数据属性直读
|
||||||
|
/// 存储槽;访问器属性触发 getter(this = 容器对象),getter 异常传播原始
|
||||||
|
/// 抛出值。
|
||||||
|
fn read_json_property_value<H: VmHost>(
|
||||||
|
vm: &mut H, obj: &JsObject, obj_val: JsValue, si: u32, pos: u32,
|
||||||
|
) -> Result<JsValue, JsValue> {
|
||||||
|
if obj.is_accessor_meta(pos) {
|
||||||
|
match vm.ordinary_get(obj, si, obj_val) {
|
||||||
|
Ok(value) => Ok(value),
|
||||||
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
Err(exc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(obj.get_prop_at(pos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `JSON.stringify(value, replacer, space)`:将 JS 值序列化为 JSON 文本。
|
/// `JSON.stringify(value, replacer, space)`:将 JS 值序列化为 JSON 文本。
|
||||||
/// 支持 replacer 函数/属性白名单、toJSON 钩子、缩进与循环引用检测。
|
/// 支持 replacer 函数/属性白名单、toJSON 钩子、缩进与循环引用检测。
|
||||||
pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
@@ -270,7 +297,12 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
let key_val = vm.new_string("");
|
let key_val = vm.new_string("");
|
||||||
match vm.call_function_sync(replacer, holder, &[key_val, value]) {
|
match vm.call_function_sync(replacer, holder, &[key_val, value]) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
return NativeResult::Err(exc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
@@ -279,7 +311,7 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let indent_level: usize = 0;
|
let indent_level: usize = 0;
|
||||||
if jsvalue_to_json(
|
match jsvalue_to_json(
|
||||||
vm,
|
vm,
|
||||||
value,
|
value,
|
||||||
&mut visited,
|
&mut visited,
|
||||||
@@ -288,21 +320,21 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
replacer_whitelist.as_ref(),
|
replacer_whitelist.as_ref(),
|
||||||
&space,
|
&space,
|
||||||
indent_level,
|
indent_level,
|
||||||
&[],
|
) {
|
||||||
)
|
Ok(()) => NativeResult::Ok(vm.new_string_owned(output)),
|
||||||
.is_err()
|
Err(exc) => NativeResult::Err(exc),
|
||||||
{
|
}
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
|
|
||||||
};
|
|
||||||
NativeResult::Ok(vm.new_string_owned(output))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 值位序列化(SerializeValue 及对象/数组展开)。toJSON 钩子不在本入口应用——
|
||||||
|
/// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array`
|
||||||
|
/// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序
|
||||||
|
/// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn jsvalue_to_json<H: VmHost>(
|
fn jsvalue_to_json<H: VmHost>(
|
||||||
vm: &mut H, val: JsValue, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
vm: &mut H, val: JsValue, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
||||||
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize, key: &[u16],
|
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), JsValue> {
|
||||||
let val = call_to_json(vm, val, key).map_err(|_| ())?;
|
|
||||||
if val.is_null() {
|
if val.is_null() {
|
||||||
out.push_str("null");
|
out.push_str("null");
|
||||||
} else if val.is_undefined() {
|
} else if val.is_undefined() {
|
||||||
@@ -329,7 +361,7 @@ fn jsvalue_to_json<H: VmHost>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !visited.insert(obj_ptr as *const JsObject) {
|
if !visited.insert(obj_ptr as *const JsObject) {
|
||||||
return Err(());
|
return Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
@@ -384,13 +416,14 @@ fn stringify_string_units(units: &[u16], out: &mut String) {
|
|||||||
fn stringify_object<H: VmHost>(
|
fn stringify_object<H: VmHost>(
|
||||||
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
||||||
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize,
|
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), JsValue> {
|
||||||
let has_space = !space.is_empty();
|
let has_space = !space.is_empty();
|
||||||
out.push('{');
|
out.push('{');
|
||||||
|
|
||||||
let keys = walk_own_keys(vm, obj);
|
let keys = walk_own_keys(vm, obj);
|
||||||
// 仅序列化可枚举自身属性(规范 EnumerableOwnPropertyNames)。
|
// 仅序列化可枚举自身属性(规范 EnumerableOwnPropertyNames)。数组 replacer
|
||||||
let entries: Vec<(Vec<u16>, String, u32)> = keys
|
// 白名单判定在 Get 之前(SerializeJSONObject 步 4a),非白名单键不触发 getter。
|
||||||
|
let entries: Vec<(Vec<u16>, String, u32, u32)> = keys
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(_si, pos)| {
|
.filter(|(_si, pos)| {
|
||||||
obj.prop_meta_at(*pos)
|
obj.prop_meta_at(*pos)
|
||||||
@@ -406,26 +439,33 @@ fn stringify_object<H: VmHost>(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((units, name, pos))
|
Some((units, name, pos, si))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for (units, _name, pos) in entries {
|
for (units, _name, pos, si) in entries {
|
||||||
let val = obj.get_prop_at(pos);
|
// 规范序 Get → toJSON → replacer(SerializeJSONProperty 步 1、2a、2b)。
|
||||||
|
let val = read_json_property_value(vm, obj, obj_val, si, pos)?;
|
||||||
|
let val = call_to_json(vm, val, &units)?;
|
||||||
|
|
||||||
// replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。
|
// replacer 函数回调。
|
||||||
let val = if let Some(replacer) = replacer_fn {
|
let val = if let Some(replacer) = replacer_fn {
|
||||||
let key_val = vm.new_string_units_owned(units.clone());
|
let key_val = vm.new_string_units_owned(units.clone());
|
||||||
let holder = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
|
||||||
match vm.call_function_sync(replacer, holder, &[key_val, val]) {
|
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
if v.is_undefined() {
|
if v.is_undefined() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
Err(_) => return Err(()),
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
return Err(exc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val
|
val
|
||||||
@@ -458,7 +498,7 @@ fn stringify_object<H: VmHost>(
|
|||||||
out.push(':');
|
out.push(':');
|
||||||
}
|
}
|
||||||
|
|
||||||
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1, &units)?;
|
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_space && !first {
|
if has_space && !first {
|
||||||
@@ -475,10 +515,11 @@ fn stringify_object<H: VmHost>(
|
|||||||
fn stringify_array<H: VmHost>(
|
fn stringify_array<H: VmHost>(
|
||||||
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option<JsValue>,
|
||||||
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize,
|
replacer_whitelist: Option<&HashSet<String>>, space: &str, indent_level: usize,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), JsValue> {
|
||||||
let has_space = !space.is_empty();
|
let has_space = !space.is_empty();
|
||||||
out.push('[');
|
out.push('[');
|
||||||
|
|
||||||
|
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||||||
let len = obj.prop_count() as usize;
|
let len = obj.prop_count() as usize;
|
||||||
for i in 0..len {
|
for i in 0..len {
|
||||||
if i > 0 && !has_space {
|
if i > 0 && !has_space {
|
||||||
@@ -494,16 +535,17 @@ fn stringify_array<H: VmHost>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = obj.get_prop_at(i);
|
|
||||||
|
|
||||||
let index_str = i.to_string();
|
let index_str = i.to_string();
|
||||||
let index_units: Vec<u16> = index_str.encode_utf16().collect();
|
let index_units: Vec<u16> = index_str.encode_utf16().collect();
|
||||||
|
|
||||||
// replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。
|
// 规范序 Get → toJSON → replacer(数组元素位同款,getter 的 this = 数组自身)。
|
||||||
|
let val = read_json_property_value(vm, obj, obj_val, make_int_key(i as u32), i as u32)?;
|
||||||
|
let val = call_to_json(vm, val, &index_units)?;
|
||||||
|
|
||||||
|
// replacer 函数回调。
|
||||||
let val = if let Some(replacer) = replacer_fn {
|
let val = if let Some(replacer) = replacer_fn {
|
||||||
let key_val = vm.new_string(&index_str);
|
let key_val = vm.new_string(&index_str);
|
||||||
let holder = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
|
||||||
match vm.call_function_sync(replacer, holder, &[key_val, val]) {
|
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
if v.is_undefined() {
|
if v.is_undefined() {
|
||||||
out.push_str("null");
|
out.push_str("null");
|
||||||
@@ -511,7 +553,12 @@ fn stringify_array<H: VmHost>(
|
|||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
Err(_) => return Err(()),
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
return Err(exc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val
|
val
|
||||||
@@ -524,17 +571,7 @@ fn stringify_array<H: VmHost>(
|
|||||||
if is_function || val.is_undefined() {
|
if is_function || val.is_undefined() {
|
||||||
out.push_str("null");
|
out.push_str("null");
|
||||||
} else {
|
} else {
|
||||||
jsvalue_to_json(
|
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
|
||||||
vm,
|
|
||||||
val,
|
|
||||||
visited,
|
|
||||||
out,
|
|
||||||
replacer_fn,
|
|
||||||
replacer_whitelist,
|
|
||||||
space,
|
|
||||||
indent_level + 1,
|
|
||||||
&index_units,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -488,6 +488,31 @@ fn namespace_export_get(obj: &JsObject, key_si: u32) -> Result<Option<JsValue>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// values/entries 族单自身属性值读(EnumerableOwnProperties 的 Get):模块命名空间
|
||||||
|
/// 导出走活值查询(未初始化抛 ReferenceError);访问器属性触发 getter(this = 对象
|
||||||
|
/// 自身),异常传播原始抛出值;数据属性直读存储槽。
|
||||||
|
fn own_property_value<H: VmHost>(
|
||||||
|
vm: &mut H, obj: &JsObject, obj_val: JsValue, si: u32, offset: u32,
|
||||||
|
) -> Result<JsValue, JsValue> {
|
||||||
|
match namespace_export_get(obj, si) {
|
||||||
|
Ok(Some(value)) => return Ok(value),
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(msg) => return Err(crate::error::create_reference_error(vm, msg)),
|
||||||
|
}
|
||||||
|
if obj.is_accessor_meta(offset) {
|
||||||
|
match vm.ordinary_get(obj, si, obj_val) {
|
||||||
|
Ok(value) => return Ok(value),
|
||||||
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
return Err(exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(obj.get_prop_at(offset))
|
||||||
|
}
|
||||||
|
|
||||||
/// `Object.keys(obj)`:返回可枚举自身属性的字符串名数组。
|
/// `Object.keys(obj)`:返回可枚举自身属性的字符串名数组。
|
||||||
pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
let obj_ptr = match require_obj_arg(vm, args, "keys") {
|
let obj_ptr = match require_obj_arg(vm, args, "keys") {
|
||||||
@@ -1570,13 +1595,13 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
n,
|
n,
|
||||||
vm.epoch().bump(),
|
vm.epoch().bump(),
|
||||||
));
|
));
|
||||||
|
let obj_val = JsValue::from_js_object(obj_ptr);
|
||||||
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
||||||
let key_val = key_si_to_js_value(vm, *si);
|
let key_val = key_si_to_js_value(vm, *si);
|
||||||
// EnumerableOwnProperties 的 "key+value":值经条目活值查询,未初始化抛错。
|
// EnumerableOwnProperties 的 "key+value":accessor 触发 getter,异常传播原值。
|
||||||
let val = match namespace_export_get(obj, *si) {
|
let val = match own_property_value(vm, obj, obj_val, *si, *offset) {
|
||||||
Ok(Some(value)) => value,
|
Ok(value) => value,
|
||||||
Ok(None) => obj.get_prop_at(*offset),
|
Err(exc) => return NativeResult::Err(exc),
|
||||||
Err(msg) => return NativeResult::Err(crate::error::create_reference_error(vm, msg)),
|
|
||||||
};
|
};
|
||||||
let pair = vm.alloc_object(JsObject::new_array(
|
let pair = vm.alloc_object(JsObject::new_array(
|
||||||
EMPTY_SHAPE_ID,
|
EMPTY_SHAPE_ID,
|
||||||
@@ -1762,12 +1787,12 @@ pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
n,
|
n,
|
||||||
vm.epoch().bump(),
|
vm.epoch().bump(),
|
||||||
));
|
));
|
||||||
for (i, (_si, offset)) in owned_keys.iter().enumerate() {
|
let obj_val = JsValue::from_js_object(obj_ptr);
|
||||||
// EnumerableOwnProperties 的 "value":值经条目活值查询,未初始化抛错。
|
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
||||||
let val = match namespace_export_get(obj, *_si) {
|
// EnumerableOwnProperties 的 "value":accessor 触发 getter,异常传播原值。
|
||||||
Ok(Some(value)) => value,
|
let val = match own_property_value(vm, obj, obj_val, *si, *offset) {
|
||||||
Ok(None) => obj.get_prop_at(*offset),
|
Ok(value) => value,
|
||||||
Err(msg) => return NativeResult::Err(crate::error::create_reference_error(vm, msg)),
|
Err(exc) => return NativeResult::Err(exc),
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
(*arr).set_prop_at(i, val);
|
(*arr).set_prop_at(i, val);
|
||||||
|
|||||||
@@ -887,3 +887,56 @@ fn test_species_ctor_null_primitive_throws_four_mounts() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(out, "16");
|
assert_eq!(out, "16");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 引擎钉:reverse arraylike 单端缺失——(N,S) 臂 Delete 先于 Set,缺端不物化。
|
||||||
|
#[test]
|
||||||
|
fn test_reverse_arraylike_single_side_missing() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {0:'a', length:2}; Array.prototype.reverse.call(o); \
|
||||||
|
return (0 in o) + '|' + (1 in o) + '|' + o[1] + '|' + o.length; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "false|true|a|2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引擎钉:reverse arraylike 洞位保持——洞不物化、length 不变。
|
||||||
|
#[test]
|
||||||
|
fn test_reverse_arraylike_hole_kept() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {0:'a', 2:'b', length:3}; Array.prototype.reverse.call(o); \
|
||||||
|
return (0 in o) + '|' + (1 in o) + '|' + (2 in o) + '|' + o[0] + '|' + o[2] + '|' + o.length; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "true|false|true|b|a|3");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引擎钉:reverse arraylike accessor length——getter 触发一次,抛错原值传播。
|
||||||
|
#[test]
|
||||||
|
fn test_reverse_arraylike_length_accessor_throws() {
|
||||||
|
let result = eval_value(
|
||||||
|
"(() => { const o = {0:'x', 1:'y', length:2}; \
|
||||||
|
Object.defineProperty(o,'length',{get(){throw 99;},set(v){},enumerable:true,configurable:true}); \
|
||||||
|
Array.prototype.reverse.call(o); return 'no-throw'; })()",
|
||||||
|
);
|
||||||
|
match result {
|
||||||
|
Err(e) => assert!(e.contains("99"), "error should carry thrown 99, got: {}", e),
|
||||||
|
Ok(_) => panic!("expected accessor length throw to propagate"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引擎钉:reverse arraylike length 值形态——ToLength 取整定界、原值不写回。
|
||||||
|
#[test]
|
||||||
|
fn test_reverse_arraylike_length_not_written_back() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {0:'x', 1:'y', length:'2'}; Array.prototype.reverse.call(o); \
|
||||||
|
return o.length + '|' + o[0] + '|' + o[1]; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "2|y|x");
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {0:'x', 1:'y', 2:'z', length:2.7}; Array.prototype.reverse.call(o); \
|
||||||
|
return o.length + '|' + o[0] + '|' + o[1]; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "2.7|y|x");
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,3 +141,189 @@ fn reviver_no_reviver_works() {
|
|||||||
let (_vm, result) = eval(r#"JSON.parse('{"a":1}')"#).unwrap();
|
let (_vm, result) = eval(r#"JSON.parse('{"a":1}')"#).unwrap();
|
||||||
assert!(result.is_object());
|
assert!(result.is_object());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- accessor 自身属性值读(SerializeJSONProperty 步 2 Get)---
|
||||||
|
|
||||||
|
// reverse arraylike + length accessor:getter 被触发,length 键保留在输出。
|
||||||
|
#[test]
|
||||||
|
fn stringify_reverse_arraylike_length_accessor() {
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"(() => { const o = {0:'x',1:'y',length:2}; \
|
||||||
|
Object.defineProperty(o,'length',{get(){return 2;},set(v){},enumerable:true,configurable:true}); \
|
||||||
|
Array.prototype.reverse.call(o); return JSON.stringify(o); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{"0":"y","1":"x","length":2}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对象属性访问器:字面量 getter 与 defineProperty 重定义 accessor 两形态。
|
||||||
|
#[test]
|
||||||
|
fn stringify_object_accessor_getter() {
|
||||||
|
let (_vm, result) = eval(r#"JSON.stringify({get x(){return 5;}, y:1})"#).unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{"x":5,"y":1}"#);
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"(() => { const o = {x:1}; \
|
||||||
|
Object.defineProperty(o,'x',{get(){return 2;}}); return JSON.stringify(o); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{"x":2}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getter 的 this 绑定为属性所在对象。
|
||||||
|
#[test]
|
||||||
|
fn stringify_accessor_this_binding() {
|
||||||
|
let (_vm, result) = eval(r#"JSON.stringify({v:1, get p(){return this.v+10;}})"#).unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{"v":1,"p":11}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组 replacer 白名单判定先于 Get:非白名单键的 getter 不触发。
|
||||||
|
#[test]
|
||||||
|
fn stringify_whitelist_before_accessor_get() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { let fired = false; const o = {a:1, get b(){fired=true;return 2;}}; \
|
||||||
|
return JSON.stringify(o,['a']) + '|' + fired; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"{"a":1}|false"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组元素访问器:元素位触发 getter,值为 getter 返回值。
|
||||||
|
#[test]
|
||||||
|
fn stringify_array_element_accessor() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const a = [1,2]; \
|
||||||
|
Object.defineProperty(a,0,{get(){return 9;},configurable:true}); \
|
||||||
|
Object.defineProperty(a,1,{get(){return 2;},configurable:true}); \
|
||||||
|
return JSON.stringify(a); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "[9,2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// getter 返回值形态:function/undefined 省略,带 toJSON 的对象走钩子,setter-only 省略。
|
||||||
|
#[test]
|
||||||
|
fn stringify_accessor_return_shapes() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {}; \
|
||||||
|
Object.defineProperty(o,'f',{get(){ return function g(){return 7;}; },enumerable:true,configurable:true}); \
|
||||||
|
Object.defineProperty(o,'u',{get(){ return undefined; },enumerable:true,configurable:true}); \
|
||||||
|
Object.defineProperty(o,'t',{get(){ return {toJSON(){return 7;}}; },enumerable:true,configurable:true}); \
|
||||||
|
Object.defineProperty(o,'s',{set(v){},enumerable:true,configurable:true}); \
|
||||||
|
return JSON.stringify(o); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"{"t":7}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// space 缩进 + 访问器:getter 值正常入输出(判别 p 键存在;对象开括号换行
|
||||||
|
// 位置是 space 格式面 pre-existing 偏差,不属本钉面)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_space_with_accessor() {
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"(() => { const o = {}; \
|
||||||
|
Object.defineProperty(o,'p',{get(){return [1];},enumerable:true,configurable:true}); \
|
||||||
|
return JSON.stringify({o},null,2); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let s = stringify_val(&result);
|
||||||
|
assert!(s.contains(r#""p": ["#), "getter value missing from indented output: {}", s);
|
||||||
|
assert!(s.contains("1"), "getter value absent: {}", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getter 抛非 Error 原始值:原值传播(捕获侧见 number 42,非 TypeError 对象)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_accessor_throws_original_value() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { try { JSON.stringify({get x(){throw 42;}, y:1}); } \
|
||||||
|
catch (e) { return typeof e + '|' + e; } })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "number|42");
|
||||||
|
}
|
||||||
|
|
||||||
|
// toJSON 抛非 Error 原始值:原值传播。
|
||||||
|
#[test]
|
||||||
|
fn stringify_tojson_throws_original_value() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { try { JSON.stringify({toJSON(){throw 42;}}); } \
|
||||||
|
catch (e) { return typeof e + '|' + e; } })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "number|42");
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacer 抛非 Error 原始值:原值传播。
|
||||||
|
#[test]
|
||||||
|
fn stringify_replacer_throws_original_value() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { try { JSON.stringify({a:1}, function(k,v){ if(k==='a') throw 'boom'; return v; }); } \
|
||||||
|
catch (e) { return typeof e + '|' + e; } })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, "string|boom");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 嵌套序:toJSON 先于 replacer 应用,replacer 见到 toJSON 结果。
|
||||||
|
#[test]
|
||||||
|
fn stringify_nested_tojson_before_replacer() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { let seen; const inner = {toJSON(){return 'T';}}; \
|
||||||
|
const s = JSON.stringify({a:inner}, function(k,v){ if(k==='a') seen = String(v); return v; }); \
|
||||||
|
return seen + '|' + s; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"T|{"a":"T"}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶层 toJSON 结果自身带 toJSON:只应用一次。
|
||||||
|
#[test]
|
||||||
|
fn stringify_top_level_tojson_once() {
|
||||||
|
let (_vm, result) = eval(r#"JSON.stringify({toJSON(){return {toJSON(){return 'inner';}};}})"#).unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 访问器返回自身成环:getter 触发后环检抛 TypeError。
|
||||||
|
#[test]
|
||||||
|
fn stringify_accessor_self_cycle() {
|
||||||
|
let result = eval(
|
||||||
|
"(() => { const o = {n:1}; \
|
||||||
|
Object.defineProperty(o,'self',{get(){return o;},enumerable:true,configurable:true}); \
|
||||||
|
return JSON.stringify(o); })()",
|
||||||
|
);
|
||||||
|
match result {
|
||||||
|
Err(e) => assert!(e.to_lowercase().contains("circular"), "expected circular error, got: {}", e),
|
||||||
|
Ok(_) => panic!("expected cycle error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不可枚举访问器:省略(守卫,防修后翻红)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_non_enumerable_accessor_skipped() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {v:2}; \
|
||||||
|
Object.defineProperty(o,'h',{get(){return 99;},enumerable:false,configurable:true}); \
|
||||||
|
return JSON.stringify(o); })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"{"v":2}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 嵌套 toJSON 结果为对象:单应用下正常展开(守卫)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_nested_tojson_result_object() {
|
||||||
|
let (_vm, result) = eval(r#"JSON.stringify({a:{toJSON(){return {};}}})"#).unwrap();
|
||||||
|
assert_eq!(stringify_val(&result), r#"{"a":{}}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组附加命名属性:序列化省略(守卫)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_array_ignores_named_props() {
|
||||||
|
let out = eval_str("(() => { const a = [1,2]; a.x = 'hello'; return JSON.stringify(a); })()").unwrap();
|
||||||
|
assert_eq!(out, "[1,2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_str(source: &str) -> Result<String, String> {
|
||||||
|
let (vm, result) = eval(source)?;
|
||||||
|
vm.lookup_str(result)
|
||||||
|
.ok_or_else(|| "completion value is not a string".to_string())
|
||||||
|
}
|
||||||
|
|||||||
@@ -223,6 +223,61 @@ fn test_values_returns_array() {
|
|||||||
assert!(result.is_int() || result.is_double());
|
assert!(result.is_int() || result.is_double());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Object.values 自身 accessor:getter 被触发,值为 getter 返回值。
|
||||||
|
#[test]
|
||||||
|
fn test_values_triggers_accessor_getter() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {}; \
|
||||||
|
Object.defineProperty(o,'a',{get(){return 1;},enumerable:true,configurable:true}); \
|
||||||
|
o.b = 2; return Object.values(o).join(','); })()",
|
||||||
|
);
|
||||||
|
assert_eq!(out, "1,2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.entries 自身 accessor:[key, getter值] 对。
|
||||||
|
#[test]
|
||||||
|
fn test_entries_triggers_accessor_getter() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const o = {}; \
|
||||||
|
Object.defineProperty(o,'a',{get(){return 1;},enumerable:true,configurable:true}); \
|
||||||
|
o.b = 2; return JSON.stringify(Object.entries(o)); })()",
|
||||||
|
);
|
||||||
|
assert_eq!(out, r#"[["a",1],["b",2]]"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组元素位 accessor:values 对元素触发 getter。
|
||||||
|
#[test]
|
||||||
|
fn test_values_array_element_accessor() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { const a = [3,4]; \
|
||||||
|
Object.defineProperty(a,2,{get(){return 9;},enumerable:true,configurable:true}); \
|
||||||
|
return Object.values(a).join(','); })()",
|
||||||
|
);
|
||||||
|
assert_eq!(out, "3,4,9");
|
||||||
|
}
|
||||||
|
|
||||||
|
// getter 的 this 绑定为属性所在对象。
|
||||||
|
#[test]
|
||||||
|
fn test_values_accessor_this_binding() {
|
||||||
|
let out = eval_str("(() => Object.values({v:1, get p(){return this.v;}}).join(','))()");
|
||||||
|
assert_eq!(out, "1,1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// values/entries 的 getter 抛非 Error 原始值:原值传播(捕获侧见原类型与值)。
|
||||||
|
#[test]
|
||||||
|
fn test_values_entries_accessor_throws_original_value() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { try { Object.values({get a(){throw 'boom';}, b:1}); } \
|
||||||
|
catch (e) { return typeof e + '|' + e; } })()",
|
||||||
|
);
|
||||||
|
assert_eq!(out, "string|boom");
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { try { Object.entries({get a(){throw 42;}, b:1}); } \
|
||||||
|
catch (e) { return typeof e + '|' + e; } })()",
|
||||||
|
);
|
||||||
|
assert_eq!(out, "number|42");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_own_property_descriptor_no_args_type_error() {
|
fn test_get_own_property_descriptor_no_args_type_error() {
|
||||||
assert!(eval("Object.getOwnPropertyDescriptor()").is_err());
|
assert!(eval("Object.getOwnPropertyDescriptor()").is_err());
|
||||||
|
|||||||
Reference in New Issue
Block a user