mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 04:01:11 +08:00
fix: JSON.stringify BigInt 值位抛 TypeError 并接 toJSON 钩子
This commit is contained in:
@@ -195,13 +195,20 @@ fn key_si_to_units<H: VmHost>(vm: &H, si: u32) -> Vec<u16> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<JsValue, JsValue> {
|
fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<JsValue, JsValue> {
|
||||||
if !obj_val.is_object() {
|
// SerializeJSONProperty 步 2:对象与 BigInt 原语均查 toJSON(Get 对原语
|
||||||
|
// 自动装箱),BigInt 的查表对象取 %BigIntPrototype%。
|
||||||
|
if !obj_val.is_object() && !obj_val.is_bigint() {
|
||||||
return Ok(obj_val);
|
return Ok(obj_val);
|
||||||
}
|
}
|
||||||
let obj_ptr = obj_val.as_js_object_ptr();
|
let obj_ptr = if obj_val.is_object() {
|
||||||
if obj_ptr.is_null() {
|
let p = obj_val.as_js_object_ptr();
|
||||||
|
if p.is_null() {
|
||||||
return Ok(obj_val);
|
return Ok(obj_val);
|
||||||
}
|
}
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
vm.session().builtin_world().bigint_proto.as_ptr() as *mut JsObject
|
||||||
|
};
|
||||||
let tojson_si = vm.kernel_core().perm_interner().intern("toJSON").0;
|
let tojson_si = vm.kernel_core().perm_interner().intern("toJSON").0;
|
||||||
let resolved = vm.resolve_property(unsafe { &*obj_ptr }, tojson_si);
|
let resolved = vm.resolve_property(unsafe { &*obj_ptr }, tojson_si);
|
||||||
match resolved {
|
match resolved {
|
||||||
@@ -349,6 +356,9 @@ fn jsvalue_to_json<H: VmHost>(
|
|||||||
} else {
|
} else {
|
||||||
oxide_runtime_api::write_number_into(n, out);
|
oxide_runtime_api::write_number_into(n, out);
|
||||||
}
|
}
|
||||||
|
} else if val.is_bigint() {
|
||||||
|
// SerializeJSONProperty 步 10:BigInt 无 JSON 文本形态,抛 TypeError。
|
||||||
|
return Err(crate::error::create_type_error(vm, "Do not know how to serialize a BigInt"));
|
||||||
} else if val.is_string() {
|
} else if val.is_string() {
|
||||||
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
|
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
|
||||||
let units = unsafe { (*val.as_string_ptr()).units() };
|
let units = unsafe { (*val.as_string_ptr()).units() };
|
||||||
@@ -365,7 +375,13 @@ fn jsvalue_to_json<H: VmHost>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
if obj.is_array() {
|
// 步 4d:装箱 BigInt 无条件解包 [[BigIntData]],后续步 10 抛 TypeError。
|
||||||
|
if obj.boxed_value().is_bigint() {
|
||||||
|
return Err(crate::error::create_type_error(vm, "Do not know how to serialize a BigInt"));
|
||||||
|
}
|
||||||
|
if obj.is_typed_array_obj() {
|
||||||
|
stringify_typed_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
||||||
|
} else if obj.is_array() {
|
||||||
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
||||||
} else {
|
} else {
|
||||||
stringify_object(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
stringify_object(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
|
||||||
@@ -584,3 +600,89 @@ fn stringify_array<H: VmHost>(
|
|||||||
out.push(']');
|
out.push(']');
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链),
|
||||||
|
/// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn stringify_typed_array<H: VmHost>(
|
||||||
|
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,
|
||||||
|
) -> Result<(), JsValue> {
|
||||||
|
let has_space = !space.is_empty();
|
||||||
|
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||||||
|
let view = crate::typed_array::get_typed_array_data(vm, this_val)?;
|
||||||
|
out.push('{');
|
||||||
|
let mut first = true;
|
||||||
|
for i in 0..view.length {
|
||||||
|
let index_str = i.to_string();
|
||||||
|
// 白名单判定先于 Get(SerializeJSONObject 步 4a),非白名单键不读元素。
|
||||||
|
if let Some(whitelist) = replacer_whitelist {
|
||||||
|
if !whitelist.contains(&index_str) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let index_units: Vec<u16> = index_str.encode_utf16().collect();
|
||||||
|
let val = crate::typed_array::typed_array_element_get(vm, obj, i as u32)
|
||||||
|
.map_err(|msg| crate::error::create_type_error(vm, &msg))?;
|
||||||
|
let val = call_to_json(vm, val, &index_units)?;
|
||||||
|
|
||||||
|
// replacer 函数回调。
|
||||||
|
let val = if let Some(replacer) = replacer_fn {
|
||||||
|
let key_val = vm.new_string(&index_str);
|
||||||
|
match vm.call_function_sync(replacer, this_val, &[key_val, val]) {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.is_undefined() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
let exc = vm
|
||||||
|
.take_uncaught_value()
|
||||||
|
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||||
|
return Err(exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_function = val.is_object() && {
|
||||||
|
let ptr = val.as_js_object_ptr();
|
||||||
|
!ptr.is_null() && unsafe { (*ptr).is_function() }
|
||||||
|
};
|
||||||
|
if val.is_undefined() || is_function {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !first && !has_space {
|
||||||
|
out.push(',');
|
||||||
|
} else if !first {
|
||||||
|
out.push_str(",\n");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if has_space {
|
||||||
|
for _ in 0..indent_level + 1 {
|
||||||
|
out.push_str(space);
|
||||||
|
}
|
||||||
|
stringify_string_units(&index_units, out);
|
||||||
|
out.push(':');
|
||||||
|
out.push(' ');
|
||||||
|
} else {
|
||||||
|
stringify_string_units(&index_units, out);
|
||||||
|
out.push(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_space && !first {
|
||||||
|
out.push('\n');
|
||||||
|
for _ in 0..indent_level {
|
||||||
|
out.push_str(space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push('}');
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -322,6 +322,113 @@ fn stringify_array_ignores_named_props() {
|
|||||||
assert_eq!(out, "[1,2]");
|
assert_eq!(out, "[1,2]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- BigInt 值位(SerializeJSONProperty 步 10 抛 TypeError;步 2 查 toJSON)---
|
||||||
|
|
||||||
|
fn assert_bigint_serialize_err(source: &str) {
|
||||||
|
let result = eval(source);
|
||||||
|
match result {
|
||||||
|
Err(e) => {
|
||||||
|
assert!(
|
||||||
|
e.to_lowercase().contains("do not know how to serialize a bigint"),
|
||||||
|
"expected BigInt serialize TypeError, got: {}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(_) => panic!("expected BigInt serialize TypeError, got ok: {}", source),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶层原语四值位:1n / -1n / 0n / -0n 恒抛,无文本产出。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_top_level_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(1n)");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(-1n)");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(0n)");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(-0n)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大位宽 BigInt 同样抛错(不产十进制文本)。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_wide_bits_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(2n ** 32n + 1n)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 嵌套对象属性与数组元素位(含深嵌套、稀疏)均经值位臂抛错。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_nested_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:1n})");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify([1n,2n,[3n]])");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:{b:1n}})");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify([,1n])");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空格缩进不改变抛错路径。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_space_indent_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:1n}, null, 2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacer 白名单命中键后仍走值位抛错。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_whitelist_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:1n}, ['a'])");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 装箱 BigInt(步 4d 无条件解包 [[BigIntData]])与裸值同抛。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_boxed_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(Object(0n))");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:Object(1n)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigInt64Array 元素位经对象臂读值后在值位臂抛错。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint64array_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(new BigInt64Array([1n,2n]))");
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacer 透传不改变值位;replacer 结果再入值位臂同样抛错。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_replacer_throws() {
|
||||||
|
assert_bigint_serialize_err("JSON.stringify(1n, (k,v) => v)");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:1n}, (k,v) => v)");
|
||||||
|
assert_bigint_serialize_err("JSON.stringify({a:1}, (k,v) => 2n)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigInt.prototype 挂数据属性 toJSON:钩子被触发,返回串值正常序列化。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_tojson_hook_top_level() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { BigInt.prototype.toJSON = function(){ return this.toString(); }; \
|
||||||
|
const s = JSON.stringify(0n); \
|
||||||
|
delete BigInt.prototype.toJSON; \
|
||||||
|
return s; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#""0""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// toJSON 钩子嵌套对象属性位:receiver 为 BigInt 原语,this.toString() 生效。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_tojson_hook_nested() {
|
||||||
|
let out = eval_str(
|
||||||
|
"(() => { BigInt.prototype.toJSON = function(){ return this.toString(); }; \
|
||||||
|
const s = JSON.stringify({a:1n}); \
|
||||||
|
delete BigInt.prototype.toJSON; \
|
||||||
|
return s; })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"{"a":"1"}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 守卫:replacer 先于值位转换 BigInt → number,不抛错。
|
||||||
|
#[test]
|
||||||
|
fn stringify_bigint_replacer_transform_guard() {
|
||||||
|
let out = eval_str("JSON.stringify({b:1n}, function(k,v){ if(typeof v==='bigint') return Number(v); return v; })")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(out, r#"{"b":1}"#);
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_str(source: &str) -> Result<String, String> {
|
fn eval_str(source: &str) -> Result<String, String> {
|
||||||
let (vm, result) = eval(source)?;
|
let (vm, result) = eval(source)?;
|
||||||
vm.lookup_str(result)
|
vm.lookup_str(result)
|
||||||
|
|||||||
Reference in New Issue
Block a user