mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
1459 lines
56 KiB
Rust
1459 lines
56 KiB
Rust
use std::collections::HashSet;
|
||
|
||
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, ProtoKind, VmHost};
|
||
|
||
/// `JSON.parse(text, reviver)`:解析 JSON 文本为 JS 值(递归下降解析器,
|
||
/// 逐原始值记录精确源文本切片)。提供 reviver 时以后序遍历逐属性调用
|
||
/// reviver 重建值,第三参 context 对象携带 `source` 属性。
|
||
///
|
||
/// `text` 经 `? ToString` 强转:对象走 ToPrimitive(string hint),Symbol
|
||
/// 抛 TypeError,强转期用户异常原值传播。
|
||
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 text_val = match oxide_runtime_api::to_string_value_full(vm.reg(args[1]), vm) {
|
||
Ok(v) => v,
|
||
Err(_) => {
|
||
// 强转期用户异常原值重抛;Symbol 无在途异常,按规范构造 TypeError。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||
}
|
||
};
|
||
let text = {
|
||
// SAFETY: text_val 已确认是字符串值。
|
||
unsafe { (*text_val.as_string_ptr()).to_owned_string() }
|
||
};
|
||
|
||
let parsed = match parse_json(&text) {
|
||
Ok(v) => v,
|
||
Err(msg) => return NativeResult::Err(crate::error::create_syntax_error(vm, &msg)),
|
||
};
|
||
|
||
let mut result = build_js_value(vm, &parsed);
|
||
|
||
// reviver 遍历以 holder 包装对象为根:解析得到的根值存于空串键槽,
|
||
// 后序遍历自该槽展开。返回值 = 根级 reviver 返回值原样(wrapper 的
|
||
// `''` 槽由父级循环施加,根级无父级,槽恒不被触碰)。
|
||
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.perm_intern("");
|
||
let holder = create_wrapper(vm, result);
|
||
let holder_ptr = holder.as_js_object_ptr();
|
||
result = match walk_reviver(vm, holder_ptr, empty_si, reviver_val, Some(&parsed)) {
|
||
Ok(new_val) => new_val,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
NativeResult::Ok(result)
|
||
}
|
||
|
||
/// rawJSON 对象禁止的首/末 code unit 集:TAB/LF/CR/SPACE。
|
||
const RAW_JSON_FORBIDDEN_EDGE_UNITS: [u16; 4] = [0x09, 0x0A, 0x0D, 0x20];
|
||
|
||
/// `JSON.rawJSON(text)`:把合法 JSON 文本包装为 raw JSON 对象。
|
||
///
|
||
/// # 步骤
|
||
/// 1. `? ToString(text)`(Symbol 抛 TypeError,对象经 ToPrimitive,用户异常原值传播)。
|
||
/// 2. 空串或首/末 code unit 为 TAB/LF/CR/SPACE 抛 SyntaxError。
|
||
/// 3. 须为合法 JSON 文本且最外层非 object/array,否则 SyntaxError。
|
||
/// 4. 建 null 原型 frozen 对象,唯一自身属性 `rawJSON` = 文本
|
||
/// (writable:false、enumerable:true、configurable:false)。
|
||
///
|
||
/// # 副作用
|
||
/// - 经统一入口分配一个 session 对象与其字符串属性值。
|
||
pub fn json_raw_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let text_val = match oxide_runtime_api::to_string_value_full(
|
||
if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() },
|
||
vm,
|
||
) {
|
||
Ok(v) => v,
|
||
Err(_) => {
|
||
// 强转期用户异常原值重抛;Symbol 无在途异常,按规范构造 TypeError。
|
||
if let Some(exc) = vm.take_uncaught_value() {
|
||
return NativeResult::Err(exc);
|
||
}
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||
}
|
||
};
|
||
let text = {
|
||
// SAFETY: text_val 已确认是字符串值。
|
||
unsafe { (*text_val.as_string_ptr()).to_owned_string() }
|
||
};
|
||
|
||
// 边界字符校验:空串或首/末 code unit 属空白四元之一。
|
||
let units: Vec<u16> = text.encode_utf16().collect();
|
||
if units.is_empty()
|
||
|| RAW_JSON_FORBIDDEN_EDGE_UNITS.contains(&units[0])
|
||
|| RAW_JSON_FORBIDDEN_EDGE_UNITS.contains(&units[units.len() - 1])
|
||
{
|
||
return NativeResult::Err(crate::error::create_syntax_error(vm, "Invalid JSON text"));
|
||
}
|
||
|
||
// 合法性校验:解析失败即非合法 JSON 文本;最外层
|
||
// object/array 不满足 raw JSON 语义。
|
||
match parse_json(&text) {
|
||
Ok(JsonNode {
|
||
kind: JsonKind::Object(_) | JsonKind::Array(_),
|
||
..
|
||
}) => {
|
||
return NativeResult::Err(crate::error::create_syntax_error(vm, "Invalid JSON text"));
|
||
}
|
||
Ok(_) => {}
|
||
Err(msg) => return NativeResult::Err(crate::error::create_syntax_error(vm, &msg)),
|
||
}
|
||
|
||
// 建 null 原型对象:rawJSON 属性为唯一自身属性,frozen 形态收尾。
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||
obj.type_tag = JsObject::OBJ_TYPE_RAW_JSON;
|
||
let raw_si = vm.perm_intern("rawJSON");
|
||
let new_shape = vm.make_shape(obj.shape_id(), raw_si);
|
||
obj.set_shape_id(new_shape);
|
||
obj.ensure_hash_props().push(text_val);
|
||
let obj_ptr = vm.alloc_object(obj);
|
||
{
|
||
let obj = unsafe { &mut *obj_ptr };
|
||
obj.set_data_meta(0, PropAttributes::new(false, true, false));
|
||
obj.set_frozen(true);
|
||
obj.set_extensible(false);
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||
}
|
||
|
||
/// `JSON.isRawJSON(value)`:value 带 raw JSON 类型标签时返回 true,其余一律 false。
|
||
pub fn json_is_raw_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let val = if args.len() >= 2 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let is_raw = val.is_object() && {
|
||
let p = val.as_js_object_ptr();
|
||
!p.is_null() && unsafe { (*p).is_raw_json_obj() }
|
||
};
|
||
NativeResult::Ok(JsValue::bool(is_raw))
|
||
}
|
||
|
||
/// 解析后的 JSON 节点:值 + 精确源文本切片(仅原始值携带)。
|
||
/// 切片供 reviver context 的 `source` 属性与 SameValue-as 判定使用。
|
||
enum JsonKind {
|
||
Null,
|
||
Bool(bool),
|
||
Number(f64),
|
||
String(String),
|
||
Array(Vec<JsonNode>),
|
||
Object(Vec<(String, JsonNode)>),
|
||
}
|
||
|
||
struct JsonNode {
|
||
kind: JsonKind,
|
||
/// 精确源文本切片(数字不规范化、字符串含引号);array/object 为 `None`。
|
||
source: Option<String>,
|
||
}
|
||
|
||
/// 递归下降 JSON 解析器:接受 JSON 文本语法(空白限 TAB/LF/CR/SPACE),
|
||
/// 逐原始值记录精确源文本切片。
|
||
///
|
||
/// # 边界与前提
|
||
/// - 前导零数字、尾随逗号、溢出(1e400)、孤立 surrogate 均报 SyntaxError。
|
||
/// - 嵌套 object/array 深度上限 `MAX_JSON_DEPTH` 层,超限报 SyntaxError
|
||
/// (进入时先计入再判定,防深嵌套在递归展开前耗尽栈)。
|
||
/// - 重复对象键:末写胜(键序由 build_js_value 统一)。
|
||
fn parse_json(text: &str) -> Result<JsonNode, String> {
|
||
let mut p = JsonParser { text, pos: 0, depth: 0 };
|
||
p.skip_ws();
|
||
let node = p.parse_value()?;
|
||
p.skip_ws();
|
||
if p.pos != text.len() {
|
||
return Err("trailing characters".into());
|
||
}
|
||
Ok(node)
|
||
}
|
||
|
||
/// 嵌套容器深度上限:object/array 进入时计入,超限报 SyntaxError。
|
||
const MAX_JSON_DEPTH: usize = 128;
|
||
|
||
struct JsonParser<'a> {
|
||
text: &'a str,
|
||
pos: usize,
|
||
depth: usize,
|
||
}
|
||
|
||
impl<'a> JsonParser<'a> {
|
||
fn skip_ws(&mut self) {
|
||
while self.pos < self.text.len() {
|
||
match self.text.as_bytes()[self.pos] {
|
||
b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
|
||
_ => break,
|
||
}
|
||
}
|
||
}
|
||
|
||
fn peek(&self) -> Option<u8> {
|
||
self.text.as_bytes().get(self.pos).copied()
|
||
}
|
||
|
||
fn peek_char(&self) -> Option<char> {
|
||
self.text.get(self.pos..).and_then(|s| s.chars().next())
|
||
}
|
||
|
||
/// 以当前游标为终点物化原始值节点(源切片 = `start..pos`)。
|
||
fn prim(&self, kind: JsonKind, start: usize) -> JsonNode {
|
||
JsonNode {
|
||
kind,
|
||
source: Some(self.text[start..self.pos].to_string()),
|
||
}
|
||
}
|
||
|
||
fn expect_word(&mut self, word: &str) -> Result<(), String> {
|
||
let end = self.pos + word.len();
|
||
if self.text.get(self.pos..end) == Some(word) {
|
||
self.pos = end;
|
||
Ok(())
|
||
} else {
|
||
Err("invalid literal".into())
|
||
}
|
||
}
|
||
|
||
fn parse_value(&mut self) -> Result<JsonNode, String> {
|
||
self.skip_ws();
|
||
let start = self.pos;
|
||
let c = self.peek().ok_or_else(|| "unexpected end of input".to_string())?;
|
||
match c {
|
||
b'n' => {
|
||
self.expect_word("null")?;
|
||
Ok(self.prim(JsonKind::Null, start))
|
||
}
|
||
b't' => {
|
||
self.expect_word("true")?;
|
||
Ok(self.prim(JsonKind::Bool(true), start))
|
||
}
|
||
b'f' => {
|
||
self.expect_word("false")?;
|
||
Ok(self.prim(JsonKind::Bool(false), start))
|
||
}
|
||
b'"' => {
|
||
let s = self.parse_string()?;
|
||
Ok(self.prim(JsonKind::String(s), start))
|
||
}
|
||
b'[' => {
|
||
// 深度计数:进入时先 +1 再判上限,超限在递归展开前拒绝。
|
||
self.depth += 1;
|
||
if self.depth > MAX_JSON_DEPTH {
|
||
self.depth -= 1;
|
||
return Err("recursion limit exceeded".into());
|
||
}
|
||
let items = self.parse_array()?;
|
||
self.depth -= 1;
|
||
Ok(JsonNode {
|
||
kind: JsonKind::Array(items),
|
||
source: None,
|
||
})
|
||
}
|
||
b'{' => {
|
||
self.depth += 1;
|
||
if self.depth > MAX_JSON_DEPTH {
|
||
self.depth -= 1;
|
||
return Err("recursion limit exceeded".into());
|
||
}
|
||
let entries = self.parse_object()?;
|
||
self.depth -= 1;
|
||
Ok(JsonNode {
|
||
kind: JsonKind::Object(entries),
|
||
source: None,
|
||
})
|
||
}
|
||
b'-' | b'0'..=b'9' => {
|
||
let n = self.parse_number()?;
|
||
Ok(self.prim(JsonKind::Number(n), start))
|
||
}
|
||
_ => Err("expected value".into()),
|
||
}
|
||
}
|
||
|
||
fn parse_array(&mut self) -> Result<Vec<JsonNode>, String> {
|
||
// 当前字节为 '['。
|
||
self.pos += 1;
|
||
let mut items = Vec::new();
|
||
self.skip_ws();
|
||
if self.peek() == Some(b']') {
|
||
self.pos += 1;
|
||
return Ok(items);
|
||
}
|
||
loop {
|
||
let item = self.parse_value()?;
|
||
items.push(item);
|
||
self.skip_ws();
|
||
match self.peek() {
|
||
Some(b',') => {
|
||
self.pos += 1;
|
||
self.skip_ws();
|
||
// 尾随逗号:',' 后必须跟值。
|
||
if self.peek() == Some(b']') {
|
||
return Err("trailing comma".into());
|
||
}
|
||
}
|
||
Some(b']') => {
|
||
self.pos += 1;
|
||
return Ok(items);
|
||
}
|
||
_ => return Err("expected ',' or ']'".into()),
|
||
}
|
||
}
|
||
}
|
||
|
||
fn parse_object(&mut self) -> Result<Vec<(String, JsonNode)>, String> {
|
||
// 当前字节为 '{'。
|
||
self.pos += 1;
|
||
let mut entries = Vec::new();
|
||
self.skip_ws();
|
||
if self.peek() == Some(b'}') {
|
||
self.pos += 1;
|
||
return Ok(entries);
|
||
}
|
||
loop {
|
||
self.skip_ws();
|
||
let key = match self.peek() {
|
||
Some(b'"') => self.parse_string()?,
|
||
_ => return Err("expected property name".into()),
|
||
};
|
||
self.skip_ws();
|
||
if self.peek() != Some(b':') {
|
||
return Err("expected ':'".into());
|
||
}
|
||
self.pos += 1;
|
||
let val = self.parse_value()?;
|
||
entries.push((key, val));
|
||
self.skip_ws();
|
||
match self.peek() {
|
||
Some(b',') => {
|
||
self.pos += 1;
|
||
self.skip_ws();
|
||
// 尾随逗号:',' 后必须跟键。
|
||
if self.peek() == Some(b'}') {
|
||
return Err("trailing comma".into());
|
||
}
|
||
}
|
||
Some(b'}') => {
|
||
self.pos += 1;
|
||
return Ok(entries);
|
||
}
|
||
_ => return Err("expected ',' or '}'".into()),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// JSON 数字文法:`-?(0|[1-9]digits)(.digits)?([eE][+-]?digits)?`;
|
||
/// 溢出(1e400)报范围错误,下溢(1e-400)归零。
|
||
fn parse_number(&mut self) -> Result<f64, String> {
|
||
let start = self.pos;
|
||
if self.peek() == Some(b'-') {
|
||
self.pos += 1;
|
||
}
|
||
match self.peek() {
|
||
Some(b'0') => self.pos += 1,
|
||
Some(b'1'..=b'9') => {
|
||
while self.peek().is_some_and(|c| c.is_ascii_digit()) {
|
||
self.pos += 1;
|
||
}
|
||
}
|
||
_ => return Err("invalid number".into()),
|
||
}
|
||
if self.peek() == Some(b'.') {
|
||
self.pos += 1;
|
||
if !self.peek().is_some_and(|c| c.is_ascii_digit()) {
|
||
return Err("invalid number".into());
|
||
}
|
||
while self.peek().is_some_and(|c| c.is_ascii_digit()) {
|
||
self.pos += 1;
|
||
}
|
||
}
|
||
if let Some(c) = self.peek() {
|
||
if c == b'e' || c == b'E' {
|
||
self.pos += 1;
|
||
if let Some(s) = self.peek() {
|
||
if s == b'+' || s == b'-' {
|
||
self.pos += 1;
|
||
}
|
||
}
|
||
if !self.peek().is_some_and(|c| c.is_ascii_digit()) {
|
||
return Err("invalid number".into());
|
||
}
|
||
while self.peek().is_some_and(|c| c.is_ascii_digit()) {
|
||
self.pos += 1;
|
||
}
|
||
}
|
||
}
|
||
let slice = &self.text[start..self.pos];
|
||
let n = slice.parse::<f64>().map_err(|_| "invalid number".to_string())?;
|
||
if !n.is_finite() {
|
||
return Err("number out of range".into());
|
||
}
|
||
Ok(n)
|
||
}
|
||
|
||
fn parse_string(&mut self) -> Result<String, String> {
|
||
// 当前字节为开引号。
|
||
self.pos += 1;
|
||
let mut units: Vec<u16> = Vec::new();
|
||
loop {
|
||
let c = self.peek_char().ok_or_else(|| "unterminated string".to_string())?;
|
||
match c {
|
||
'"' => {
|
||
self.pos += 1;
|
||
// 解析期已拒绝孤立 surrogate,单元序列恒为良形 UTF-16。
|
||
return Ok(String::from_utf16(&units).expect("no lone surrogate survives parse"));
|
||
}
|
||
'\\' => {
|
||
self.pos += 1;
|
||
let e = self.peek_char().ok_or_else(|| "unterminated escape".to_string())?;
|
||
self.pos += 1;
|
||
let u = match e {
|
||
'"' => 0x22,
|
||
'\\' => 0x5C,
|
||
'/' => 0x2F,
|
||
'b' => 0x08,
|
||
'f' => 0x0C,
|
||
'n' => 0x0A,
|
||
'r' => 0x0D,
|
||
't' => 0x09,
|
||
'u' => self.parse_unicode_escape()?,
|
||
_ => return Err("invalid escape".into()),
|
||
};
|
||
units.push(u as u16);
|
||
}
|
||
c => {
|
||
if (c as u32) <= 0x1F {
|
||
return Err("unescaped control character in string".into());
|
||
}
|
||
// 码元编码(含 astral 字符双单元)。
|
||
let mut buf = [0u16; 2];
|
||
let written = c.encode_utf16(&mut buf);
|
||
units.extend_from_slice(written);
|
||
self.pos += c.len_utf8();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn parse_unicode_escape(&mut self) -> Result<u32, String> {
|
||
let hi = self.parse_hex4()?;
|
||
if (0xD800..=0xDBFF).contains(&hi) {
|
||
// 代理对:高 surrogate 必须紧跟 \uXXXX 低 surrogate。
|
||
if self.text.get(self.pos..).is_some_and(|s| s.starts_with("\\u")) {
|
||
self.pos += 2;
|
||
let lo = self.parse_hex4()?;
|
||
if (0xDC00..=0xDFFF).contains(&lo) {
|
||
return Ok(0x10000 + ((u32::from(hi) - 0xD800) << 10) + (u32::from(lo) - 0xDC00));
|
||
}
|
||
return Err("invalid surrogate pair".into());
|
||
}
|
||
return Err("lone leading surrogate in hex escape".into());
|
||
}
|
||
if (0xDC00..=0xDFFF).contains(&hi) {
|
||
return Err("lone leading surrogate in hex escape".into());
|
||
}
|
||
Ok(u32::from(hi))
|
||
}
|
||
|
||
fn parse_hex4(&mut self) -> Result<u16, String> {
|
||
let mut v: u16 = 0;
|
||
for _ in 0..4 {
|
||
let c = self.peek_char().ok_or_else(|| "unexpected end of hex escape".to_string())?;
|
||
let d = c
|
||
.to_digit(16)
|
||
.map(|d| d as u16)
|
||
.ok_or_else(|| "invalid hex digit".to_string())?;
|
||
v = v * 16 + d;
|
||
self.pos += 1;
|
||
}
|
||
Ok(v)
|
||
}
|
||
}
|
||
|
||
/// InternalizeJSONProperty 后序遍历:Get 读当前值 → 递归子节点并把每子返回值
|
||
/// 施加于容器(undefined → [[Delete]],否则 CreateDataProperty,两失败路径
|
||
/// 静默)→ 构造 context 对象 → 调用 reviver。返回值 = 本级 reviver 返回值
|
||
/// 原样,由父级循环施加(根级由 json_parse 直接作 parse 结果)。
|
||
///
|
||
/// `node` 是本级对应的解析节点(可缺):子集恒按当前值(容器可被 reviver
|
||
/// 前向改写,键集与解析节点不再重合);节点仅作子节点映射与 context 的
|
||
/// `source` 属性来源,无节点处按空 context 处理。
|
||
fn walk_reviver<H: VmHost>(
|
||
vm: &mut H, holder_ptr: *mut JsObject, key_si: u32, reviver: JsValue, node: Option<&JsonNode>,
|
||
) -> Result<JsValue, JsValue> {
|
||
let holder_val = JsValue::from_js_object(holder_ptr);
|
||
|
||
// 读臂:完整 Get(原型链 + 自身访问器触发),getter 抛出传播原值。
|
||
let val = match vm.ordinary_get(unsafe { &*holder_ptr }, key_si, holder_val) {
|
||
Ok(v) => v,
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return Err(exc);
|
||
}
|
||
};
|
||
|
||
// 后序遍历:先递归子节点,每子返回值施加于容器。
|
||
if val.is_object() {
|
||
let obj_ptr = val.as_js_object_ptr();
|
||
if !obj_ptr.is_null() {
|
||
if unsafe { (*obj_ptr).is_array() } {
|
||
let len = unsafe { (*obj_ptr).prop_count() } as usize;
|
||
for i in 0..len {
|
||
let child_si = make_int_key(i as u32);
|
||
let child_node = node.and_then(|n| match &n.kind {
|
||
JsonKind::Array(items) => items.get(i),
|
||
_ => None,
|
||
});
|
||
let new_val = walk_reviver(vm, obj_ptr, child_si, reviver, child_node)?;
|
||
apply_child_result(vm, obj_ptr, child_si, new_val);
|
||
}
|
||
} else {
|
||
// 对象臂:节点键经同一键规范化映射 si(重复键末写胜,
|
||
// 与 build_js_value 一致),容器键逐一按 si 定位节点。
|
||
let child_nodes = match node {
|
||
Some(JsonNode {
|
||
kind: JsonKind::Object(entries),
|
||
..
|
||
}) => {
|
||
let mut seen: Vec<(u32, &JsonNode)> = Vec::new();
|
||
for (key, val) in entries {
|
||
let si = vm.string_key_si(key);
|
||
match seen.iter_mut().find(|(s, _)| *s == si) {
|
||
Some(slot) => slot.1 = val,
|
||
None => seen.push((si, val)),
|
||
}
|
||
}
|
||
seen
|
||
}
|
||
_ => Vec::new(),
|
||
};
|
||
let keys = {
|
||
let obj = unsafe { &*obj_ptr };
|
||
walk_own_keys(vm, obj)
|
||
};
|
||
for (child_si, _child_pos) in keys {
|
||
let child_node = child_nodes.iter().find(|(s, _)| *s == child_si).map(|(_, n)| *n);
|
||
let new_val = walk_reviver(vm, obj_ptr, child_si, reviver, child_node)?;
|
||
apply_child_result(vm, obj_ptr, child_si, new_val);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// context 对象:仅当当前值与解析时值 SameValue-as 时携带 source。
|
||
let context = build_context(vm, val, node);
|
||
|
||
// 对当前值调用 reviver,返回值上抛由父级施加。
|
||
let key_val = crate::object::key_si_to_js_value(vm, key_si);
|
||
match vm.call_function_sync(reviver, holder_val, &[key_val, val, context]) {
|
||
Ok(new_val) => Ok(new_val),
|
||
Err(msg) => {
|
||
// reviver 内抛出的原始值原样传播(不折叠为 TypeError 文本)。
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
Err(exc)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 构造 reviver 的 context 对象:普通对象(proto = Object.prototype)。
|
||
/// 仅当节点带源切片且 `val` 与解析时值 SameValue-as 时注入 own `source`
|
||
/// 属性(w/e/c 全 true)——被 reviver 前向替换的值、array/object 节点
|
||
/// 与无节点处一律空 context。
|
||
fn build_context<H: VmHost>(vm: &mut H, val: JsValue, node: Option<&JsonNode>) -> JsValue {
|
||
let object_proto = vm.builtin_proto(ProtoKind::ObjectProto);
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||
if let Some(n) = node {
|
||
if let Some(src) = n.source.as_ref() {
|
||
if same_value_as(vm, val, n) {
|
||
let source_si = vm.perm_intern("source");
|
||
let new_shape = vm.make_shape(obj.shape_id(), source_si);
|
||
obj.set_shape_id(new_shape);
|
||
obj.ensure_hash_props().push(vm.new_string(src));
|
||
}
|
||
}
|
||
}
|
||
let obj_ptr = vm.alloc_object(obj);
|
||
JsValue::from_js_object(obj_ptr)
|
||
}
|
||
|
||
/// SameValue-as 判定(当前值 vs 解析时值):数字区分 +0/-0、NaN 自反;
|
||
/// 字符串按内容比;布尔/null 按值。
|
||
fn same_value_as<H: VmHost>(vm: &H, val: JsValue, node: &JsonNode) -> bool {
|
||
match &node.kind {
|
||
JsonKind::Null => val.is_null(),
|
||
JsonKind::Bool(b) => val.is_bool() && val.as_bool() == *b,
|
||
JsonKind::Number(n) => val.is_double() && same_value_number(val.as_double(), *n),
|
||
JsonKind::String(s) => {
|
||
if !val.is_string() {
|
||
return false;
|
||
}
|
||
let parsed: Vec<u16> = s.encode_utf16().collect();
|
||
vm.string_units(val).as_ref() == parsed.as_slice()
|
||
}
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
fn same_value_number(a: f64, b: f64) -> bool {
|
||
if a.is_nan() {
|
||
return b.is_nan();
|
||
}
|
||
if a == b {
|
||
// SameValue:+0 与 -0 不相等。
|
||
a != 0.0 || a.is_sign_positive() == b.is_sign_positive()
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// 子节点递归返回值施加于容器:`undefined` → [[Delete]](非可配置保留),
|
||
/// 否则 CreateDataProperty(非可配置静默保旧值,新键建自身)。两失败路径
|
||
/// 均静默不抛(规范明注)。
|
||
fn apply_child_result<H: VmHost>(vm: &mut H, child_ptr: *mut JsObject, child_si: u32, new_val: JsValue) {
|
||
if new_val.is_undefined() {
|
||
// 子节点为 JSON.parse 新建的 plain 对象(非 deferred namespace),触发
|
||
// 恒 no-op;删除失败静默不抛(规范明注)。
|
||
let _ = crate::object::delete_own_property(vm, unsafe { &mut *child_ptr }, child_si);
|
||
} else {
|
||
let _ = vm.define_data_property(
|
||
unsafe { &mut *child_ptr },
|
||
child_si,
|
||
new_val,
|
||
PropAttributes::new(true, true, true),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 解析节点树 → JS 值树。对象键经同一字符串规范化(规范数字串映射整数键),
|
||
/// 重复键末写胜、键序字典序(与旧解析器语义一致)。
|
||
fn build_js_value<H: VmHost>(vm: &mut H, node: &JsonNode) -> JsValue {
|
||
match &node.kind {
|
||
JsonKind::Null => JsValue::null(),
|
||
JsonKind::Bool(b) => JsValue::bool(*b),
|
||
JsonKind::Number(n) => JsValue::float(*n),
|
||
JsonKind::String(s) => vm.new_string(s),
|
||
JsonKind::Array(items) => {
|
||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||
let n = items.len();
|
||
let array_obj =
|
||
vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n));
|
||
for (i, item) in items.iter().enumerate() {
|
||
let jsv = build_js_value(vm, item);
|
||
unsafe {
|
||
(*array_obj).set_prop_at(i, jsv);
|
||
}
|
||
}
|
||
JsValue::from_js_object(array_obj)
|
||
}
|
||
JsonKind::Object(entries) => {
|
||
// 重复键末写胜;键序字典序(与旧解析器语义一致)。
|
||
let mut unique: Vec<(&String, &JsonNode)> = Vec::new();
|
||
for (key, val) in entries {
|
||
match unique.iter_mut().find(|(k, _)| *k == key.as_str()) {
|
||
Some(slot) => slot.1 = val,
|
||
None => unique.push((key, val)),
|
||
}
|
||
}
|
||
unique.sort_by(|a, b| a.0.cmp(b.0));
|
||
|
||
let object_proto = vm.builtin_proto(ProtoKind::ObjectProto);
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||
for (key, val) in unique {
|
||
// 键经字符串规范化:规范数字串("0"/"5")映射整数键,与属性访问统一。
|
||
let si = vm.string_key_si(key);
|
||
let jsv = build_js_value(vm, val);
|
||
let new_shape = vm.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.builtin_proto(ProtoKind::ObjectProto);
|
||
let empty_si = vm.perm_intern("");
|
||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||
let new_shape = vm.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)
|
||
}
|
||
|
||
/// space 参数文本化(Stringify 步 5):数字与装箱 Number 经 ToNumber →
|
||
/// ToIntegerOrInfinity 后 min(10, x) 个空格(+∞ → 10、−∞/NaN → 空);字符串与
|
||
/// 装箱 String 取前 10 码元(码元口径,astral 字符可截在代理对边界,截断产物
|
||
/// 保留孤立 surrogate;装箱 String 走完整 ToString,步 4c 同款语义,抛出值原样
|
||
/// 上抛);其余形态(装箱 Boolean、普通对象、Symbol 等)gap 为空。
|
||
fn process_space<H: VmHost>(vm: &mut H, val: JsValue) -> Result<Vec<u16>, JsValue> {
|
||
if val.is_int() || val.is_double() {
|
||
let n = oxide_runtime_api::to_integer_or_infinity(val);
|
||
return Ok(number_space_units(n));
|
||
}
|
||
if val.is_string() {
|
||
let units = vm.string_units(val);
|
||
return Ok(units.iter().take(10).copied().collect());
|
||
}
|
||
if val.is_object() {
|
||
let ptr = val.as_js_object_ptr();
|
||
if !ptr.is_null() {
|
||
let obj = unsafe { &*ptr };
|
||
// [[NumberData]]:ToNumber(ToPrimitive number hint,用户方法可触发)
|
||
// 后 ToIntegerOrInfinity。
|
||
if obj.is_number_obj() {
|
||
let n = match oxide_runtime_api::to_number_full(val, vm) {
|
||
Ok(n) => n,
|
||
Err(msg) => {
|
||
return Err(vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)));
|
||
}
|
||
};
|
||
let n = oxide_runtime_api::to_integer_or_infinity(JsValue::float(n));
|
||
return Ok(number_space_units(n));
|
||
}
|
||
// [[StringData]]:完整 ToString。
|
||
if obj.is_string_obj() {
|
||
let s = match oxide_runtime_api::to_string_value_full(val, vm) {
|
||
Ok(s) => s,
|
||
Err(msg) => {
|
||
return Err(vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)));
|
||
}
|
||
};
|
||
let units = vm.string_units(s);
|
||
return Ok(units.iter().take(10).copied().collect());
|
||
}
|
||
}
|
||
}
|
||
Ok(Vec::new())
|
||
}
|
||
|
||
/// 数字 space → min(10, ToIntegerOrInfinity(space)) 个空格码元:+∞ → 10、
|
||
/// −∞/NaN/小于 1 → 空。
|
||
fn number_space_units(n: f64) -> Vec<u16> {
|
||
let m = n.min(10.0);
|
||
if m.is_nan() || m < 1.0 {
|
||
Vec::new()
|
||
} else {
|
||
vec![0x20; m as usize]
|
||
}
|
||
}
|
||
|
||
/// 键 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.perm_lookup(si)
|
||
.map(oxide_kernel::string_forge::decode_key)
|
||
.unwrap_or_default()
|
||
}
|
||
}
|
||
|
||
/// 白名单元素 → 键名提取(Stringify 步 4):String/Number 原语直取;装箱
|
||
/// String/Number 经 ToPrimitive string hint(toString 优先、valueOf 兜底、
|
||
/// 两法皆不可调用落盒值);其余形态(装箱 Boolean、普通对象、Symbol 等)
|
||
/// 返回 None 跳过。方法读取与调用异常传播原始抛出值。
|
||
fn whitelist_element_name<H: VmHost>(vm: &mut H, elem: JsValue) -> Result<Option<String>, JsValue> {
|
||
if elem.is_undefined() {
|
||
return Ok(None);
|
||
}
|
||
if elem.is_string() || elem.is_int() || elem.is_double() {
|
||
return Ok(Some(oxide_runtime_api::to_string(elem)));
|
||
}
|
||
if !elem.is_object() {
|
||
return Ok(None);
|
||
}
|
||
let ptr = elem.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return Ok(None);
|
||
}
|
||
let obj = unsafe { &*ptr };
|
||
let boxed = obj.boxed_value();
|
||
if !boxed.is_string() && !boxed.is_int() && !boxed.is_double() {
|
||
return Ok(None);
|
||
}
|
||
|
||
// ToPrimitive string hint:toString 优先,valueOf 兜底。
|
||
let to_string_si = vm.perm_intern("toString");
|
||
let value_of_si = vm.perm_intern("valueOf");
|
||
for m_si in [to_string_si, value_of_si] {
|
||
let m = vm.ordinary_get(obj, m_si, elem).map_err(|msg| {
|
||
vm.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg))
|
||
})?;
|
||
if !m.is_object() {
|
||
continue;
|
||
}
|
||
let fptr = m.as_js_object_ptr();
|
||
if fptr.is_null() || !unsafe { (*fptr).is_function() } {
|
||
continue;
|
||
}
|
||
let r = vm.call_function_sync(m, elem, &[]).map_err(|msg| {
|
||
vm.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg))
|
||
})?;
|
||
return Ok(Some(oxide_runtime_api::to_string(r)));
|
||
}
|
||
|
||
// 装箱原型面理论不可达兜底:盒值直取。
|
||
Ok(Some(oxide_runtime_api::to_string(boxed)))
|
||
}
|
||
|
||
fn call_to_json<H: VmHost>(vm: &mut H, obj_val: JsValue, key: &[u16]) -> Result<JsValue, JsValue> {
|
||
// SerializeJSONProperty 步 2:对象与 BigInt 原语均查 toJSON(Get 对原语
|
||
// 自动装箱),BigInt 的查表对象取 %BigIntPrototype%。
|
||
if !obj_val.is_object() && !obj_val.is_bigint() {
|
||
return Ok(obj_val);
|
||
}
|
||
let obj_ptr = if obj_val.is_object() {
|
||
let p = obj_val.as_js_object_ptr();
|
||
if p.is_null() {
|
||
return Ok(obj_val);
|
||
}
|
||
p
|
||
} else {
|
||
vm.builtin_proto(ProtoKind::BigIntProto)
|
||
};
|
||
let tojson_si = vm.perm_intern("toJSON");
|
||
// Get 语义查表:访问器形 toJSON 触发 getter(receiver = 原值,BigInt 原语
|
||
// 以自身作 this),getter 异常传播原始抛出值。
|
||
let fn_val = match vm.ordinary_get(unsafe { &*obj_ptr }, tojson_si, obj_val) {
|
||
Ok(v) => v,
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return Err(exc);
|
||
}
|
||
};
|
||
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) => {
|
||
// 用户抛出的原始值原样传播(toJSON 抛非 Error 值不降级为 TypeError)。
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
Err(exc)
|
||
}
|
||
}
|
||
} else {
|
||
Ok(obj_val)
|
||
}
|
||
} else {
|
||
Ok(obj_val)
|
||
}
|
||
}
|
||
|
||
/// 序列化族单自身属性值读(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 文本。
|
||
/// 支持 replacer 函数/属性白名单、toJSON 钩子、缩进与循环引用检测。
|
||
/// 顶层值序为 Get → toJSON → replacer,undefined/函数/Symbol 早返在 replacer
|
||
/// 之后(replacer 可替换早返形态)。
|
||
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]);
|
||
|
||
let mut replacer_fn: Option<JsValue> = None;
|
||
let mut replacer_whitelist: Option<Vec<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: Vec<String> = Vec::new();
|
||
for i in 0..len {
|
||
let elem = robj.get_prop_at(i);
|
||
match whitelist_element_name(vm, elem) {
|
||
Ok(Some(name)) => {
|
||
// 有序去重:重复项不入列表。
|
||
if !whitelist.contains(&name) {
|
||
whitelist.push(name);
|
||
}
|
||
}
|
||
Ok(None) => {}
|
||
Err(exc) => return NativeResult::Err(exc),
|
||
}
|
||
}
|
||
replacer_whitelist = Some(whitelist);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
let space = if args.len() > 3 {
|
||
match process_space(vm, vm.reg(args[3])) {
|
||
Ok(s) => s,
|
||
Err(exc) => return NativeResult::Err(exc),
|
||
}
|
||
} else {
|
||
Vec::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) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
return NativeResult::Err(exc);
|
||
}
|
||
}
|
||
} else {
|
||
value
|
||
};
|
||
|
||
// 顶层省略值:replacer 之后的 undefined、函数、Symbol 一律返回 undefined
|
||
// (非空串)——replacer 先于本判定应用(SerializeJSONProperty 步 3 先于步 8/10)。
|
||
let top_is_fn = value.is_object() && {
|
||
let p = value.as_js_object_ptr();
|
||
!p.is_null() && unsafe { (*p).is_function() }
|
||
};
|
||
if value.is_undefined() || value.is_symbol() || top_is_fn {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
|
||
let mut visited = HashSet::new();
|
||
let mut output: Vec<u16> = Vec::new();
|
||
let ctx = StringifyCtx {
|
||
visited: &mut visited,
|
||
out: &mut output,
|
||
replacer_fn,
|
||
replacer_whitelist: replacer_whitelist.as_ref(),
|
||
space: &space,
|
||
indent_level: 0,
|
||
};
|
||
match jsvalue_to_json(vm, value, ctx) {
|
||
Ok(()) => NativeResult::Ok(vm.new_string_units_owned(output)),
|
||
Err(exc) => NativeResult::Err(exc),
|
||
}
|
||
}
|
||
|
||
/// JSON 序列化上下文:环检表、输出流、replacer 与缩进状态。
|
||
/// 递归传递时经 [`StringifyCtx::child`] 派生下一缩进层。
|
||
struct StringifyCtx<'data, 'w> {
|
||
visited: &'w mut HashSet<*const JsObject>,
|
||
out: &'w mut Vec<u16>,
|
||
replacer_fn: Option<JsValue>,
|
||
replacer_whitelist: Option<&'data Vec<String>>,
|
||
space: &'data [u16],
|
||
indent_level: usize,
|
||
}
|
||
|
||
impl<'data, 'w> StringifyCtx<'data, 'w> {
|
||
/// 派生同缩进层上下文:共享同一环检表与输出流,indent_level 不变
|
||
/// (值位序列化入口传给对象/数组展开时用)。
|
||
fn same(&mut self) -> StringifyCtx<'data, '_> {
|
||
StringifyCtx {
|
||
visited: &mut *self.visited,
|
||
out: &mut *self.out,
|
||
replacer_fn: self.replacer_fn,
|
||
replacer_whitelist: self.replacer_whitelist,
|
||
space: self.space,
|
||
indent_level: self.indent_level,
|
||
}
|
||
}
|
||
|
||
/// 派生下一缩进层上下文:共享同一环检表与输出流,indent_level 加一
|
||
/// (对象/数组展开内递归回值位序列化时用)。
|
||
fn child(&mut self) -> StringifyCtx<'data, '_> {
|
||
StringifyCtx {
|
||
visited: &mut *self.visited,
|
||
out: &mut *self.out,
|
||
replacer_fn: self.replacer_fn,
|
||
replacer_whitelist: self.replacer_whitelist,
|
||
space: self.space,
|
||
indent_level: self.indent_level + 1,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 值位序列化(SerializeValue 及对象/数组展开)。toJSON 钩子不在本入口应用——
|
||
/// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array`
|
||
/// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序
|
||
/// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。
|
||
fn jsvalue_to_json<H: VmHost>(vm: &mut H, val: JsValue, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||
if val.is_null() {
|
||
push_ascii(ctx.out, "null");
|
||
} else if val.is_undefined() {
|
||
} else if val.is_symbol() {
|
||
push_ascii(ctx.out, "null");
|
||
} else if val.is_bool() {
|
||
push_ascii(ctx.out, if val.as_bool() { "true" } else { "false" });
|
||
} else if val.is_int() {
|
||
push_ascii(ctx.out, &val.as_int().to_string());
|
||
} else if val.is_double() {
|
||
let n = val.as_double();
|
||
if !n.is_finite() {
|
||
push_ascii(ctx.out, "null");
|
||
} else {
|
||
push_number(ctx.out, n);
|
||
}
|
||
} 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() {
|
||
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
|
||
let units = unsafe { (*val.as_string_ptr()).units() };
|
||
stringify_string_units(&units, ctx.out);
|
||
} else if val.is_object() {
|
||
let obj_ptr = val.as_js_object_ptr();
|
||
if obj_ptr.is_null() {
|
||
push_ascii(ctx.out, "null");
|
||
return Ok(());
|
||
}
|
||
|
||
// SerializeJSONProperty 步 1a:raw JSON 对象直接输出原始文本(先于
|
||
// toJSON 查找;该对象仅一个字符串属性,不可能参与环,免环检)。
|
||
{
|
||
let obj = unsafe { &*obj_ptr };
|
||
if obj.is_raw_json_obj() {
|
||
let raw_val = obj.get_prop_at(0);
|
||
// SAFETY: rawJSON 属性构造期即写入字符串值。
|
||
let raw_text = unsafe { (*raw_val.as_string_ptr()).to_owned_string() };
|
||
push_ascii(ctx.out, &raw_text);
|
||
return Ok(());
|
||
}
|
||
}
|
||
|
||
if !ctx.visited.insert(obj_ptr as *const JsObject) {
|
||
return Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
|
||
}
|
||
|
||
let obj = unsafe { &*obj_ptr };
|
||
// 步 4a:装箱 Number → ToNumber(用户方法可触发),非有限值 → null。
|
||
if obj.is_number_obj() {
|
||
let n = match oxide_runtime_api::to_number_full(val, vm) {
|
||
Ok(n) => n,
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||
return Err(exc);
|
||
}
|
||
};
|
||
if n.is_finite() {
|
||
push_number(ctx.out, n);
|
||
} else {
|
||
push_ascii(ctx.out, "null");
|
||
}
|
||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||
return Ok(());
|
||
}
|
||
// 步 4b:装箱 Boolean → [[BooleanData]]。
|
||
if obj.is_boolean_obj() {
|
||
let b = obj.boxed_value();
|
||
push_ascii(ctx.out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
|
||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||
return Ok(());
|
||
}
|
||
// 步 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"));
|
||
}
|
||
// 步 4c:装箱 String 走完整 ToString([[StringData]] 臂):尊重
|
||
// toString/Symbol.toPrimitive 覆盖、不直读载荷,抛出值原样传播。
|
||
if obj.is_string_obj() {
|
||
let s = match oxide_runtime_api::to_string_value_full(val, vm) {
|
||
Ok(s) => s,
|
||
Err(msg) => {
|
||
let exc = vm
|
||
.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||
return Err(exc);
|
||
}
|
||
};
|
||
let units = vm.string_units(s);
|
||
stringify_string_units(&units, ctx.out);
|
||
} else if obj.is_typed_array_obj() {
|
||
stringify_typed_array(vm, obj, ctx.same())?;
|
||
} else if obj.is_array() {
|
||
stringify_array(vm, obj, ctx.same())?;
|
||
} else {
|
||
stringify_object(vm, obj, ctx.same())?;
|
||
}
|
||
|
||
ctx.visited.remove(&(obj_ptr as *const JsObject));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 追加 ASCII 字面量到单元流(JSON 结构字符与转义序列恒为 ASCII)。
|
||
fn push_ascii(out: &mut Vec<u16>, s: &str) {
|
||
out.extend(s.encode_utf16());
|
||
}
|
||
|
||
/// 追加数字规范文本到单元流(write_number_into 输出恒为 ASCII)。
|
||
fn push_number(out: &mut Vec<u16>, n: f64) {
|
||
let mut s = String::new();
|
||
oxide_runtime_api::write_number_into(n, &mut s);
|
||
push_ascii(out, &s);
|
||
}
|
||
|
||
/// JSON 字符串序列化(按码单元流):代理对原样输出 astral 字符,非配对孤立
|
||
/// surrogate 输出 \uXXXX(well-formed JSON 要求,round-trip 经 JSON.parse 复原
|
||
/// 同值),C0/C1 控制码输出 \uXXXX,其余按 JSON 转义规则。
|
||
fn stringify_string_units(units: &[u16], out: &mut Vec<u16>) {
|
||
push_ascii(out, "\"");
|
||
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]) {
|
||
out.push(u);
|
||
out.push(units[i + 1]);
|
||
i += 2;
|
||
continue;
|
||
}
|
||
match u {
|
||
0x22 => push_ascii(out, "\\\""),
|
||
0x5C => push_ascii(out, "\\\\"),
|
||
0x08 => push_ascii(out, "\\b"),
|
||
0x0C => push_ascii(out, "\\f"),
|
||
0x0A => push_ascii(out, "\\n"),
|
||
0x0D => push_ascii(out, "\\r"),
|
||
0x09 => push_ascii(out, "\\t"),
|
||
0x00..=0x1F | 0x7F..=0x9F | 0xD800..=0xDFFF => {
|
||
push_ascii(out, &format!("\\u{:04x}", u));
|
||
}
|
||
_ => out.push(u),
|
||
}
|
||
i += 1;
|
||
}
|
||
push_ascii(out, "\"");
|
||
}
|
||
|
||
fn stringify_object<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||
let has_space = !ctx.space.is_empty();
|
||
push_ascii(ctx.out, "{");
|
||
|
||
// 键集:白名单给定时 K = P(列表序,不限自身可枚举,值读走 Get);
|
||
// 无白名单走自身可枚举序(EnumerableOwnPropertyNames)。
|
||
let entries: Vec<(Vec<u16>, u32, Option<u32>)> = if let Some(whitelist) = ctx.replacer_whitelist {
|
||
whitelist
|
||
.iter()
|
||
.map(|name| {
|
||
let si = vm.string_key_si(name);
|
||
(key_si_to_units(vm, si), si, None)
|
||
})
|
||
.collect()
|
||
} else {
|
||
let keys = walk_own_keys(vm, obj);
|
||
keys.into_iter()
|
||
.filter(|(_si, pos)| {
|
||
obj.prop_meta_at(*pos)
|
||
.map(|m| m.attributes.enumerable())
|
||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||
})
|
||
.map(|(si, pos)| (key_si_to_units(vm, si), si, Some(pos)))
|
||
.collect()
|
||
};
|
||
|
||
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||
let mut first = true;
|
||
for (units, si, read_pos) in entries {
|
||
// 规范序 Get → toJSON → replacer(SerializeJSONProperty 步 1、2a、2b)。
|
||
// K = P 时值读经 Get(原型链 + 访问器),无白名单自身槽直读。
|
||
let val = match read_pos {
|
||
Some(pos) => read_json_property_value(vm, obj, obj_val, si, pos)?,
|
||
None => vm.ordinary_get(obj, si, obj_val).map_err(|msg| {
|
||
vm.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg))
|
||
})?,
|
||
};
|
||
let val = call_to_json(vm, val, &units)?;
|
||
|
||
// replacer 函数回调。
|
||
let val = if let Some(replacer) = ctx.replacer_fn {
|
||
let key_val = vm.new_string_units_owned(units.clone());
|
||
match vm.call_function_sync(replacer, obj_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() || val.is_symbol() || is_function {
|
||
continue;
|
||
}
|
||
|
||
if !first {
|
||
push_ascii(ctx.out, ",");
|
||
}
|
||
first = false;
|
||
|
||
if has_space {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level + 1 {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
stringify_string_units(&units, ctx.out);
|
||
push_ascii(ctx.out, ":");
|
||
push_ascii(ctx.out, " ");
|
||
} else {
|
||
stringify_string_units(&units, ctx.out);
|
||
push_ascii(ctx.out, ":");
|
||
}
|
||
|
||
jsvalue_to_json(vm, val, ctx.child())?;
|
||
}
|
||
|
||
if has_space && !first {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
}
|
||
push_ascii(ctx.out, "}");
|
||
Ok(())
|
||
}
|
||
|
||
fn stringify_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||
let has_space = !ctx.space.is_empty();
|
||
push_ascii(ctx.out, "[");
|
||
|
||
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
|
||
let len = obj.prop_count() as usize;
|
||
for i in 0..len {
|
||
if i > 0 {
|
||
push_ascii(ctx.out, ",");
|
||
}
|
||
|
||
if has_space {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level + 1 {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
}
|
||
|
||
let index_str = i.to_string();
|
||
let index_units: Vec<u16> = index_str.encode_utf16().collect();
|
||
|
||
// 规范序 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) = ctx.replacer_fn {
|
||
let key_val = vm.new_string(&index_str);
|
||
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
|
||
Ok(v) => {
|
||
if v.is_undefined() {
|
||
push_ascii(ctx.out, "null");
|
||
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 is_function || val.is_undefined() {
|
||
push_ascii(ctx.out, "null");
|
||
} else {
|
||
jsvalue_to_json(vm, val, ctx.child())?;
|
||
}
|
||
}
|
||
|
||
if has_space && len > 0 {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
}
|
||
push_ascii(ctx.out, "]");
|
||
Ok(())
|
||
}
|
||
|
||
/// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链),
|
||
/// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。
|
||
fn stringify_typed_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
|
||
let has_space = !ctx.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)?;
|
||
// live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。
|
||
let len = crate::typed_array::ta_live_length(view);
|
||
push_ascii(ctx.out, "{");
|
||
|
||
// 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、
|
||
// 非索引名经 Get 读命名键如 length);无白名单走升序元素序。
|
||
let entries: Vec<(Vec<u16>, JsValue)> = if let Some(whitelist) = ctx.replacer_whitelist {
|
||
let mut list: Vec<(Vec<u16>, JsValue)> = Vec::new();
|
||
for name in whitelist {
|
||
let si = vm.string_key_si(name);
|
||
let val = if is_int_key(si) {
|
||
let idx = int_key_value(si);
|
||
if idx as usize >= len {
|
||
continue;
|
||
}
|
||
crate::typed_array::typed_array_element_get(vm, obj, idx)
|
||
} else {
|
||
vm.ordinary_get(obj, si, this_val)
|
||
}
|
||
.map_err(|msg| crate::error::create_type_error(vm, &msg))?;
|
||
list.push((key_si_to_units(vm, si), val));
|
||
}
|
||
list
|
||
} else {
|
||
let mut list: Vec<(Vec<u16>, JsValue)> = Vec::with_capacity(len);
|
||
for i in 0..len {
|
||
let val = crate::typed_array::typed_array_element_get(vm, obj, i as u32)
|
||
.map_err(|msg| crate::error::create_type_error(vm, &msg))?;
|
||
list.push((i.to_string().encode_utf16().collect(), val));
|
||
}
|
||
list
|
||
};
|
||
|
||
let mut first = true;
|
||
for (index_units, val) in entries {
|
||
let val = call_to_json(vm, val, &index_units)?;
|
||
|
||
// replacer 函数回调。
|
||
let val = if let Some(replacer) = ctx.replacer_fn {
|
||
let key_val = vm.new_string_units_owned(index_units.clone());
|
||
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() || val.is_symbol() || is_function {
|
||
continue;
|
||
}
|
||
|
||
if !first {
|
||
push_ascii(ctx.out, ",");
|
||
}
|
||
first = false;
|
||
|
||
if has_space {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level + 1 {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
stringify_string_units(&index_units, ctx.out);
|
||
push_ascii(ctx.out, ":");
|
||
push_ascii(ctx.out, " ");
|
||
} else {
|
||
stringify_string_units(&index_units, ctx.out);
|
||
push_ascii(ctx.out, ":");
|
||
}
|
||
|
||
jsvalue_to_json(vm, val, ctx.child())?;
|
||
}
|
||
|
||
if has_space && !first {
|
||
push_ascii(ctx.out, "\n");
|
||
for _ in 0..ctx.indent_level {
|
||
ctx.out.extend_from_slice(ctx.space);
|
||
}
|
||
}
|
||
push_ascii(ctx.out, "}");
|
||
Ok(())
|
||
}
|