mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 符号原语数值与字符串转换改抛类型错误
This commit is contained in:
@@ -87,6 +87,10 @@ pub fn bigint_to_string(v: &num_bigint::BigInt) -> String {
|
||||
///
|
||||
/// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`;
|
||||
/// Object 与 Symbol 不在此处理,返回 `NaN`(对象需走 [`to_number_full`])。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// 本函数无异常通道,Symbol 的 `TypeError`(ECMA-262 §7.1.4)由调用方的
|
||||
/// ToNumber 入口先行拦截,不得把 Symbol 传入后取 `NaN` 当结果。
|
||||
pub fn to_number(val: JsValue) -> f64 {
|
||||
match val.js_type() {
|
||||
JsType::Int => val.as_int() as f64,
|
||||
@@ -350,6 +354,10 @@ pub fn push_to_string(val: JsValue, buf: &mut String) {
|
||||
///
|
||||
/// 数值走 ECMA-262 Number::toString 格式化;Object 在此返回占位符 `[object]`,
|
||||
/// 完整路径见 [`to_string_full`]。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// 本函数无异常通道,Symbol 的 `TypeError`(ECMA-262 §7.1.17)由调用方的
|
||||
/// ToString 入口先行拦截,不得把 Symbol 传入后取结果。
|
||||
pub fn to_string(val: JsValue) -> String {
|
||||
if val.is_int() {
|
||||
return val.as_int().to_string();
|
||||
@@ -381,6 +389,9 @@ pub fn to_string(val: JsValue) -> String {
|
||||
/// 把任意值转为 UTF-16 单元序列(`ToPrimitive(string hint)` 的单元口径):
|
||||
/// 字符串值直接借出单元(Flat 惰性编码、rope 扁平化);Symbol 返回 TypeError
|
||||
/// 错误;其余值经 ToString 原始值路径后 UTF-16 编码。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// 非字符串原始值经 [`to_string`] 编码,故调用方须已排除 Symbol(§7.1.17)。
|
||||
pub fn to_units_full<H: VmHost>(val: JsValue, host: &mut H) -> Result<Vec<u16>, String> {
|
||||
if val.is_string() {
|
||||
return Ok(host.string_units(val).into_owned());
|
||||
@@ -399,6 +410,10 @@ pub fn to_units_full<H: VmHost>(val: JsValue, host: &mut H) -> Result<Vec<u16>,
|
||||
|
||||
/// 把值追加其 UTF-16 单元到 `buf`:字符串值直接扩单元序列,非字符串值经
|
||||
/// ToString 后 UTF-16 编码。字符串拼接的单元通道路径(不可 lossy)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// 本函数无异常通道,Symbol 的 `TypeError`(ECMA-262 §7.1.17)由调用方的
|
||||
/// 字符串拼接入口先行拦截,不得把 Symbol 传入(否则被静默丢弃为空单元)。
|
||||
pub fn push_units_to(val: JsValue, buf: &mut Vec<u16>) {
|
||||
if val.is_string() {
|
||||
// SAFETY: val 为字符串值,借用即时消费。
|
||||
@@ -954,22 +969,7 @@ pub fn well_known_symbol_id<H: VmHost + ?Sized>(host: &H, ptr: *mut JsObject) ->
|
||||
|
||||
/// well-known symbol id 的描述名(如 id 9 对应 `Symbol.toStringTag`)。
|
||||
pub fn well_known_symbol_name(id: u32) -> Option<&'static str> {
|
||||
Some(match id {
|
||||
0 => "Symbol.iterator",
|
||||
1 => "Symbol.match",
|
||||
2 => "Symbol.replace",
|
||||
3 => "Symbol.search",
|
||||
4 => "Symbol.split",
|
||||
5 => "Symbol.toPrimitive",
|
||||
6 => "Symbol.hasInstance",
|
||||
7 => "Symbol.matchAll",
|
||||
8 => "Symbol.asyncIterator",
|
||||
9 => "Symbol.toStringTag",
|
||||
10 => "Symbol.species",
|
||||
11 => "Symbol.asyncDispose",
|
||||
12 => "Symbol.dispose",
|
||||
_ => return None,
|
||||
})
|
||||
oxide_types::private_key::WELL_KNOWN_SYMBOL_NAMES.get(id as usize).copied()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -56,6 +56,54 @@ pub const SYMBOL_KEY_BASE: u32 = 0xA000_0000;
|
||||
/// `Symbol.asyncDispose` 与 `Symbol.dispose`(显式资源管理提案)。
|
||||
pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 13;
|
||||
|
||||
/// well-known symbol 的名称表,索引即 well-known symbol id。
|
||||
///
|
||||
/// 表序与 `id` 严格一致,供 Symbol 构造器属性绑定与符号描述查询共用;新增
|
||||
/// well-known symbol 时只改此处(连同 [`WELL_KNOWN_SYMBOL_COUNT`] 与具名 id 常量),
|
||||
/// 避免 id/名映射在多处各自维护造成错位。
|
||||
pub const WELL_KNOWN_SYMBOL_NAMES: [&str; WELL_KNOWN_SYMBOL_COUNT as usize] = [
|
||||
"Symbol.iterator",
|
||||
"Symbol.match",
|
||||
"Symbol.replace",
|
||||
"Symbol.search",
|
||||
"Symbol.split",
|
||||
"Symbol.toPrimitive",
|
||||
"Symbol.hasInstance",
|
||||
"Symbol.matchAll",
|
||||
"Symbol.asyncIterator",
|
||||
"Symbol.toStringTag",
|
||||
"Symbol.species",
|
||||
"Symbol.asyncDispose",
|
||||
"Symbol.dispose",
|
||||
];
|
||||
|
||||
/// well-known symbol 具名 id:`0`(键值 = `SYMBOL_KEY_BASE + id`)。
|
||||
pub const WELL_KNOWN_SYMBOL_ITERATOR: u32 = 0;
|
||||
/// well-known symbol 具名 id:`Symbol.match`。
|
||||
pub const WELL_KNOWN_SYMBOL_MATCH: u32 = 1;
|
||||
/// well-known symbol 具名 id:`Symbol.replace`。
|
||||
pub const WELL_KNOWN_SYMBOL_REPLACE: u32 = 2;
|
||||
/// well-known symbol 具名 id:`Symbol.search`。
|
||||
pub const WELL_KNOWN_SYMBOL_SEARCH: u32 = 3;
|
||||
/// well-known symbol 具名 id:`Symbol.split`。
|
||||
pub const WELL_KNOWN_SYMBOL_SPLIT: u32 = 4;
|
||||
/// well-known symbol 具名 id:`Symbol.toPrimitive`。
|
||||
pub const WELL_KNOWN_SYMBOL_TO_PRIMITIVE: u32 = 5;
|
||||
/// well-known symbol 具名 id:`Symbol.hasInstance`。
|
||||
pub const WELL_KNOWN_SYMBOL_HAS_INSTANCE: u32 = 6;
|
||||
/// well-known symbol 具名 id:`Symbol.matchAll`。
|
||||
pub const WELL_KNOWN_SYMBOL_MATCH_ALL: u32 = 7;
|
||||
/// well-known symbol 具名 id:`Symbol.asyncIterator`。
|
||||
pub const WELL_KNOWN_SYMBOL_ASYNC_ITERATOR: u32 = 8;
|
||||
/// well-known symbol 具名 id:`Symbol.toStringTag`。
|
||||
pub const WELL_KNOWN_SYMBOL_TO_STRING_TAG: u32 = 9;
|
||||
/// well-known symbol 具名 id:`Symbol.species`。
|
||||
pub const WELL_KNOWN_SYMBOL_SPECIES: u32 = 10;
|
||||
/// well-known symbol 具名 id:`Symbol.asyncDispose`。
|
||||
pub const WELL_KNOWN_SYMBOL_ASYNC_DISPOSE: u32 = 11;
|
||||
/// well-known symbol 具名 id:`Symbol.dispose`。
|
||||
pub const WELL_KNOWN_SYMBOL_DISPOSE: u32 = 12;
|
||||
|
||||
/// 用户 symbol 下标的保留位掩码(低 28 位)。
|
||||
const SYMBOL_INDEX_MASK: u32 = 0x0FFF_FFFF;
|
||||
|
||||
@@ -83,15 +131,19 @@ pub const fn is_symbol_key(key: u32) -> bool {
|
||||
key >= SYMBOL_KEY_BASE
|
||||
}
|
||||
|
||||
/// 把用户 symbol 的表下标编码为属性键。
|
||||
/// 把用户 symbol 的全局下标编码为属性键。
|
||||
///
|
||||
/// 键 = Symbol 区间起点 + well-known 预留槽 + 下标(截断到 28 位防加法回绕)。
|
||||
/// 传入的是 interner 分配的**全局**下标(用户符号从 [`WELL_KNOWN_SYMBOL_COUNT`]
|
||||
/// 起,见 VM 的 `SymbolState::intern`),编码时再叠加一次 well-known 预留槽偏移。
|
||||
/// 两段偏移使 `[SYMBOL_KEY_BASE + WELL_KNOWN_SYMBOL_COUNT, SYMBOL_KEY_BASE + 2 *
|
||||
/// WELL_KNOWN_SYMBOL_COUNT)` 成为空档;与 [`symbol_index_from_key`] 减去同一常量,
|
||||
/// 往返自洽,空档不落任何产键路径。
|
||||
#[inline]
|
||||
pub const fn make_symbol_key(idx: u32) -> u32 {
|
||||
SYMBOL_KEY_BASE + WELL_KNOWN_SYMBOL_COUNT + (idx & SYMBOL_INDEX_MASK)
|
||||
}
|
||||
|
||||
/// 从用户 symbol 键反解表下标(与 [`make_symbol_key`] 互逆)。
|
||||
/// 从用户 symbol 键反解全局下标(与 [`make_symbol_key`] 互逆)。
|
||||
#[inline]
|
||||
pub const fn symbol_index_from_key(key: u32) -> u32 {
|
||||
(key - SYMBOL_KEY_BASE - WELL_KNOWN_SYMBOL_COUNT) & SYMBOL_INDEX_MASK
|
||||
|
||||
@@ -46,23 +46,10 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
);
|
||||
|
||||
// well-known symbol 以符号原语绑定,下标与内建符号表(0..WELL_KNOWN_SYMBOL_COUNT)
|
||||
// 一一对应;键编码由该下标直接推出。
|
||||
for (name, id) in [
|
||||
("match", 1u32),
|
||||
("replace", 2),
|
||||
("search", 3),
|
||||
("split", 4),
|
||||
("iterator", 0),
|
||||
("toPrimitive", 5),
|
||||
("hasInstance", 6),
|
||||
("matchAll", 7),
|
||||
("asyncIterator", 8),
|
||||
("toStringTag", 9),
|
||||
("species", 10),
|
||||
("asyncDispose", 11),
|
||||
("dispose", 12),
|
||||
] {
|
||||
bind_well_known_symbol(core, ctor, name, JsValue::symbol(id));
|
||||
// 一一对应;名称表是 id/名映射的唯一来源,属性名去掉 `Symbol.` 前缀。
|
||||
for (id, full_name) in oxide_types::private_key::WELL_KNOWN_SYMBOL_NAMES.iter().enumerate() {
|
||||
let name = full_name.strip_prefix("Symbol.").unwrap_or(full_name);
|
||||
bind_well_known_symbol(core, ctor, name, JsValue::symbol(id as u32));
|
||||
}
|
||||
|
||||
bind_constructor!(core, global, "Symbol", ctor_ptr, oxide_builtins::symbol::symbol_constructor::<crate::vm::Vm>, 1, hash: true);
|
||||
|
||||
@@ -468,7 +468,8 @@ fn bind_iterator_protos(core: &Arc<KernelCore>, session: &KernelSession) {
|
||||
oxide_builtins::iterator::iterator_constructor_setter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
}
|
||||
let sym_to_string_tag = oxide_types::private_key::make_well_known_symbol_key(9);
|
||||
let sym_to_string_tag =
|
||||
oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_TO_STRING_TAG);
|
||||
if core
|
||||
.shape_forge()
|
||||
.lookup_position(iter_proto.shape_id(), sym_to_string_tag)
|
||||
@@ -487,13 +488,14 @@ fn bind_iterator_protos(core: &Arc<KernelCore>, session: &KernelSession) {
|
||||
}
|
||||
|
||||
// %IteratorPrototype% 的 @@dispose:显式资源管理下用 return 关闭迭代器。
|
||||
let sym_dispose = oxide_types::private_key::make_well_known_symbol_key(12);
|
||||
let dispose_id = oxide_types::private_key::WELL_KNOWN_SYMBOL_DISPOSE;
|
||||
let sym_dispose = oxide_types::private_key::make_well_known_symbol_key(dispose_id);
|
||||
if core.shape_forge().lookup_position(iter_proto.shape_id(), sym_dispose).is_none() {
|
||||
bind_well_known_method(
|
||||
world,
|
||||
core,
|
||||
iter_proto,
|
||||
12,
|
||||
dispose_id,
|
||||
"[Symbol.dispose]",
|
||||
oxide_builtins::iterator::iterator_dispose::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
|
||||
@@ -56,6 +56,37 @@ fn int_mul(a: i32, b: i32) -> JsValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// `+` 系运算在数值/字符串分流前的 Symbol 守卫。
|
||||
///
|
||||
/// 任一操作数为 Symbol 时规范都要求抛 `TypeError`:字符串分支走 ToString、数值
|
||||
/// 分支走 ToNumber(ECMA-262 §7.1.4 / §7.1.17)。消息按分流走向取对应文本;
|
||||
/// 两侧须已过 ToPrimitive。
|
||||
///
|
||||
/// `return` 直接返回抛出结果,避免已展开到 try 处理器后继续执行后续分支造成二次抛错。
|
||||
macro_rules! reject_symbol_add {
|
||||
($self:ident, $lhs:expr, $rhs:expr) => {{
|
||||
let (lhs, rhs) = ($lhs, $rhs);
|
||||
if lhs.is_symbol() || rhs.is_symbol() {
|
||||
let msg = if lhs.is_string() || rhs.is_string() {
|
||||
"Cannot convert a Symbol value to a string"
|
||||
} else {
|
||||
"Cannot convert a Symbol value to a number"
|
||||
};
|
||||
return $self.raise_type_error(msg);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// 纯数值算术(减/乘/除/模/幂等)的 Symbol 守卫:ToNumber(Symbol) 抛 `TypeError`
|
||||
/// (ECMA-262 §7.1.4)。仅检查最终原始值,对象操作数须先经 ToPrimitive。
|
||||
macro_rules! reject_symbol_number {
|
||||
($self:ident, $l:expr, $r:expr) => {{
|
||||
if $l.is_symbol() || $r.is_symbol() {
|
||||
return $self.raise_type_error("Cannot convert a Symbol value to a number");
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
@@ -79,6 +110,7 @@ impl Vm {
|
||||
// 必须放在 coerce 之后。
|
||||
let lhs = self.coerce_primitive_bounded(lv, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_add!(self, lhs, rhs);
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
self.regs[rd] = self.concat_strings(lhs, rhs);
|
||||
return Ok(());
|
||||
@@ -208,6 +240,7 @@ impl Vm {
|
||||
// 顺序与左结合 ADD 一致。
|
||||
let lhs = self.coerce_primitive_bounded(acc, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(next, false)?;
|
||||
reject_symbol_add!(self, lhs, rhs);
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
// ── 阶段 2:字符串模式单趟预分配 ──
|
||||
self.regs[rd] = self.concat_n_strings(lhs, rhs, &ops[idx + 1..])?;
|
||||
@@ -248,6 +281,11 @@ impl Vm {
|
||||
for ® in rest {
|
||||
let v = self.regs[reg];
|
||||
let prim = if v.is_object() { self.coerce_primitive_bounded(v, false)? } else { v };
|
||||
// 字符串模式对全部操作数做 ToString,Symbol 按 §7.1.17 抛 TypeError。
|
||||
if prim.is_symbol() {
|
||||
self.raise_type_error("Cannot convert a Symbol value to a string")?;
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
parts.push(prim);
|
||||
}
|
||||
let cap = parts.iter().fold(0usize, |acc, p| {
|
||||
@@ -288,6 +326,7 @@ impl Vm {
|
||||
if prim.is_bigint() {
|
||||
return self.raise_type_error("Cannot convert a BigInt value to a number");
|
||||
}
|
||||
reject_symbol_number!(self, prim, prim);
|
||||
let n = coercion::to_number(prim);
|
||||
self.regs[rd] = JsValue::float(n);
|
||||
Ok(())
|
||||
@@ -311,6 +350,7 @@ impl Vm {
|
||||
}
|
||||
let lhs = self.coerce_primitive_bounded(lv, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_add!(self, lhs, rhs);
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
self.regs[rd] = self.concat_strings(lhs, rhs);
|
||||
return Ok(());
|
||||
@@ -349,6 +389,7 @@ impl Vm {
|
||||
}
|
||||
let l = self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_number!(self, l, r);
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(l) - self.bigint_value(r));
|
||||
return Ok(());
|
||||
@@ -381,6 +422,7 @@ impl Vm {
|
||||
}
|
||||
let l = self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_number!(self, l, r);
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
self.regs[rd] = self.new_bigint(self.bigint_value(l) * self.bigint_value(r));
|
||||
return Ok(());
|
||||
@@ -417,6 +459,7 @@ impl Vm {
|
||||
}
|
||||
let l = self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_number!(self, l, r);
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
let rv = self.bigint_value(r);
|
||||
if bigint_is_zero(rv) {
|
||||
@@ -465,6 +508,7 @@ impl Vm {
|
||||
}
|
||||
let l = self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
reject_symbol_number!(self, l, r);
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
let rv = self.bigint_value(r);
|
||||
if bigint_is_zero(rv) {
|
||||
@@ -509,6 +553,10 @@ impl Vm {
|
||||
}
|
||||
let l = self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = self.coerce_primitive_bounded(rv, false)?;
|
||||
if l.is_symbol() || r.is_symbol() {
|
||||
self.raise_type_error("Cannot convert a Symbol value to a number")?;
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
let base = self.bigint_value(l).clone();
|
||||
let exp = self.bigint_value(r).clone();
|
||||
|
||||
@@ -193,6 +193,16 @@ impl Vm {
|
||||
};
|
||||
let prop_val = self.coerce_primitive_bounded(prop_raw, false)?;
|
||||
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
if prop_val.is_symbol() || rhs.is_symbol() {
|
||||
// 复合成员加法分流同 `+`:字符串分支走 ToString、数值分支走 ToNumber,
|
||||
// 两者对 Symbol 均抛 TypeError(ECMA-262 §7.1.4 / §7.1.17)。
|
||||
let msg = if prop_val.is_string() || rhs.is_string() {
|
||||
"Cannot convert a Symbol value to a string"
|
||||
} else {
|
||||
"Cannot convert a Symbol value to a number"
|
||||
};
|
||||
return self.raise_type_error(msg);
|
||||
}
|
||||
let new_val = if prop_val.is_string() || rhs.is_string() {
|
||||
let ls = coercion::to_string(prop_val);
|
||||
let rs = coercion::to_string(rhs);
|
||||
|
||||
@@ -31,7 +31,11 @@ macro_rules! binary_arith {
|
||||
// `{valueOf: () => 2n}` 需先 ToPrimitive 再判定),统一放 coerce 之后。
|
||||
let l = $self.coerce_primitive_bounded(lv, false)?;
|
||||
let r = $self.coerce_primitive_bounded(rv, false)?;
|
||||
if l.is_bigint() && r.is_bigint() {
|
||||
if l.is_symbol() || r.is_symbol() {
|
||||
// ToNumber(Symbol) 抛 TypeError(ECMA-262 §7.1.4),先于 BigInt 混合判定。
|
||||
$self.raise_type_error("Cannot convert a Symbol value to a number")?;
|
||||
$self.regs[$rd] = JsValue::undefined();
|
||||
} else if l.is_bigint() && r.is_bigint() {
|
||||
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) / 2n)。
|
||||
let lv = $self.bigint_value(l).clone();
|
||||
let rv = $self.bigint_value(r).clone();
|
||||
|
||||
@@ -5,15 +5,12 @@ use oxide_runtime_api as coercion;
|
||||
|
||||
use oxide_types::error::JsError;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::private_key::make_well_known_symbol_key;
|
||||
use oxide_types::private_key::{make_well_known_symbol_key, WELL_KNOWN_SYMBOL_TO_PRIMITIVE};
|
||||
use oxide_types::value::{JsValue, PTR_MASK};
|
||||
|
||||
use super::{format_error_message, js_error_kind, js_error_kind_name, Vm};
|
||||
use crate::vm_debug;
|
||||
|
||||
/// `Symbol.toPrimitive` 的 well-known symbol 下标。
|
||||
const TO_PRIMITIVE_SYMBOL_ID: u32 = 5;
|
||||
|
||||
impl Vm {
|
||||
/// ToPrimitive 有界版:按规范序把对象转为原始值,失败统一抛 `TypeError`。
|
||||
///
|
||||
@@ -51,7 +48,7 @@ impl Vm {
|
||||
|
||||
// ECMA-262 §7.1.1 step 1:exotic 对象上(沿原型链读取)的 Symbol.toPrimitive 优先于
|
||||
// OrdinaryToPrimitive;well-known symbol 键恒定,直接取保留槽。
|
||||
let sym_si = make_well_known_symbol_key(TO_PRIMITIVE_SYMBOL_ID);
|
||||
let sym_si = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_TO_PRIMITIVE);
|
||||
let exotic = {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
self.ordinary_get(obj, sym_si, value)?
|
||||
@@ -148,8 +145,16 @@ impl Vm {
|
||||
}
|
||||
|
||||
/// ToNumber 有界版:先按 number hint 做 ToPrimitive,再转 `f64`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// 原始值为 Symbol 时按规范(ECMA-262 §7.1.4)抛 `TypeError`,不落入
|
||||
/// [`coercion::to_number`] 的宽松快路径(后者对该输入返回 `NaN`)。
|
||||
pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> {
|
||||
let primitive = self.coerce_primitive_bounded(value, false)?;
|
||||
if primitive.is_symbol() {
|
||||
self.conversion_error("Cannot convert a Symbol value to a number")?;
|
||||
return Ok(f64::NAN);
|
||||
}
|
||||
Ok(coercion::to_number(primitive))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@ use oxide_runtime_api::{push_units_to, to_boolean, to_units_full};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::{
|
||||
int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key,
|
||||
WELL_KNOWN_SYMBOL_HAS_INSTANCE,
|
||||
};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// `Symbol.hasInstance` 的 well-known symbol 下标。
|
||||
const HAS_INSTANCE_SYMBOL_ID: u32 = 6;
|
||||
|
||||
impl Vm {
|
||||
pub(crate) fn dispatch_new_expression(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
|
||||
let constructor_reg = a;
|
||||
@@ -319,7 +317,7 @@ impl Vm {
|
||||
return self.raise_type_error("INSTANCEOF right-hand side is not callable");
|
||||
}
|
||||
|
||||
let has_instance_si = make_well_known_symbol_key(HAS_INSTANCE_SYMBOL_ID);
|
||||
let has_instance_si = make_well_known_symbol_key(WELL_KNOWN_SYMBOL_HAS_INSTANCE);
|
||||
|
||||
let ctor_obj = unsafe { &*rhs_val.as_js_object_ptr() };
|
||||
let has_instance_val = self.ordinary_get(ctor_obj, has_instance_si, rhs_val)?;
|
||||
|
||||
@@ -138,8 +138,6 @@ fn to_primitive_symbol_hint_invoked() {
|
||||
}
|
||||
|
||||
// ToNumber:to_number_full 对 Symbol 抛 TypeError。
|
||||
// 注意:一元加号路径用 coerce_number_bounded(对 Symbol 不抛),因此运算符层的
|
||||
// +Symbol() 得到 NaN;见下个测试。
|
||||
#[test]
|
||||
fn to_number_full_symbol_throws() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -149,10 +147,65 @@ fn to_number_full_symbol_throws() {
|
||||
assert!(result.unwrap_err().contains("Symbol"), "expected a Symbol TypeError message");
|
||||
}
|
||||
|
||||
// 运算符层实况:+Symbol 为 NaN(Symbol 抛出未接入算术路径)。
|
||||
// 运算符层:一元加号对 Symbol 抛 TypeError(ToNumber(Symbol),§7.1.4)。
|
||||
#[test]
|
||||
fn unary_plus_symbol_is_nan() {
|
||||
assert_eq!(eval("+Symbol('x')"), "NaN");
|
||||
fn unary_plus_symbol_throws() {
|
||||
let err = eval("+Symbol('x')");
|
||||
assert!(err.contains("TypeError"), "expected TypeError for unary +symbol, got {err}");
|
||||
}
|
||||
|
||||
// 运算符层:二元 `+` 的数值与字符串两条分支对 Symbol 均抛 TypeError。
|
||||
#[test]
|
||||
fn add_symbol_throws_both_branches() {
|
||||
for source in [
|
||||
"Symbol('x') + ''",
|
||||
"'' + Symbol('x')",
|
||||
"Symbol('x') + 0",
|
||||
"0 + Symbol('x')",
|
||||
"Symbol('x') + Symbol('y')",
|
||||
"Symbol.iterator + {}",
|
||||
] {
|
||||
let err = eval(source);
|
||||
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
|
||||
}
|
||||
}
|
||||
|
||||
// 运算符层:其余数值算术(减/乘/除/模/幂/位运算/复合赋值)对 Symbol 抛 TypeError。
|
||||
#[test]
|
||||
fn numeric_operators_symbol_throw() {
|
||||
for source in [
|
||||
"Symbol('x') - 1",
|
||||
"Symbol('x') * 2",
|
||||
"Symbol('x') / 2",
|
||||
"Symbol('x') % 2",
|
||||
"Symbol('x') ** 2",
|
||||
"Symbol('x') | 0",
|
||||
"(-Symbol('x'))",
|
||||
"var v = Symbol('x'); v += ''",
|
||||
"var v = Symbol('x'); v -= 1",
|
||||
] {
|
||||
let err = eval(source);
|
||||
assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}");
|
||||
}
|
||||
}
|
||||
|
||||
// Number 构造器对 Symbol 抛 TypeError(ToNumber 语义),不走 NaN 兜底。
|
||||
#[test]
|
||||
fn number_constructor_symbol_throws() {
|
||||
let err = eval("Number(Symbol('x'))");
|
||||
assert!(err.contains("TypeError"), "expected TypeError for Number(symbol), got {err}");
|
||||
}
|
||||
|
||||
// String 构造器的 Symbol 专属分支保留:返回 `Symbol(desc)`,不经 ToString。
|
||||
// 模板字面量则走 ToString,Symbol 仍抛 TypeError。
|
||||
#[test]
|
||||
fn string_constructor_and_template_symbol() {
|
||||
let (vm, result) = eval_ty("String(Symbol('x'))").expect("String(Symbol) 求值失败");
|
||||
assert_eq!(vm.lookup_str(result).as_deref(), Some("Symbol(x)"));
|
||||
let (vm, result) = eval_ty("Symbol('x').description").expect("description 求值失败");
|
||||
assert_eq!(vm.lookup_str(result).as_deref(), Some("x"));
|
||||
let err = eval("`v=${Symbol('x')}`");
|
||||
assert!(err.contains("TypeError"), "expected TypeError for template symbol, got {err}");
|
||||
}
|
||||
|
||||
// 抽象相等:字符串与数字互相强转。
|
||||
|
||||
@@ -96,12 +96,12 @@ fn concat_n_call_side_effect_order() {
|
||||
|
||||
#[test]
|
||||
fn concat_n_symbol_matches_binary_add_behavior() {
|
||||
// Symbol 在字符串模式下被静默忽略(既有 concat_strings 行为),CONCAT_N 与二元
|
||||
// ADD 结果逐字节一致,不放大不修复。
|
||||
let flat = eval_str("var s = Symbol(); 'a' + s + 'b'");
|
||||
let binary = eval_str("var s = Symbol(); ('a' + s) + 'b'");
|
||||
assert_eq!(flat, binary);
|
||||
assert_eq!(flat, "ab");
|
||||
// Symbol 在字符串模式下按 §7.1.17 抛 TypeError;CONCAT_N 与二元 ADD 行为一致,
|
||||
// 不放大不遗漏。
|
||||
let flat = eval(&mut Vm::new(), "var s = Symbol(); 'a' + s + 'b'").unwrap_err();
|
||||
let binary = eval(&mut Vm::new(), "var s = Symbol(); ('a' + s) + 'b'").unwrap_err();
|
||||
assert!(flat.contains("TypeError"), "expected TypeError, got {flat}");
|
||||
assert!(binary.contains("TypeError"), "expected TypeError, got {binary}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user