mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
emit/kernel/vm/builtins: 常量池键编码 + 键物化真实串值面 + 16 回归钉——emit 侧字面量/键/模板统一改存无标志键编码:lone_surrogates 字面量与模板 cooked 按 oxc marker 解码还原真单元序列(FFFD+hex4≠fffd→surrogate 单元、FFFD+fffd→FFFD、FFFD+非 hex 防御臂),无标志串 encode_key(encode_utf16()) 入池(BMP 无 FFFD 键恒等 = 现值,池条目零漂移),string_pool 集中收口 9 处入池点;interner string_ptr 由单元物化(含孤立→FlatU16、良形→Flat);键物化面改真实 JS 串值(key_si_to_js_value 经 decode_key,Object.keys/ownKeys/entries/Reflect.ownKeys/json reviver 键同源;整数键反解数字串);批内修复三处子集 A/B 差分撞出回归:replace $n 展开按 spec GetSubstitution 重写(digitCount 2/1、越界回退 1 位、越界含 $0 原文输出,node 13 用例对照钉住)、String 构造器 Symbol 路径按 new.target 分流(String(sym)→描述串、new String(sym)→TypeError,恢复 to_string_for_string_constructor)、padStart/padEnd 未给 fill 时按单空格补(原 lossy 串取 'un' 杂质,pre-existing 一并归零);迭代器 dispose 钉改回真值断言(String(Symbol.dispose) 现合法返回描述串);property-escapes 613/0/0(B167 基线 582/31/0,31 归零)、String 子集 963/250/10 vs BASE 949/264/10(14 修 0 回归)、for-of +1(string-astral-truncated)、五 crate 113 套件 2680/0、clippy/fmt 零新增(JSON 面与全量 test262 收尾在 131.2 后续 commit 闭合)
This commit is contained in:
Generated
+1
@@ -1205,6 +1205,7 @@ dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxide_bytecode",
|
||||
"oxide_ir",
|
||||
"oxide_kernel",
|
||||
"oxide_log",
|
||||
"oxide_parser",
|
||||
"regex",
|
||||
|
||||
@@ -91,9 +91,8 @@ fn walk_reviver<H: VmHost>(
|
||||
}
|
||||
}
|
||||
|
||||
// 对当前值调用 reviver,用返回值覆盖属性槽。
|
||||
let key_str = key_si_to_string(vm, key_si);
|
||||
let key_val = vm.new_string(&key_str);
|
||||
// 对当前值调用 reviver,用返回值覆盖属性槽(键物化为 JS 可见串值,含孤立 surrogate 单元)。
|
||||
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) => {
|
||||
|
||||
@@ -88,6 +88,9 @@ fn int_or_string_index<H: VmHost>(vm: &H, si: u32) -> Option<u32> {
|
||||
}
|
||||
|
||||
/// 键 si 物化为字符串文本:整数键反解数字串,其余查 interner。
|
||||
///
|
||||
/// 注意返回的是**键空间文本**(孤立 surrogate 为 FFFD+hex4 转义形态),仅可用于
|
||||
/// 键比较/数字判定等键空间用途;产 JS 可见串值用 `key_si_to_js_value`。
|
||||
pub fn key_si_to_string<H: VmHost>(vm: &H, si: u32) -> String {
|
||||
if is_int_key(si) {
|
||||
int_key_value(si).to_string()
|
||||
@@ -96,6 +99,22 @@ pub fn key_si_to_string<H: VmHost>(vm: &H, si: u32) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// 键 si 物化为 JS 可见字符串值(Object.keys / ownKeys / entries 族):
|
||||
/// 整数键反解数字串,字符串键经 `decode_key` 还原单元序列(孤立 surrogate
|
||||
/// 物化为 FlatU16,良形键与旧行为逐位一致)。
|
||||
pub fn key_si_to_js_value<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
|
||||
if is_int_key(si) {
|
||||
return vm.new_string(&int_key_value(si).to_string());
|
||||
}
|
||||
let units = vm
|
||||
.kernel_core()
|
||||
.perm_interner()
|
||||
.lookup(si)
|
||||
.map(oxide_kernel::string_forge::decode_key)
|
||||
.unwrap_or_default();
|
||||
vm.new_string_units_owned(units)
|
||||
}
|
||||
|
||||
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
|
||||
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键,供 `getOwnPropertySymbols` 使用。
|
||||
fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
|
||||
@@ -377,21 +396,16 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
|
||||
let key_names: Vec<String>;
|
||||
{
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let owned_keys: Vec<(u32, u32)> = keys
|
||||
.into_iter()
|
||||
.filter(|(_si, offset)| {
|
||||
obj.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
})
|
||||
.collect();
|
||||
key_names = owned_keys.iter().map(|(si, _offset)| key_si_to_string(vm, *si)).collect();
|
||||
}
|
||||
let n = key_names.len();
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj)
|
||||
.into_iter()
|
||||
.filter(|(_si, offset)| {
|
||||
obj.prop_meta_at(*offset)
|
||||
.map(|m| m.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
|
||||
})
|
||||
.collect();
|
||||
let n = owned_keys.len();
|
||||
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
@@ -399,10 +413,10 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
for (i, k) in key_names.iter().enumerate() {
|
||||
let str_val = vm.new_string(k);
|
||||
for (i, (si, _offset)) in owned_keys.iter().enumerate() {
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
unsafe {
|
||||
(*arr).set_prop_at(i, str_val);
|
||||
(*arr).set_prop_at(i, key_val);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
@@ -1009,13 +1023,9 @@ pub fn object_is_extensible<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
|
||||
|
||||
let key_names: Vec<String> = {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
keys.iter().map(|(si, _)| key_si_to_string(vm, *si)).collect()
|
||||
};
|
||||
|
||||
let n = key_names.len();
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let n = keys.len();
|
||||
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
@@ -1023,10 +1033,10 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
for (i, k) in key_names.iter().enumerate() {
|
||||
let str_val = vm.new_string(k);
|
||||
for (i, (si, _)) in keys.iter().enumerate() {
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
unsafe {
|
||||
(*arr).set_prop_at(i, str_val);
|
||||
(*arr).set_prop_at(i, key_val);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
@@ -1333,8 +1343,7 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
for (i, (si, offset)) in owned_keys.iter().enumerate() {
|
||||
let key_str = key_si_to_string(vm, *si);
|
||||
let key_val = vm.new_string(&key_str);
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
let val = obj.get_prop_at(*offset);
|
||||
let pair = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
|
||||
@@ -3,7 +3,7 @@ use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::object::{delete_own_property, key_si_to_string, walk_own_keys};
|
||||
use crate::object::{delete_own_property, key_si_to_js_value, walk_own_keys};
|
||||
|
||||
use oxide_runtime_api::{NativeResult, VmHost};
|
||||
|
||||
@@ -191,11 +191,25 @@ pub fn reflect_own_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return type_error(vm, "Reflect.ownKeys target is not an object");
|
||||
};
|
||||
let target = unsafe { &*target_ptr };
|
||||
let key_names: Vec<String> = walk_own_keys(vm, target)
|
||||
.into_iter()
|
||||
.map(|(si, _)| key_si_to_string(vm, si))
|
||||
.collect();
|
||||
NativeResult::Ok(make_string_array(vm, &key_names))
|
||||
let keys = walk_own_keys(vm, target);
|
||||
let n = keys.len();
|
||||
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(array_proto),
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
for (i, (si, _)) in keys.iter().enumerate() {
|
||||
let key_val = key_si_to_js_value(vm, *si);
|
||||
unsafe {
|
||||
(*arr).set_prop_at(i, key_val);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
/// `Reflect.preventExtensions(target)`:阻止扩展,返回 true。
|
||||
@@ -273,19 +287,3 @@ fn own_field<H: VmHost>(vm: &mut H, desc: JsValue, prop_si: u32) -> Option<JsVal
|
||||
vm.resolve_property(obj, prop_si)?;
|
||||
vm.ordinary_get(obj, prop_si, desc).ok()
|
||||
}
|
||||
|
||||
fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue {
|
||||
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto),
|
||||
parts.len(),
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
for (idx, part) in parts.iter().enumerate() {
|
||||
let value = vm.new_string(part);
|
||||
unsafe { &mut *arr }.set_prop_at(idx, value);
|
||||
}
|
||||
unsafe { &mut *arr }.set_prop_count(parts.len());
|
||||
JsValue::from_js_object(arr)
|
||||
}
|
||||
|
||||
@@ -409,22 +409,33 @@ fn expand_dollar_units(text: &[u16], m: ®ress::Match, replacement: &[u16]) ->
|
||||
} else if rest.starts_with(&[0x24, 0x27]) {
|
||||
out.extend_from_slice(&text[range.end..]);
|
||||
i += 2;
|
||||
} else {
|
||||
// $n(1-2 位数字)→ 捕获组;未匹配/越界 → 空串。
|
||||
let mut j = 1;
|
||||
while i + j < replacement.len() && (0x30..=0x39).contains(&replacement[i + j]) {
|
||||
j += 1;
|
||||
}
|
||||
if j == 0 {
|
||||
out.push(0x24);
|
||||
i += 1;
|
||||
} else if i + 1 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 1]) {
|
||||
// $digits:digitCount 取 2(后续尚有数字)否则 1;两位数值越界回退
|
||||
// 一位(次位留给后续按字面处理);仍越界(含 $0)→ 整段 ref 字面。
|
||||
let digit_count = if i + 2 < replacement.len() && (0x30..=0x39).contains(&replacement[i + 2]) {
|
||||
2
|
||||
} else {
|
||||
let n: u32 = (0..j).fold(0, |acc, k| acc * 10 + (replacement[i + k] - 0x30) as u32);
|
||||
if let Some(g) = m.group(n as usize) {
|
||||
1
|
||||
};
|
||||
let d1 = (replacement[i + 1] - 0x30) as u32;
|
||||
let mut index = if digit_count == 2 { d1 * 10 + (replacement[i + 2] - 0x30) as u32 } else { d1 };
|
||||
let mut ref_len = 1 + digit_count;
|
||||
let capture_len = (m.groups().count() - 1) as u32;
|
||||
if index > capture_len && digit_count == 2 {
|
||||
index = d1;
|
||||
ref_len = 2;
|
||||
}
|
||||
if (1..=capture_len).contains(&index) {
|
||||
if let Some(g) = m.group(index as usize) {
|
||||
out.extend_from_slice(&text[g.start..g.end]);
|
||||
}
|
||||
i += 1 + j;
|
||||
} else {
|
||||
out.extend_from_slice(&replacement[i..i + ref_len]);
|
||||
}
|
||||
i += ref_len;
|
||||
} else {
|
||||
out.push(0x24);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
out
|
||||
@@ -692,22 +703,6 @@ pub fn string_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// JS `String()` 构造逻辑:把参数转成字符串(单元保真);new 语义返回
|
||||
/// `[[StringData]]` 包装对象。
|
||||
pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let str_val = if args.len() > 1 {
|
||||
// 对象参数须经 ToPrimitive/ToString 完整转换(字符串结果按载荷形态保留)。
|
||||
match oxide_runtime_api::to_string_value_full(vm.reg(args[1]), vm) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
// ToString on an object may throw via toString/valueOf; propagate the original exception.
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vm.new_string("")
|
||||
};
|
||||
|
||||
let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let is_ctor = if this_val.is_object() {
|
||||
@@ -722,6 +717,30 @@ pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
false
|
||||
};
|
||||
|
||||
let str_val = if args.len() > 1 {
|
||||
let v = vm.reg(args[1]);
|
||||
if is_ctor && v.is_symbol() {
|
||||
// new String(Symbol):构造器路径走 ToString(规范步骤 3b),
|
||||
// 对 Symbol 抛 TypeError——与函数调用路径(3a 描述串)相异。
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||||
}
|
||||
// 对象参数须经 ToPrimitive/ToString 完整转换;函数调用路径的 Symbol
|
||||
// 走 SymbolDescriptiveString(规范步骤 3a,不经 ToString)。
|
||||
let v = oxide_runtime_api::to_string_for_string_constructor(v, vm);
|
||||
match v {
|
||||
Ok(s) => vm.new_string_owned(s),
|
||||
Err(_) => {
|
||||
// ToString on an object may throw via toString/valueOf; propagate the original exception.
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vm.new_string("")
|
||||
};
|
||||
|
||||
if !is_ctor {
|
||||
return NativeResult::Ok(str_val);
|
||||
}
|
||||
@@ -1155,10 +1174,12 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pad: Vec<u16> = if args.len() > 2 {
|
||||
try_string!(as_units(vm, vm.reg(args[2]))).into_owned()
|
||||
} else {
|
||||
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
|
||||
let pad: Vec<u16> = if pad_val.is_undefined() {
|
||||
vec![0x20]
|
||||
} else {
|
||||
try_string!(as_units(vm, pad_val)).into_owned()
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let s_len = code_point_count(&s);
|
||||
@@ -1194,10 +1215,12 @@ pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pad: Vec<u16> = if args.len() > 2 {
|
||||
try_string!(as_units(vm, vm.reg(args[2]))).into_owned()
|
||||
} else {
|
||||
let pad_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
// 规范口径:padString 为 undefined(含显式传入)回落空格填充。
|
||||
let pad: Vec<u16> = if pad_val.is_undefined() {
|
||||
vec![0x20]
|
||||
} else {
|
||||
try_string!(as_units(vm, pad_val)).into_owned()
|
||||
};
|
||||
let s: Vec<u16> = try_string!(this_units(vm, args)).into_owned();
|
||||
let s_len = code_point_count(&s);
|
||||
|
||||
@@ -8,6 +8,7 @@ description = "OxideJS - AST to IR emit pass"
|
||||
|
||||
[dependencies]
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_kernel = { path = "../oxide_kernel" }
|
||||
oxc_allocator.workspace = true
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_ir = { path = "../oxide_ir" }
|
||||
|
||||
@@ -74,7 +74,7 @@ impl Emitter {
|
||||
} else {
|
||||
(undef_reg, accessor_reg)
|
||||
};
|
||||
let key_name = self.class_property_name(&method.key)?;
|
||||
let key_name = crate::shared::string_pool::pool_key_property(&method.key)?;
|
||||
let key_idx = ctx.add_constant(Constant::String(key_name));
|
||||
ctx.inst(Inst::define_accessor_attrs(
|
||||
Operand::Reg(home_reg),
|
||||
|
||||
@@ -1876,8 +1876,6 @@ mod tests {
|
||||
assert!(!deletable, "只读三常量名误入可删集:{name}");
|
||||
}
|
||||
}
|
||||
for name in ["$262"] {
|
||||
assert!(!crate::CompileCtx::is_deletable_global_builtin(name), "不可删名误入可删集:{name}");
|
||||
}
|
||||
assert!(!crate::CompileCtx::is_deletable_global_builtin("$262"), "不可删名误入可删集:$262");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ impl Emitter {
|
||||
fn emit_string_literal_expression(
|
||||
&self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
let idx = ctx.add_constant(Constant::String(s.value.to_string()));
|
||||
let idx = ctx
|
||||
.add_constant(Constant::String(crate::shared::string_pool::pool_key_marker(&s.value, s.lone_surrogates)));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(r), idx));
|
||||
Ok(r)
|
||||
|
||||
@@ -133,10 +133,13 @@ pub(crate) fn is_array_index_str(s: &str) -> bool {
|
||||
/// (运行期非常量 / 键求值有副作用)与规范数组下标串返回 `None`,维持 DYNAMIC 路径。
|
||||
pub(crate) fn computed_const_key(expr: &Expression) -> Option<String> {
|
||||
let key = match expr {
|
||||
Expression::StringLiteral(s) => s.value.to_string(),
|
||||
Expression::TemplateLiteral(tl) if tl.expressions.is_empty() && tl.quasis.len() == 1 => {
|
||||
tl.quasis[0].value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default()
|
||||
}
|
||||
Expression::StringLiteral(s) => crate::shared::string_pool::pool_key_marker(&s.value, s.lone_surrogates),
|
||||
Expression::TemplateLiteral(tl) if tl.expressions.is_empty() && tl.quasis.len() == 1 => tl.quasis[0]
|
||||
.value
|
||||
.cooked
|
||||
.as_ref()
|
||||
.map(|c| crate::shared::string_pool::pool_key_marker(c, tl.quasis[0].lone_surrogates))
|
||||
.unwrap_or_default(),
|
||||
_ => return None,
|
||||
};
|
||||
if is_array_index_str(&key) {
|
||||
|
||||
@@ -24,7 +24,7 @@ impl Emitter {
|
||||
let ObjectPropertyKind::ObjectProperty(p) = prop else {
|
||||
unreachable!("批量前缀必为 ObjectProperty");
|
||||
};
|
||||
let name = self.class_property_name(&p.key).expect("批量前缀键为静态名");
|
||||
let name = crate::shared::string_pool::pool_key_property(&p.key).expect("批量前缀键为静态名");
|
||||
u32::from(ctx.add_constant(Constant::String(name)))
|
||||
})
|
||||
.collect();
|
||||
@@ -141,7 +141,8 @@ impl Emitter {
|
||||
));
|
||||
}
|
||||
None => {
|
||||
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
let key = crate::shared::string_pool::pool_key_property(&p.key)?;
|
||||
let idx = ctx.add_constant(Constant::String(key));
|
||||
ctx.inst(Inst::define_accessor(
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(get_reg),
|
||||
@@ -155,7 +156,8 @@ impl Emitter {
|
||||
let key_reg = if computed {
|
||||
self.emit_expression(p.key.to_expression(), ctx)?
|
||||
} else {
|
||||
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
let key = crate::shared::string_pool::pool_key_property(&p.key)?;
|
||||
let idx = ctx.add_constant(Constant::String(key));
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
|
||||
reg
|
||||
|
||||
@@ -23,12 +23,15 @@ impl Emitter {
|
||||
for quasi in &tt.quasi.quasis {
|
||||
match quasi.value.cooked.as_ref() {
|
||||
Some(c) => {
|
||||
let idx = ctx.add_constant(Constant::String(c.to_string()));
|
||||
let key = crate::shared::string_pool::pool_key_marker(c, quasi.lone_surrogates);
|
||||
let idx = ctx.add_constant(Constant::String(key));
|
||||
cooked_words.push(idx as u32);
|
||||
}
|
||||
None => cooked_words.push(0x8000_0000),
|
||||
}
|
||||
let raw_idx = ctx.add_constant(Constant::String(quasi.value.raw.to_string()));
|
||||
// raw 是源文本(保留转义原文),无 marker 语义,走普通编码。
|
||||
let raw_idx =
|
||||
ctx.add_constant(Constant::String(crate::shared::string_pool::pool_key_plain(&quasi.value.raw)));
|
||||
raw_idxs.push(raw_idx);
|
||||
}
|
||||
let site_no = ctx.next_template_site;
|
||||
|
||||
@@ -20,18 +20,20 @@ impl Emitter {
|
||||
.map(|e| self.emit_expression(e, ctx))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let quasi_const_idxs: Vec<u16> = quasis
|
||||
let mut quasi_keys: Vec<String> = Vec::with_capacity(quasis.len());
|
||||
for q in quasis {
|
||||
quasi_keys.push(match &q.value.cooked {
|
||||
Some(c) => crate::shared::string_pool::pool_key_marker(c, q.lone_surrogates),
|
||||
None => String::new(),
|
||||
});
|
||||
}
|
||||
let quasi_const_idxs: Vec<u16> = quasi_keys
|
||||
.iter()
|
||||
.map(|q| {
|
||||
let s = q.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default();
|
||||
ctx.add_constant(Constant::String(s))
|
||||
})
|
||||
.map(|s| ctx.add_constant(Constant::String(s.clone())))
|
||||
.collect();
|
||||
|
||||
let total_len_hint: usize = quasis
|
||||
.iter()
|
||||
.map(|q| q.value.cooked.as_ref().map(|c| c.len()).unwrap_or(0))
|
||||
.sum();
|
||||
// 容量提示按单元数口径(键文本经物化解码还原单元序列)。
|
||||
let total_len_hint: usize = quasi_keys.iter().map(|k| oxide_kernel::string_forge::decode_key(k).len()).sum();
|
||||
|
||||
let mut parts = Vec::with_capacity(quasi_const_idxs.len() * 2);
|
||||
let mut expr_iter = expr_regs.iter();
|
||||
|
||||
@@ -267,7 +267,10 @@ impl Emitter {
|
||||
// 只读局部符号表,嵌套函数引用外层绑定会静默回退读全局。
|
||||
PropertyKey::Identifier(_) => self.emit_expression(key.to_expression(), ctx),
|
||||
PropertyKey::StringLiteral(s) => {
|
||||
let key_idx = ctx.add_constant(Constant::String(s.value.to_string()));
|
||||
let key_idx = ctx.add_constant(Constant::String(crate::shared::string_pool::pool_key_marker(
|
||||
&s.value,
|
||||
s.lone_surrogates,
|
||||
)));
|
||||
let key_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
|
||||
Ok(key_reg)
|
||||
@@ -294,7 +297,7 @@ impl Emitter {
|
||||
&self, src_reg: u32, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx,
|
||||
) -> Result<(u32, Option<String>, Option<u32>), String> {
|
||||
if !computed {
|
||||
let key_name = self.static_property_name(key)?;
|
||||
let key_name = crate::shared::string_pool::pool_key_property(key)?;
|
||||
let prop_reg = self.emit_object_property_read(src_reg, &key_name, ctx);
|
||||
return Ok((prop_reg, Some(key_name), None));
|
||||
}
|
||||
@@ -302,7 +305,7 @@ impl Emitter {
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(prop_reg), Operand::Reg(src_reg), Operand::None));
|
||||
// 字符串字面量计算键折叠为 IC 静态路径;标识符/数字键走 DYNAMIC。
|
||||
if let PropertyKey::StringLiteral(s) = key {
|
||||
let key_str = s.value.to_string();
|
||||
let key_str = crate::shared::string_pool::pool_key_marker(&s.value, s.lone_surrogates);
|
||||
if !is_array_index_str(&key_str) {
|
||||
let key_idx = ctx.add_constant(Constant::String(key_str));
|
||||
let key_reg = ctx.alloc_reg();
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Emitter {
|
||||
&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
if !computed {
|
||||
let name = self.class_property_name(key)?;
|
||||
let name = crate::shared::string_pool::pool_key_property(key)?;
|
||||
let idx = ctx.add_constant(Constant::String(name));
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
|
||||
@@ -72,16 +72,6 @@ impl Emitter {
|
||||
self.emit_expression(key.to_expression(), ctx)
|
||||
}
|
||||
|
||||
pub(crate) fn static_property_name(&self, key: &PropertyKey) -> Result<String, String> {
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
PropertyKey::Identifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
PropertyKey::StringLiteral(s) => Ok(s.value.to_string()),
|
||||
PropertyKey::NumericLiteral(n) => Ok(n.value.to_string()),
|
||||
_ => Err("computed destructuring keys not yet supported".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 从类定义期 computed key 数组按 slot 取键(方法/静态字段在类定义期使用)。
|
||||
pub(crate) fn emit_class_array_key_reg(&self, slot: u8, ctx: &mut CompileCtx) -> Result<u32, String> {
|
||||
let arr_reg = ctx.class_keys_reg.expect("class computed keys array missing");
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod binding;
|
||||
pub mod chain;
|
||||
pub mod class_field;
|
||||
pub mod class_key;
|
||||
pub mod string_pool;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//! 常量池字符串键编码:池统一存无标志键编码(`oxide_kernel::string_forge::encode_key`
|
||||
//! 语义),物化侧(perm_string → `decode_key`)是其精确逆变换。
|
||||
//!
|
||||
//! 两类来源文本:
|
||||
//! - oxc marker 文本(`StringLiteral.value` / `TemplateElement.cooked`):
|
||||
//! `lone_surrogates` 置位时 marker 与键编码同构(FFFD+hex4 = 孤立 surrogate 单元、
|
||||
//! FFFD+fffd = 真实 FFFD),原样即池键;未置位时为普通文本(可含裸 FFFD),
|
||||
//! 须逃逸编码防裸 FFFD 与 surrogate marker 碰撞;
|
||||
//! - 普通文本(标识符 / 数字 / 源文本):非 FFFD 恒等,真实 FFFD 逃逸为 FFFD+"fffd"。
|
||||
|
||||
use oxide_kernel::string_forge::encode_key;
|
||||
use oxide_parser::PropertyKey;
|
||||
|
||||
/// 普通文本 → 池键:真实 FFFD 逃逸为 FFFD+"fffd",其余逐单元恒等。
|
||||
pub(crate) fn pool_key_plain(text: &str) -> String {
|
||||
let units: Vec<u16> = text.encode_utf16().collect();
|
||||
encode_key(&units)
|
||||
}
|
||||
|
||||
/// oxc marker 文本 → 池键,按 `lone_surrogates` 分派(语义见模块头注释)。
|
||||
pub(crate) fn pool_key_marker(text: &str, lone_surrogates: bool) -> String {
|
||||
if lone_surrogates {
|
||||
text.to_string()
|
||||
} else {
|
||||
pool_key_plain(text)
|
||||
}
|
||||
}
|
||||
|
||||
/// 属性键 → 池键:字符串字面量键经 marker 解码,标识符 / 数字键走普通编码。
|
||||
pub(crate) fn pool_key_property(key: &PropertyKey) -> Result<String, String> {
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => Ok(pool_key_plain(ident.name.as_str())),
|
||||
PropertyKey::Identifier(ident) => Ok(pool_key_plain(ident.name.as_str())),
|
||||
PropertyKey::StringLiteral(s) => Ok(pool_key_marker(&s.value, s.lone_surrogates)),
|
||||
PropertyKey::NumericLiteral(n) => Ok(pool_key_plain(&n.value.to_string())),
|
||||
PropertyKey::PrivateIdentifier(_) => Err("private class elements not yet supported".into()),
|
||||
_ => Err("unsupported class property key type".into()),
|
||||
}
|
||||
}
|
||||
@@ -446,6 +446,8 @@ mod tests {
|
||||
assert_eq!(encode_key(&[0xFFFD]), "\u{FFFD}fffd");
|
||||
// 两种不同单元序列的键文本互异(解码可区分)。
|
||||
assert_ne!(encode_key(&[0xFFFD]), encode_key(&[0xD800]));
|
||||
// 文本 [FFFD,'d','8','0','0'] 与单元 [D800] 的键必须可区分(防物化串键)。
|
||||
assert_ne!(encode_key(&[0xFFFD, 0x64, 0x38, 0x30, 0x30]), encode_key(&[0xD800]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -528,9 +528,10 @@ fn symbol_dispose_and_async_dispose_registered() {
|
||||
let result = eval(&mut vm, src).unwrap();
|
||||
assert_eq!(result.as_bool(), expected, "for {}", src);
|
||||
}
|
||||
// ToString(Symbol) 按规格抛 TypeError(String 构造器无描述性名称通道)。
|
||||
let err = eval(&mut vm, "try { String(Symbol.dispose); 'no-throw' } catch (e) { e.name }").unwrap();
|
||||
assert_eq!(to_str(&vm, err), "TypeError");
|
||||
// String(Symbol) 走 SymbolDescriptiveString(规范 String 构造器步骤),
|
||||
// 不经 ToString——well-known 符号对象按描述名产出。
|
||||
let s = eval(&mut vm, "String(Symbol.dispose)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "Symbol(Symbol.dispose)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -150,6 +150,9 @@ fn string_pad_end() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hi'.padEnd(4)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hi ");
|
||||
// 显式 undefined 填充与缺省同义:回落空格(规范 PadString 步骤)。
|
||||
let s = eval(&mut vm, "'hi'.padEnd(4, undefined)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hi ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -264,6 +267,17 @@ fn string_missing_arg_searches_undefined() {
|
||||
assert_eq!(r.as_int(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_ctor_symbol_paths() {
|
||||
// 函数调用:Symbol → SymbolDescriptiveString(描述串,不抛)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "String(Symbol('66'))").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "Symbol(66)");
|
||||
// new 语义:构造器路径走 ToString,Symbol 抛 TypeError。
|
||||
let name = eval(&mut vm, "try { new String(Symbol('66')); 'no-throw' } catch (e) { e.name }").unwrap();
|
||||
assert_eq!(to_str(&vm, name), "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_symbol_argument_throws_type_error() {
|
||||
// Symbol 参数经 as_string 抛 TypeError(不静默降级为文本查找)。
|
||||
@@ -425,6 +439,37 @@ fn string_replace_function_replacer_string_pattern() {
|
||||
assert_eq!(to_str(&vm, s), "a1c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_dollar_group_expansion() {
|
||||
// `$n` 展开按规范 GetSubstitution:两位数值越界回退一位、仍越界(含 $0)
|
||||
// 整段 ref 字面、未参与捕获组为空。预言值对照 V8(node)实测。
|
||||
let mut vm = Vm::new();
|
||||
let cases = [
|
||||
// 两位越界回退一位(次位留字面)。
|
||||
("\"uid=31\".replace(/(uid=)(\\d+)/, \"$11\" + 15)", "uid=115"),
|
||||
("\"x\".replace(/(x)/, \"$12\")", "x2"),
|
||||
("\"xy\".replace(/(x)(y)/, \"$12\")", "x2"),
|
||||
// 越界(含 $0)→ ref 字面;无捕获组全字面。
|
||||
("\"xy\".replace(/(x)(y)/, \"$3\")", "$3"),
|
||||
("\"abc\".replace(/b/, \"$0$1\")", "a$0$1c"),
|
||||
("\"abc\".replace(/(b)/, \"$22\")", "a$22c"),
|
||||
("\"x\".replace(/x/, \"$10\")", "$10"),
|
||||
// $& / $` 与组引用混排。
|
||||
("\"aaaaa,aaaaa\".replace(/(a+),/, \"$1$&$`$1\")", "aaaaaaaaaa,aaaaaaaaaa"),
|
||||
// $$ 折叠与文本内 $ 无关;$ 序列多位数字逐段消费。
|
||||
("\"a$b$c\".replace(/b/, \"$$$$\")", "a$$$$c"),
|
||||
("\"x12y\".replace(/1/, \"$1$12$123\")", "x$1$12$1232y"),
|
||||
("\"x12y\".replace(/(1)/, \"$1$12$123\")", "x1121232y"),
|
||||
("\"x12y\".replace(/(1)(2)/, \"$1$12$123\")", "x112123y"),
|
||||
// 组参与但未匹配 → 空。
|
||||
("\"x12y\".replace(/(1)(2)(3)?/, \"$3\")", "xy"),
|
||||
];
|
||||
for (src, expected) in cases {
|
||||
let s = eval(&mut vm, src).unwrap();
|
||||
assert_eq!(to_str(&vm, s), expected, "for {}", src);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_function_replacer_string_arg() {
|
||||
// 回调第 4 参为原字符串:原始 receiver 直接传值(=== 原串),
|
||||
@@ -1154,3 +1199,151 @@ fn string_utf16_replace_position_callback() {
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.replaceAll('a', function(m, o){ return o })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}2b");
|
||||
}
|
||||
|
||||
// UTF-16 载荷层回归钉:孤立 surrogate 一等单元(fromCodePoint/fromCharCode 直推单元、
|
||||
// 字面量/模板 oxc marker 解码、切片/迭代/搜索/键/正则面单元语义)。
|
||||
|
||||
#[test]
|
||||
fn utf16_from_code_point_lone_surrogate() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"String.fromCodePoint(0xD800).length===1 && String.fromCodePoint(0xD800).charCodeAt(0)===0xD800",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_from_char_code_pair_units() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"String.fromCharCode(0xD800,0xDC00).length===2 && String.fromCharCode(0xD800,0xDC00).charCodeAt(1)===0xDC00",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_from_code_point_astral_pair() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"String.fromCodePoint(0x1F600).length===2 && String.fromCodePoint(0x1F600).charCodeAt(0)===0xD83D && String.fromCodePoint(0x1F600).charCodeAt(1)===0xDE00",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_literal_lone_surrogate_emit_decode() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "'\\ud800'.length===1 && '\\ud800'.charCodeAt(0)===0xD800").unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_template_cooked_lone_surrogate() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "`\\ud800`.length===1").unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_char_at_single_unit_astral() {
|
||||
// charAt 按单元取:astral 字符首单元为高代理(非整码点)。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "'\u{1D11E}'.charAt(0).charCodeAt(0)===0xD834").unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_slice_substr_single_unit_astral() {
|
||||
// slice/substr 单元精确:'\u{1D11E}' = [0xD834, 0xDD1E]。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'\u{1D11E}'.slice(-1).charCodeAt(0)===0xDD1E && '\u{1D11E}'.substr(1).charCodeAt(0)===0xDD1E && '\u{1D11E}'.slice(0,1).charCodeAt(0)===0xD834",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_iterator_lone_surrogate_units() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(function(){ let n=0, ok=true; for (const c of 'a\\ud800') { n++; if (n===2) ok = c.length===1 && c.charCodeAt(0)===0xD800; } return n===2 && ok; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_spread_and_to_object_lone_surrogate() {
|
||||
// 迭代/spread 按单元产出;rest 解构的 ToObject 字符串下标读得 1 单元串
|
||||
// (非空串)。字符串包装对象的索引/length 读(new String(...)[0])另有
|
||||
// 独立既有缺口,不在此钉。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(function(){ const arr=[...'a\\ud800']; const o={...'a\\ud800'}; const { ...rest } = 'a\\ud800'; return arr.length===2 && arr[1].length===1 && arr[1].charCodeAt(0)===0xD800 && o[1].length===1 && o[1].charCodeAt(0)===0xD800 && rest[1].length===1 && rest[1].charCodeAt(0)===0xD800; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_regex_surrogate_property_class() {
|
||||
// 属性转义须 u 标志(无 u 为规范 SyntaxError,引擎宽容面不在此钉)。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"/\\p{General_Category=Surrogate}/u.test(String.fromCodePoint(0xD800))===true && /\\P{ASCII}/u.test(String.fromCodePoint(0xD800))===true && /\\p{General_Category=Surrogate}/u.test('a')===false",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_is_well_formed_to_well_formed() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'a\\ud800b'.isWellFormed()===false && 'a\\ud800b'.toWellFormed().length===3 && 'a\\ud800b'.toWellFormed().charCodeAt(1)===0xFFFD",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_index_of_lone_surrogate() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(&mut vm, "'a\\ud800\\ud800'.indexOf('\\ud800')===1").unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_property_key_round_trip() {
|
||||
let mut vm = Vm::new();
|
||||
// 孤立 surrogate 计算键:写入/读取 round-trip,Object.keys 出 1 单元键。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(function(){ const k='\\ud800'; const o={}; o[k]=1; const keys=Object.keys(o); return o[k]===1 && keys.length===1 && keys[0].length===1 && keys[0].charCodeAt(0)===0xD800; })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16_normalize_astral_identity() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'\u{1F441}'.normalize('NFD')==='\u{1F441}' && '\u{1F441}'.normalize('NFC').length===2",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user