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(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( 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(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(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(vm: &H, si: u32) -> Vec { 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(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result { 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(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 = None; let mut replacer_whitelist: Option> = 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( vm: &mut H, val: JsValue, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option, replacer_whitelist: Option<&HashSet>, 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( vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option, replacer_whitelist: Option<&HashSet>, 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, 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( vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut String, replacer_fn: Option, replacer_whitelist: Option<&HashSet>, 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 = 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(()) }