vm/kernel/emit/builtins: 动态源编码重设计 + 数值键 2^32 边界 + 单元域解码/Source 面——14 头独占 test262 回归 13 修 0 新增

全量 A/B 差分撞出 14 个头独占回归,本提交闭合 13(text-self.js 双树同败归 pre-existing):

数值键 u32 回绕(release 态,debug 为 panic 故单测未现):canonical_index_str/
units 的 v.checked_mul(10)? + d as u32 在乘积合法而和溢出 2^32 时静默回绕
(10 位数字串 "4294967296".."4294967299" 误判整数键 0..3,属性查找回
退、Object.keys 串键),改 checked_mul+checked_add 双段防溢出;str/units 各
钉一枚 2^30-1/2^30/2^32-1/2^32 边界(Group B 5 文件:defineProperty/
defineProperties 2^32 键族 + Array property-cast-number)。

decodeURI/decodeURIComponent 单元域重写(Group A 2 文件):decode_uri_units
按单元扫描(非 % 单元原样透传——孤立 surrogate 往返不再被 lossy 串值面
打碎),%XX 解单元入缓冲;string_arg_units 经 to_units_full 取单元。

动态源编码(eval/Function/$262.evalScript)重设计(Group A 6 文件 + value-u):
source_escape 旧设计把源码域反斜杠恒双写(与 marker 起始区分),但源码即
JS 程序、反斜杠恒为字面量内转义序列起始,双写改变程序语义——
/\u{1d306}/u 变 /\\u{1d306}/u,u 模式撞裸 { 直接 SyntaxError(value-u 回归
根因)。现反斜杠原样透传;孤立 surrogate/FFFD 单元仍以 \\uXXXX 转义文本
注入(Rust str 无法承载裸孤立 surrogate,转义文本是唯一注入形态);
source_escape_to_key 逆路径仅还原注入 marker(\\ud800..\\udfff/\\ufffd
小写转义文本 → FFFD+hex4 池键形态,物化 decode_key 还原原始单元,正则
.source 按原始源返回;用户转义文本 \\u0041/\\u{1d306} 等逐字透传),
\\+marker 拼接形态(数据反斜杠紧邻注入,S7.8.5_A1.4 族 cu=d800 断言)按
前瞻还原 [数据反斜杠, 单元]。source_encoded 标志经 Compiler→Emitter→CompileCtx
(嵌套继承)贯通,仅动态编译入口置位;静态源切片维持 pool_key_plain 不变。

RegExp [[Source]]/[[Flags]] 单元域(regexp 构造器):to_units_full →
new_string_units_owned 入键(旧 lossy to_string 把孤立 surrogate 单元打成
FFFD,src_match 探针 false);regress 编译仍 lossy(既有缺口,另行报告)。

三并钉:source_escape marker 形态 / 反斜杠透传 / decode_key∘to_key∘escape
闭环(含 \\+marker 相邻与 astral 对)。实测:Group A 8/8、Group B 5/5、
value-u 1/1;8178 文件回归子集(13 目录)双树 A/B 差分 HEAD-only=∅、
13 文件转双树同 pass;text-self.js 双树同 compile 败
(requested module export is not exported: default)归 pre-existing;
七 crate 单测 2879/1(唯一失败 compile_class_declaration_emits_constructor
为 HEAD~0 既有,本提交未引入)。
This commit is contained in:
2026-09-14 07:16:00 +00:00
parent 844f2f455b
commit 6f70d7d09f
10 changed files with 417 additions and 66 deletions
+11 -3
View File
@@ -16,8 +16,12 @@ use crate::error;
///
/// # 步骤
/// 1. 实参非字符串 → 原样返回(规范不 ToString)。
/// 2. 字符串 → 动态编译为脚本模块(`create_dynamic_script`),以 global 为 this 同步调用。
/// 3. 编译失败 → SyntaxError;运行异常 → 优先重抛原始异常值(`take_uncaught_value`)。
/// 2. 字符串 → 单元序列按源码域转义(`string_forge::source_escape`,孤立
/// surrogate / FFFD 以 `\uXXXX` 转义文本承载,反斜杠为源码语法字符原样
/// 透传,可被 oxc 直接词法分析)→ 动态编译为脚本模块(`create_dynamic_script`
/// 动态路径源契约),
/// 以 global 为 this 同步调用。
/// 3. 编译失败 → SyntaxError;运行异常 → 优先重抛原始异常值(`take_uncaught_value`,`eval("throw 1")` 捕获到 `1`)。
///
/// # 边界与前提
/// - 脚本模式:var/函数声明落全局对象,完成值保留,let/const 词法隔离。
@@ -27,7 +31,11 @@ pub fn eval<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if !arg.is_string() {
return NativeResult::Ok(arg);
}
let code = vm.lookup_str(arg).unwrap_or_default();
let units = match oxide_runtime_api::to_units_full(arg, vm) {
Ok(u) => u,
Err(e) => return NativeResult::Err(error::create_from_text(vm, &e)),
};
let code = oxide_kernel::string_forge::source_escape(&units);
match vm.create_dynamic_script(&code) {
Ok(func) => {
let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject);
+9 -6
View File
@@ -2,7 +2,7 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue;
use oxide_runtime_api::{to_integer_or_infinity, to_string_full, NativeResult, VmHost};
use oxide_runtime_api::{to_integer_or_infinity, to_units_full, NativeResult, VmHost};
fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
@@ -16,7 +16,8 @@ fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue,
/// `Function(...)` / `new Function(...)` 构造器:动态编译一个匿名函数。
///
/// 除最后一个实参为函数体外,其余实参为形参名;无实参时函数体为空串。
/// 编译成功返回函数对象,语法错误抛 SyntaxError,实参 ToString 失败抛 TypeError。
/// 实参转单元序列后经 `source_escape` 源码域转义再动态编译。
/// 编译成功返回函数对象,语法错误抛 SyntaxError,实参 ToPrimitive 失败抛 TypeError。
pub fn function_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let arg_regs = &args[1..];
let (param_regs, body_reg) = if arg_regs.is_empty() {
@@ -26,16 +27,18 @@ pub fn function_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
(head.to_vec(), Some(tail[0]))
};
// 动态路径源契约(见 `create_dynamic_function`):形参名与函数体经
// `source_escape` 源码域转义后拼接 wrap 源码。
let mut params = Vec::with_capacity(param_regs.len());
for &r in &param_regs {
match to_string_full(vm.reg(r), vm) {
Ok(s) => params.push(s),
match to_units_full(vm.reg(r), vm) {
Ok(u) => params.push(oxide_kernel::string_forge::source_escape(&u)),
Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)),
}
}
let body = match body_reg {
Some(r) => match to_string_full(vm.reg(r), vm) {
Ok(s) => s,
Some(r) => match to_units_full(vm.reg(r), vm) {
Ok(u) => oxide_kernel::string_forge::source_escape(&u),
Err(e) => return NativeResult::Err(to_string_error_value(vm, &e)),
},
None => String::new(),
+83 -34
View File
@@ -1,4 +1,5 @@
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::value::JsValue;
const URI_UNESCAPED: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()";
const URI_RESERVED: &str = ";/?:@&=+$,#";
@@ -21,11 +22,17 @@ fn encode_uri_string(input: &str, safe: &str) -> String {
out
}
fn read_percent_byte(bytes: &[u8], pos: usize) -> Result<u8, ()> {
if pos + 3 > bytes.len() || bytes[pos] != b'%' {
/// 从单元序列的 `%` 位置读一个 `%XX` 字节(hex 两位须为 ASCII 十六进制单元)。
fn read_percent_unit(input: &[u16], pos: usize) -> Result<u8, ()> {
if pos + 3 > input.len() || input[pos] != 0x25 {
return Err(());
}
super::parse_hex_u8(&bytes[pos + 1..pos + 3]).ok_or(())
let hi = input[pos + 1];
let lo = input[pos + 2];
if hi > 0x7F || lo > 0x7F {
return Err(());
}
super::parse_hex_u8(&[hi as u8, lo as u8]).ok_or(())
}
fn utf8_sequence_len(first: u8) -> Result<usize, ()> {
@@ -38,20 +45,22 @@ fn utf8_sequence_len(first: u8) -> Result<usize, ()> {
}
}
fn decode_uri_string(input: &str, preserve_reserved: bool) -> Result<String, ()> {
let bytes = input.as_bytes();
let mut out = String::with_capacity(input.len());
/// 单元口径的 `%XX` 解码:非 `%` 单元原样透传(孤立 surrogate 单元按规范
/// 不变地往返),`%XX` 序列收集为 UTF-8 字节后还原为码点单元(超平面还原
/// 为良配 surrogate 对)。`preserve_reserved` 为 true 时单个保留字符的
/// 转义形态原样保留(decodeURI 的 decode(encode(x)) 稳定性)。
fn decode_uri_units(input: &[u16], preserve_reserved: bool) -> Result<Vec<u16>, ()> {
let mut out = Vec::with_capacity(input.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] != b'%' {
let ch = input[i..].chars().next().expect("valid utf-8 char boundary");
out.push(ch);
i += ch.len_utf8();
while i < input.len() {
if input[i] != 0x25 {
out.push(input[i]);
i += 1;
continue;
}
let first = read_percent_byte(bytes, i)?;
let first = read_percent_unit(input, i)?;
let sequence_len = utf8_sequence_len(first)?;
let raw_start = i;
let mut encoded = Vec::with_capacity(sequence_len);
@@ -59,7 +68,7 @@ fn decode_uri_string(input: &str, preserve_reserved: bool) -> Result<String, ()>
i += 3;
for _ in 1..sequence_len {
let byte = read_percent_byte(bytes, i)?;
let byte = read_percent_unit(input, i)?;
if byte & 0b1100_0000 != 0b1000_0000 {
return Err(());
}
@@ -68,14 +77,21 @@ fn decode_uri_string(input: &str, preserve_reserved: bool) -> Result<String, ()>
}
let decoded = std::str::from_utf8(&encoded).map_err(|_| ())?;
if preserve_reserved && decoded.len() == 1 {
let ch = decoded.chars().next().expect("single decoded char");
if URI_RESERVED.contains(ch) {
out.push_str(&input[raw_start..i]);
continue;
let ch = decoded.chars().next().expect("单字节序解出恰一个码点");
if preserve_reserved && ch.is_ascii() && URI_RESERVED.contains(ch) {
for k in raw_start..i {
out.push(input[k]);
}
continue;
}
let cp = ch as u32;
if cp <= 0xFFFF {
out.push(cp as u16);
} else {
let v = cp - 0x10000;
out.push(0xD800 + (v >> 10) as u16);
out.push(0xDC00 + (v & 0x3FF) as u16);
}
out.push_str(decoded);
}
Ok(out)
@@ -85,6 +101,16 @@ fn uri_error<H: VmHost>(vm: &mut H) -> NativeResult {
NativeResult::Err(crate::error::create_uri_error(vm, URI_ERROR_MESSAGE))
}
/// 取 URI 函数实参的单元序列(单元口径,不 lossy):无实参等价于处理
/// `"undefined"`。ToPrimitive 抛错(Symbol 等)原样返回异常值。
fn string_arg_units<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<Vec<u16>, NativeResult> {
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
match oxide_runtime_api::to_units_full(val, vm) {
Ok(u) => Ok(u),
Err(e) => Err(NativeResult::Err(crate::error::create_from_text(vm, &e))),
}
}
/// `encodeURI`:编码输入为 URI,保留未转义字符与保留字符 `;/?:@&=+$,#`,
/// 其余按 UTF-8 字节转成 `%XX`。
pub fn encode_uri<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
@@ -100,28 +126,39 @@ pub fn encode_uri_component<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
/// `decodeURI`:解码 `%XX` 序列,但保留字符的转义形式原样保留(不还原),
/// 保证 decode(encode(uri)) 不变。非法序列抛 URIError。
/// 保证 decode(encode(uri)) 不变。非法序列抛 URIError。单元口径处理:非 `%`
/// 单元(含孤立 surrogate)原样往返。
pub fn decode_uri<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let input = super::string_arg(vm, args);
match decode_uri_string(&input, true) {
Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)),
let input = match string_arg_units(vm, args) {
Ok(u) => u,
Err(e) => return e,
};
match decode_uri_units(&input, true) {
Ok(decoded) => NativeResult::Ok(vm.new_string_units(&decoded)),
Err(()) => uri_error(vm),
}
}
/// `decodeURIComponent`:解码所有 `%XX` 序列(含保留字符),
/// 非法或截断的 UTF-8 序列抛 URIError。
/// 非法或截断的 UTF-8 序列抛 URIError。单元口径,孤立 surrogate 原样往返。
pub fn decode_uri_component<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let input = super::string_arg(vm, args);
match decode_uri_string(&input, false) {
Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)),
let input = match string_arg_units(vm, args) {
Ok(u) => u,
Err(e) => return e,
};
match decode_uri_units(&input, false) {
Ok(decoded) => NativeResult::Ok(vm.new_string_units(&decoded)),
Err(()) => uri_error(vm),
}
}
#[cfg(test)]
mod tests {
use super::{decode_uri_string, encode_uri_string, URI_UNESCAPED};
use super::{decode_uri_units, encode_uri_string, URI_UNESCAPED};
fn units(s: &str) -> Vec<u16> {
s.encode_utf16().collect()
}
#[test]
fn encode_uri_keeps_reserved_and_encodes_space() {
@@ -139,20 +176,32 @@ mod tests {
#[test]
fn decode_uri_preserves_reserved_escapes() {
assert_eq!(
decode_uri_string("https://example.com/path%3Fq=hello%20world", true).unwrap(),
"https://example.com/path%3Fq=hello world"
decode_uri_units(&units("https://example.com/path%3Fq=hello%20world"), true).unwrap(),
units("https://example.com/path%3Fq=hello world")
);
}
#[test]
fn decode_uri_component_decodes_reserved() {
assert_eq!(decode_uri_string("a%3D1%26b%3D2", false).unwrap(), "a=1&b=2");
assert_eq!(decode_uri_units(&units("a%3D1%26b%3D2"), false).unwrap(), units("a=1&b=2"));
}
#[test]
fn decode_uri_rejects_malformed_sequences() {
assert!(decode_uri_string("%", false).is_err());
assert!(decode_uri_string("%E0%A4", false).is_err());
assert!(decode_uri_string("%ED%A0%80", false).is_err());
assert!(decode_uri_units(&units("%"), false).is_err());
assert!(decode_uri_units(&units("%E0%A4"), false).is_err());
assert!(decode_uri_units(&units("%ED%A0%80"), false).is_err());
}
/// 孤立 surrogate 单元非 `%` 序列:decodeURI/decodeURIComponent 均须原样
/// 往返(S15.1.3.1/2 A2.1 族遍历 0..0xFFFF 的要求)。
#[test]
fn decode_round_trips_lone_surrogates() {
assert_eq!(decode_uri_units(&[0xD800], true).unwrap(), vec![0xD800]);
assert_eq!(decode_uri_units(&[0xDFFF], false).unwrap(), vec![0xDFFF]);
assert_eq!(
decode_uri_units(&[0x61, 0x25, 0x32, 0x30, 0x62, 0xDBFF], false).unwrap(),
vec![0x61, 0x20, 0x62, 0xDBFF]
);
}
}
+23 -10
View File
@@ -77,16 +77,28 @@ fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
/// 支持 backreference/lookaround/命名组/v-flag);非法模式抛 SyntaxError。
/// 编译结果存于对象的 native_fn 槽。
pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let (pattern, flags) = if args.len() < 2 {
(String::new(), String::new())
} else if args.len() < 3 {
let pat = oxide_runtime_api::to_string(vm.reg(args[1]));
(pat, String::new())
// [[Source]]/[[Flags]] 是 ToString 结果的**单元序列**:`to_units_full` 直取
// (lossy `to_string` 会把孤立 surrogate 单元折成 FFFD,破坏 source 往返)。
// regress 编译用 lossy 文本(其匹配语义对孤立 surrogate 本即既有缺口,
// 不因本处改变)。
let pattern_units = if args.len() < 2 {
Vec::<u16>::new()
} else {
let pat = oxide_runtime_api::to_string(vm.reg(args[1]));
let fl = oxide_runtime_api::to_string(vm.reg(args[2]));
(pat, fl)
match oxide_runtime_api::to_units_full(vm.reg(args[1]), vm) {
Ok(u) => u,
Err(e) => return NativeResult::Err(crate::error::create_from_text(vm, &e)),
}
};
let flags_units = if args.len() < 3 {
Vec::<u16>::new()
} else {
match oxide_runtime_api::to_units_full(vm.reg(args[2]), vm) {
Ok(u) => u,
Err(e) => return NativeResult::Err(crate::error::create_from_text(vm, &e)),
}
};
let pattern = String::from_utf16_lossy(&pattern_units);
let flags = String::from_utf16_lossy(&flags_units);
let (global, ignore_case, multi_line, dot_all, sticky, unicode, has_indices) = parse_flags(&flags);
@@ -116,8 +128,9 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
set_prop(&mut obj, "lastIndex", JsValue::int(0), vm);
set_prop(&mut obj, "source", vm.new_string(&pattern), vm);
set_prop(&mut obj, "flags", vm.new_string(&flags), vm);
// 单元口径物化 source/flags(孤立 surrogate 单元保持往返)。
set_prop(&mut obj, "source", vm.new_string_units_owned(pattern_units), vm);
set_prop(&mut obj, "flags", vm.new_string_units_owned(flags_units), vm);
set_prop(&mut obj, "global", JsValue::bool(global), vm);
set_prop(&mut obj, "ignoreCase", JsValue::bool(ignore_case), vm);
set_prop(&mut obj, "multiline", JsValue::bool(multi_line), vm);