Files
OxideJS-New/crates/oxide_builtins/src/json.rs
T

550 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::HashSet;
use std::fmt::Write;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{int_key_value, is_int_key, make_int_key};
use oxide_types::value::JsValue;
use crate::object::walk_own_keys;
use oxide_runtime_api::{NativeResult, VmHost};
/// `JSON.parse(text, reviver)`:解析 JSON 文本为 JS 值(经 serde_json)。
/// 提供 reviver 时以后序遍历逐属性调用 reviver 重建值。
pub fn json_parse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_syntax_error(vm, "JSON.parse requires 1 argument"));
}
let val = vm.reg(args[1]);
if !val.is_string() {
return NativeResult::Err(crate::error::create_syntax_error(vm, "JSON.parse: argument is not a string"));
}
let text = {
// SAFETY: val 已确认是字符串值。
unsafe { (*val.as_string_ptr()).to_owned_string() }
};
let parsed: serde_json::Value = match serde_json::from_str(&text) {
Ok(v) => v,
Err(e) => return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("{}", e))),
};
let mut result = value_to_jsvalue(vm, &parsed);
// reviver 遍历以 holder 包装对象为根:解析得到的根值存于空串键槽,
// 后序遍历自该槽展开,重建完成后以槽内最终值作为返回值。
if args.len() > 2 {
let reviver_val = vm.reg(args[2]);
if reviver_val.is_object() {
let rptr = reviver_val.as_js_object_ptr();
if !rptr.is_null() && unsafe { (*rptr).is_function() } {
let empty_si = vm.kernel_core().perm_interner().intern("").0;
let holder = create_wrapper(vm, result);
let holder_ptr = holder.as_js_object_ptr();
match walk_reviver(vm, holder_ptr, empty_si, reviver_val) {
Ok(()) => {
let holder_obj = unsafe { &*holder_ptr };
let final_val = holder_obj.get_prop_at(0u32);
result = if final_val.is_undefined() { JsValue::undefined() } else { final_val };
}
Err(e) => return NativeResult::Err(e),
}
}
}
}
NativeResult::Ok(result)
}
fn walk_reviver<H: VmHost>(
vm: &mut H, holder_ptr: *mut JsObject, key_si: u32, reviver: JsValue,
) -> Result<(), JsValue> {
let holder = unsafe { &*holder_ptr };
let slot = vm.get_own_property_slot(holder, key_si);
let pos = match slot {
Some(p) => p,
None => return Ok(()),
};
let mut val = holder.get_prop_at(pos);
// 后序遍历:先递归处理子节点。
if val.is_object() {
let obj_ptr = val.as_js_object_ptr();
if !obj_ptr.is_null() {
let obj = unsafe { &*obj_ptr };
if obj.is_array() {
let len = obj.prop_count() as usize;
for i in 0..len {
let child_si = make_int_key(i as u32);
walk_reviver(vm, obj_ptr, child_si, reviver)?;
}
// 子节点可能已改写父级,重新读取当前值。
val = holder.get_prop_at(pos);
} else {
let keys = walk_own_keys(vm, obj);
for (child_si, _child_pos) in keys {
walk_reviver(vm, obj_ptr, child_si, reviver)?;
}
// 子节点可能已改写父级,重新读取当前值。
val = holder.get_prop_at(pos);
}
}
}
// 对当前值调用 reviver,用返回值覆盖属性槽。
let key_val = crate::object::key_si_to_js_value(vm, key_si);
let holder_val = JsValue::from_js_object(holder_ptr);
match vm.call_function_sync(reviver, holder_val, &[key_val, val]) {
Ok(new_val) => {
unsafe {
(*holder_ptr).set_prop_at(pos, new_val);
}
Ok(())
}
Err(msg) => Err(crate::error::create_type_error(vm, &msg)),
}
}
fn value_to_jsvalue<H: VmHost>(vm: &mut H, val: &serde_json::Value) -> JsValue {
match val {
serde_json::Value::Null => JsValue::null(),
serde_json::Value::Bool(b) => JsValue::bool(*b),
serde_json::Value::Number(n) => {
if let Some(f) = n.as_f64() {
JsValue::float(f)
} else {
JsValue::float(0.0)
}
}
serde_json::Value::String(s) => vm.new_string(s),
serde_json::Value::Array(arr) => {
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = arr.len();
let array_obj = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, v) in arr.iter().enumerate() {
let jsv = value_to_jsvalue(vm, v);
unsafe {
(*array_obj).set_prop_at(i, jsv);
}
}
JsValue::from_js_object(array_obj)
}
serde_json::Value::Object(map) => {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
for (key, val) in map {
// 键经字符串规范化:规范数字串("0"/"5")映射整数键,与属性访问统一。
let si = vm.string_key_si(key);
let jsv = value_to_jsvalue(vm, val);
let new_shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(new_shape);
obj.ensure_hash_props().push(jsv);
}
let obj_ptr = vm.alloc_object(obj);
JsValue::from_js_object(obj_ptr)
}
}
}
fn create_wrapper<H: VmHost>(vm: &mut H, value: JsValue) -> JsValue {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let empty_si = vm.kernel_core().perm_interner().intern("").0;
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
let new_shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), empty_si);
obj.set_shape_id(new_shape);
obj.ensure_hash_props().push(value);
let obj_ptr = vm.alloc_object(obj);
JsValue::from_js_object(obj_ptr)
}
fn process_space(val: JsValue) -> String {
if val.is_int() || val.is_double() {
let n = oxide_runtime_api::to_integer_or_infinity(val);
if n.is_nan() || n.is_infinite() || n <= 0.0 {
return String::new();
}
let clamped = (n as usize).min(10);
" ".repeat(clamped)
} else if val.is_string() {
let s = unsafe { (*val.as_string_ptr()).to_owned_string() };
s.chars().take(10).collect()
} else {
let s = oxide_runtime_api::to_string(val);
s.chars().take(10).collect()
}
}
/// 键 si 物化为单元序列:整数键 → ASCII 数字串,字符串键 → 码表键经 `decode_key`
/// 还原(含孤立 surrogate 单元)。序列化文本与 toJSON/replacer 键参数均用此真实串。
fn key_si_to_units<H: VmHost>(vm: &H, si: u32) -> Vec<u16> {
if is_int_key(si) {
int_key_value(si).to_string().encode_utf16().collect()
} else {
vm.kernel_core()
.perm_interner()
.lookup(si)
.map(oxide_kernel::string_forge::decode_key)
.unwrap_or_default()
}
}
fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<JsValue, JsValue> {
if !obj_val.is_object() {
return Ok(obj_val);
}
let obj_ptr = obj_val.as_js_object_ptr();
if obj_ptr.is_null() {
return Ok(obj_val);
}
let tojson_si = vm.kernel_core().perm_interner().intern("toJSON").0;
let resolved = vm.resolve_property(unsafe { &*obj_ptr }, tojson_si);
match resolved {
Some(fn_val) if fn_val.is_object() => {
let fn_ptr = fn_val.as_js_object_ptr();
if !fn_ptr.is_null() && unsafe { (*fn_ptr).is_function() } {
let key_val = vm.new_string_units_owned(key.to_vec());
match vm.call_function_sync(fn_val, obj_val, &[key_val]) {
Ok(v) => Ok(v),
Err(msg) => Err(crate::error::create_type_error(vm, &msg)),
}
} else {
Ok(obj_val)
}
}
_ => Ok(obj_val),
}
}
/// `JSON.stringify(value, replacer, space)`:将 JS 值序列化为 JSON 文本。
/// 支持 replacer 函数/属性白名单、toJSON 钩子、缩进与循环引用检测。
pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::undefined());
}
let value = vm.reg(args[1]);
if value.is_undefined() {
return NativeResult::Ok(JsValue::undefined());
}
let mut replacer_fn: Option<JsValue> = None;
let mut replacer_whitelist: Option<HashSet<String>> = None;
if args.len() > 2 {
let replacer_val = vm.reg(args[2]);
if replacer_val.is_object() {
let rptr = replacer_val.as_js_object_ptr();
if !rptr.is_null() {
let robj = unsafe { &*rptr };
if robj.is_function() {
replacer_fn = Some(replacer_val);
} else if robj.is_array() {
let len = robj.prop_count() as usize;
let mut whitelist = HashSet::new();
for i in 0..len {
let elem = robj.get_prop_at(i);
if !elem.is_undefined() {
whitelist.insert(oxide_runtime_api::to_string(elem));
}
}
replacer_whitelist = Some(whitelist);
}
}
}
}
let space = if args.len() > 3 { process_space(vm.reg(args[3])) } else { String::new() };
let holder = create_wrapper(vm, value);
let value = match call_to_json(vm, value, &[]) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
};
let value = if let Some(replacer) = replacer_fn {
let key_val = vm.new_string("");
match vm.call_function_sync(replacer, holder, &[key_val, value]) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
}
} else {
value
};
let mut visited = HashSet::new();
let mut output = String::new();
let indent_level: usize = 0;
if jsvalue_to_json(
vm,
value,
&mut visited,
&mut output,
replacer_fn,
replacer_whitelist.as_ref(),
&space,
indent_level,
&[],
)
.is_err()
{
return NativeResult::Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
};
NativeResult::Ok(vm.new_string_owned(output))
}
#[allow(clippy::too_many_arguments)]
fn jsvalue_to_json<H: VmHost>(
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],
) -> Result<(), ()> {
let val = call_to_json(vm, val, key).map_err(|_| ())?;
if val.is_null() {
out.push_str("null");
} else if val.is_undefined() {
} else if val.is_bool() {
out.push_str(if val.as_bool() { "true" } else { "false" });
} else if val.is_int() {
write!(out, "{}", val.as_int()).unwrap();
} else if val.is_double() {
let n = val.as_double();
if !n.is_finite() {
out.push_str("null");
} else {
oxide_runtime_api::write_number_into(n, out);
}
} else if val.is_string() {
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
let units = unsafe { (*val.as_string_ptr()).units() };
stringify_string_units(&units, out);
} else if val.is_object() {
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
out.push_str("null");
return Ok(());
}
if !visited.insert(obj_ptr as *const JsObject) {
return Err(());
}
let obj = unsafe { &*obj_ptr };
if obj.is_array() {
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
} else {
stringify_object(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
}
visited.remove(&(obj_ptr as *const JsObject));
}
Ok(())
}
/// JSON 字符串序列化(按码单元流):代理对原样输出 astral 字符,非配对孤立
/// surrogate 输出 \uXXXX(well-formed JSON 要求,round-trip 经 JSON.parse 复原同值),
/// C0/C1 控制码输出 \uXXXX,其余按 JSON 转义规则。
fn stringify_string_units(units: &[u16], out: &mut String) {
out.push('"');
let mut i = 0;
while i < units.len() {
let u = units[i];
if (0xD800..=0xDBFF).contains(&u) && i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
let cp = 0x10000 + ((u32::from(u) - 0xD800) << 10) + (u32::from(units[i + 1]) - 0xDC00);
if let Some(c) = char::from_u32(cp) {
out.push(c);
}
i += 2;
continue;
}
match u {
0x22 => out.push_str("\\\""),
0x5C => out.push_str("\\\\"),
0x0A => out.push_str("\\n"),
0x0D => out.push_str("\\r"),
0x09 => out.push_str("\\t"),
0x00..=0x1F | 0x7F..=0x9F | 0xD800..=0xDFFF => {
let _ = write!(out, "\\u{:04x}", u);
}
_ => {
if let Some(c) = char::from_u32(u32::from(u)) {
out.push(c);
}
}
}
i += 1;
}
out.push('"');
}
#[allow(clippy::too_many_arguments)]
fn stringify_object<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<(), ()> {
let has_space = !space.is_empty();
out.push('{');
let keys = walk_own_keys(vm, obj);
// 仅序列化可枚举自身属性(规范 EnumerableOwnPropertyNames)。
let entries: Vec<(Vec<u16>, String, u32)> = keys
.into_iter()
.filter(|(_si, pos)| {
obj.prop_meta_at(*pos)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.filter_map(|(si, pos)| {
let units = key_si_to_units(vm, si);
// 白名单匹配走 lossy 串形态(与白名单构造侧 to_string 同口径)。
let name = String::from_utf16_lossy(&units);
if let Some(whitelist) = replacer_whitelist {
if !whitelist.contains(&name) {
return None;
}
}
Some((units, name, pos))
})
.collect();
let mut first = true;
for (units, _name, pos) in entries {
let val = obj.get_prop_at(pos);
// replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。
let val = if let Some(replacer) = replacer_fn {
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, holder, &[key_val, val]) {
Ok(v) => {
if v.is_undefined() {
continue;
}
v
}
Err(_) => return Err(()),
}
} 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(&units, out);
out.push(':');
out.push(' ');
} else {
stringify_string_units(&units, out);
out.push(':');
}
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1, &units)?;
}
if has_space && !first {
out.push('\n');
for _ in 0..indent_level {
out.push_str(space);
}
}
out.push('}');
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn stringify_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<(), ()> {
let has_space = !space.is_empty();
out.push('[');
let len = obj.prop_count() as usize;
for i in 0..len {
if i > 0 && !has_space {
out.push(',');
} else if i > 0 {
out.push_str(",\n");
}
if has_space {
out.push('\n');
for _ in 0..indent_level + 1 {
out.push_str(space);
}
}
let val = obj.get_prop_at(i);
let index_str = i.to_string();
let index_units: Vec<u16> = index_str.encode_utf16().collect();
// replacer 函数回调(toJSON 已在 jsvalue_to_json 中处理)。
let val = if let Some(replacer) = replacer_fn {
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, holder, &[key_val, val]) {
Ok(v) => {
if v.is_undefined() {
out.push_str("null");
continue;
}
v
}
Err(_) => return Err(()),
}
} else {
val
};
let is_function = val.is_object() && {
let ptr = val.as_js_object_ptr();
!ptr.is_null() && unsafe { (*ptr).is_function() }
};
if is_function || val.is_undefined() {
out.push_str("null");
} else {
jsvalue_to_json(
vm,
val,
visited,
out,
replacer_fn,
replacer_whitelist,
space,
indent_level + 1,
&index_units,
)?;
}
}
if has_space && len > 0 {
out.push('\n');
for _ in 0..indent_level {
out.push_str(space);
}
}
out.push(']');
Ok(())
}