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);
+11 -1
View File
@@ -18,6 +18,8 @@ pub struct Compiler {
repl_persist: bool,
/// 是否为 eval 脚本:顶层 var/function 声明落全局属性 configurable:true。
is_eval_script: bool,
/// 源码是否为 `source_escape` 产物(动态编译入口:eval / Function 构造器)。
is_source_encoded: bool,
}
impl Compiler {
@@ -28,6 +30,7 @@ impl Compiler {
enable_regalloc: true,
repl_persist: false,
is_eval_script: false,
is_source_encoded: false,
}
}
@@ -54,10 +57,17 @@ impl Compiler {
Self { is_eval_script: enable, ..self }
}
/// 标记源码为 `source_escape` 产物(动态编译入口:eval / Function 构造器)。
pub fn with_source_encoded(self, enable: bool) -> Self {
Self { is_source_encoded: enable, ..self }
}
/// 编译整个 script program:AST → IR(`Emitter::emit_program`)→ 统一 IR 管线。
pub fn compile(&self, program: &oxide_parser::Program) -> Result<CompiledModule, String> {
crate::compiler_debug!("compile: starting...");
let ir = Emitter::new().emit_program(program, self.repl_persist, self.is_eval_script)?;
let ir = Emitter::new()
.with_source_encoded(self.is_source_encoded)
.emit_program(program, self.repl_persist, self.is_eval_script)?;
self.compile_ir(ir)
}
+27 -4
View File
@@ -34,8 +34,17 @@ pub use oxide_parser::VariableDeclarationKind;
/// AST 语法树节点与运算符类型,re-export 自 parser。
pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator};
/// 编译入口(marker 类型)。方法按语法域组织在 `impl Emitter` 中。
pub struct Emitter;
/// 编译入口。方法按语法域组织在 `impl Emitter` 中。
pub struct Emitter {
/// 源码是否为 `oxide_kernel::string_forge::source_escape` 产物
/// (eval/Function 动态编译):源文本内的孤立 surrogate 单元 / FFFD 以
/// `\uXXXX` 转义文本承载(oxc 不可见裸单元——Rust str 无孤立 surrogate,
/// 转义文本是唯一注入形态),反斜杠原样透传。正则字面量的源文本切片据此
/// 经 `source_escape_to_key` 还原池键 marker 形态入池(物化时
/// `decode_key` 还原为原始单元,`.source` 按原始源返回);静态源切片不含
/// 注入 marker,走 `pool_key_plain`。
source_encoded: bool,
}
/// 判断 f64 是否为整数值且在 i32 范围内(整数常量编码用)。
pub fn is_int_literal(value: f64) -> bool {
@@ -194,6 +203,9 @@ pub struct CompileCtx {
/// 是否为 eval 脚本:脚本顶层 var/function 声明落全局对象时属性
/// configurable:true(普通脚本顶层为 false)。仅动态脚本入口设置。
pub(crate) is_eval_script: bool,
/// 源码是否为 `source_escape` 产物(动态编译入口):正则字面量源文本切片
/// 的池键形态据此选择(见 [`Emitter::source_encoded`])。
pub(crate) source_encoded: bool,
pub(crate) static_block_this_reg: Option<u8>,
pub(crate) field_buffer: Option<FieldBuffer>,
/// 类构造器模块中 `@@field_keys` upvalue 下标(实例字段 computed key 数组)。
@@ -343,6 +355,7 @@ impl CompileCtx {
is_global_scope: false,
repl_persist: false,
is_eval_script: false,
source_encoded: false,
static_block_this_reg: None,
field_buffer: None,
field_keys_uv: None,
@@ -825,9 +838,16 @@ impl ConstantKey {
}
impl Emitter {
/// 构造空 `Emitter`(无内部状态,所有状态在 `CompileCtx` 中)。
/// 构造空 `Emitter`(静态编译口径:源文本为良形 UTF-8,无注入 marker)。
pub fn new() -> Self {
Self
Self { source_encoded: false }
}
/// 置位编码源口径(见 [`Emitter::source_encoded`]):仅动态编译入口
/// (eval / Function 构造器)经编译器设置。
pub fn with_source_encoded(mut self, enable: bool) -> Self {
self.source_encoded = enable;
self
}
/// 生成运行时抛 `kind` 类型错误的指令序列,返回一个未定义 dummy 寄存器
@@ -1151,6 +1171,8 @@ impl Emitter {
// 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
// 编码源口径是整棵编译树属性(嵌套函数仍在同一编码源内)。
ctx.source_encoded = parent_ctx.source_encoded;
// 箭头函数词法继承 super;类方法体顶层编译也需要类提供的 super 上下文。
if matches!(body_context, FunctionBodyContext::Arrow | FunctionBodyContext::ClassElement) {
@@ -1694,6 +1716,7 @@ impl Emitter {
ctx.is_global_scope = true;
ctx.repl_persist = repl_persist;
ctx.is_eval_script = is_eval_script;
ctx.source_encoded = self.source_encoded;
// 脚本顶层严格模式由源码 "use strict" directive 决定(嵌套函数经父 ctx 继承)。
ctx.is_strict = program.has_use_strict_directive();
+14 -2
View File
@@ -89,10 +89,22 @@ impl Emitter {
if regress::Regex::with_flags(&pattern, flags.as_str()).is_err() {
return Err(format!("SyntaxError: Invalid regular expression: /{pattern}/{flags}"));
}
let pat_ci = ctx.add_constant(Constant::String(pattern));
// 源文本池键:静态源切片经 `pool_key_plain` 逃逸真实 FFFD(防
// 物化侧把裸 FFFD+hex4 误读为 surrogate marker),转义文本
// `decode_key` 逐字透传,正则引擎自行解析转义;编码源切片经
// `source_escape_to_key` 把注入的 `\ud800`..`\udfff`/`\ufffd`
// 转义文本还原为池键 marker 形态(物化还原原始单元,
// `.source` 按原始源返回),用户转义文本逐字透传。
let pattern_key = if ctx.source_encoded {
oxide_kernel::string_forge::source_escape_to_key(&pattern)
} else {
crate::shared::string_pool::pool_key_plain(&pattern)
};
let flags_key = crate::shared::string_pool::pool_key_plain(&flags);
let pat_ci = ctx.add_constant(Constant::String(pattern_key));
let pat_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(pat_reg), pat_ci));
let flags_ci = ctx.add_constant(Constant::String(flags));
let flags_ci = ctx.add_constant(Constant::String(flags_key));
let flags_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(flags_reg), flags_ci));
let r = ctx.alloc_reg();
+174
View File
@@ -120,6 +120,124 @@ pub fn decode_key(text: &str) -> Vec<u16> {
out
}
/// 动态编译源(eval / Function 构造器 / `$262.evalScript`)的单元 → 源码文本
/// 编码。产物直接是可被 oxc 词法分析的源码文本;文本本身是 JS 程序,反斜杠
/// 是语法字符(字面量内构成转义序列),必须原样透传,不得改写:
/// - 孤立 surrogate 单元 U → 文本 `\uXXXX`(4 位小写十六进制);
/// - 单元 `U+FFFD` → 文本 `\ufffd`(裸 FFFD 会被 oxc 判为二进制文件而终止
/// 解析,源码域必须转义);
/// - 良配 surrogate 对 → 对应超平面字符,其余单元(含反斜杠)逐字恒等。
///
/// 字符串/模板字面量中的 `\uXXXX` 转义经 oxc 值域还原为真值(孤立 surrogate
/// 置 `lone_surrogates` 位),再按值域 marker 形态入池,与静态源码同路径。
/// 编码源中的正则字面量按源文本切片经 [`source_escape_to_key`] 还原注入
/// marker 后入池键(静态源切片走 `pool_key_plain`),物化时 `decode_key`
/// 还原原始单元。
pub fn source_escape(units: &[u16]) -> String {
let mut out = String::with_capacity(units.len());
let mut i = 0;
while i < units.len() {
let u = units[i];
if (0xD800..=0xDBFF).contains(&u) && i + 1 < units.len() {
let lo = units[i + 1];
if (0xDC00..=0xDFFF).contains(&lo) {
let cp = 0x10000u32 + (((u as u32) - 0xD800) << 10) + (lo as u32 - 0xDC00);
out.push(char::from_u32(cp).expect("良配 surrogate 对必映射为合法码点"));
i += 2;
continue;
}
}
match u {
0xD800..=0xDFFF => out.push_str(&format!("\\u{:04x}", u)),
0xFFFD => out.push_str("\\ufffd"),
_ => out.push(char::from_u32(u as u32).expect("非 surrogate 单元必为合法码点")),
}
i += 1;
}
out
}
/// [`source_escape`] 的逆路径(源文本域 → 池键域):仅当文本确为
/// `source_escape` 产物(动态编译源的正则字面量源文本切片)时调用;静态源
/// 切片不含注入 marker,直接走 `pool_key_plain`:
/// - `\ud800`..`\udfff`(小写 hex)→ FFFD+hex4(键域孤立 surrogate 形态,
/// 对应 `source_escape` 注入的孤立 surrogate 单元);
/// - `\ufffd` → FFFD+`fffd`(键域 FFFD 转义形态);
/// - 其余反斜杠序列(`\u0041`、`\u{1d306}`、`\n` 等用户转义文本)逐字透传:
/// 正则引擎自行解析转义,物化 pattern 保持源文本形态,`.source` 按书写
/// 返回;
/// - `\\` + marker 转义 形态(数据反斜杠紧邻注入 marker,`source_escape`
/// 拼接产物)按 [数据反斜杠, 单元] 还原;`\\` 后非 marker 时整对透传
/// (用户转义反斜杠)。
///
/// 已知边界:动态源中用户自写的 `\ud800`..`\udfff`/`\ufffd` 小写转义文本
/// (及 `\\` 紧邻该文本)与注入 marker 不可区分,一律还原为单元——只影响
/// 该边缘场景下动态正则 `.source` 的形态(转义文本 vs 原始单元),静态源
/// 不受影响。
pub fn source_escape_to_key(text: &str) -> String {
let chars: Vec<char> = text.chars().collect();
let mut out = String::with_capacity(text.len());
let mut i = 0;
while i < chars.len() {
if chars[i] != '\\' {
out.push(chars[i]);
i += 1;
continue;
}
// `\\` 前瞻:次反斜杠后若紧跟 marker 转义(`u`+4 位小写 hex),首
// 反斜杠归数据、marker 归注入(`source_escape` 把数据反斜杠与
// 注入 marker 拼接成 `\\ud800` 形态);否则 `\\` 对整体透传
// (用户转义反斜杠,正则引擎自行解析)。
if i + 1 < chars.len() && chars[i + 1] == '\\' {
let is_marker = i + 7 <= chars.len()
&& chars[i + 2] == 'u'
&& chars[i + 3..i + 7]
.iter()
.all(|c| matches!(c, '0'..='9' | 'a'..='f'));
if is_marker {
out.push('\\');
i += 1;
} else {
out.push('\\');
out.push('\\');
i += 2;
}
continue;
}
// `\u` marker / 透传:仅 4 位小写 hex 且值域命中(FFFD / surrogate
// 段)还原为键域 marker,其余转义文本逐字透传。
if i + 1 < chars.len() && chars[i + 1] == 'u' {
let tail_len = 4.min(chars.len() - i - 2);
let tail = &chars[i + 2..i + 2 + tail_len];
let ok = tail
.iter()
.all(|c| matches!(c, '0'..='9' | 'a'..='f'));
let v: u32 = ok
.then(|| tail.iter().fold(0u32, |v, c| v * 16 + c.to_digit(16).unwrap()))
.unwrap_or(0);
if tail_len == 4 && ok && (v == 0xFFFD || (0xD800..=0xDFFF).contains(&v)) {
out.push('\u{FFFD}');
out.extend(tail);
} else {
out.push('\\');
out.push('u');
out.extend(tail);
}
i += 2 + tail_len;
continue;
}
// 其余反斜杠组合透传(`\n`、行尾孤立反斜杠等)。
out.push('\\');
if i + 1 < chars.len() {
out.push(chars[i + 1]);
i += 2;
} else {
i += 1;
}
}
out
}
/// 一条 intern 过的键。`data` 是泄漏的 `&'static str`——永久键从不释放
/// (按设计 append-only),所以泄漏即存储模型,而非 bug。键 id 与 64 位
/// 哈希经 `DashMap` 的哈希键→候选 id 表寻址,条目自身不存哈希。
@@ -473,6 +591,62 @@ mod tests {
assert_eq!(decode_key("a\u{FFFD}zz"), &[0x61, 0xFFFD, 0x7A, 0x7A]);
}
#[test]
fn source_escape_marker_forms() {
// 孤立 surrogate / FFFD → 源码域 \u 转义形态;配对与 BMP 恒等。
assert_eq!(source_escape(&[0xD800]), "\\ud800");
assert_eq!(source_escape(&[0xDFFF]), "\\udfff");
assert_eq!(source_escape(&[0xFFFD]), "\\ufffd");
assert_eq!(source_escape(&[0x61, 0xD800, 0x62]), "a\\ud800b");
assert_eq!(source_escape(&[0xD83D, 0xDE00]), "\u{1F600}");
}
/// 源码是 JS 程序,反斜杠为语法字符:原样透传,转义文本形态逐字保留
/// (正则/字符串字面量内 `\u{1d306}`、`\\` 等序列不得被改写)。
#[test]
fn source_escape_backslash_passthrough() {
// 正则字面量源文本(u 模式 \u{..} 转义):逐字恒等。
let re_src: Vec<u16> = "/\\u{1d306}/u".encode_utf16().collect();
assert_eq!(source_escape(&re_src), "/\\u{1d306}/u");
// 转义文本(反斜杠 + "ud800" 字面文本)与孤立 surrogate 单元同形:
// 源码域中 `\ud800` 文本即孤立 surrogate 的源码表示,两者编码同一
// 源码文本是设计使然(源码即程序,oxc 只见转义文本)。
let text: Vec<u16> = "\\ud800".encode_utf16().collect();
assert_eq!(source_escape(&text), "\\ud800");
assert_eq!(source_escape(&[0xD800]), source_escape(&text));
// 双反斜杠(字面量内转义反斜杠)恒等。
let dbl: Vec<u16> = "\\\\".encode_utf16().collect();
assert_eq!(source_escape(&dbl), "\\\\");
}
/// 键域闭环:`decode_key ∘ source_escape_to_key ∘ source_escape` 对单元
/// 序列恒等(注入 marker 还原、用户转义文本透传、双反斜杠与 astral 对)。
#[test]
fn source_escape_roundtrip_via_key() {
let cases: Vec<Vec<u16>> = vec![
vec![0xD800],
vec![0xDFFF],
vec![0xFFFD],
vec![0x5C],
// 用户字面转义文本(非 marker 值域 / \u{..} 形态):逐字透传。
vec![0x5C, 0x75, 0x30, 0x30, 0x34, 0x31],
vec![0x5C, 0x75, 0x7B, 0x31, 0x64, 0x33, 0x30, 0x36, 0x7D],
vec![0x5C, 0x5C],
// 数据反斜杠紧邻注入 marker(`\\ud800`/`\\ufffd` 拼接形态)。
vec![0x5C, 0xD800],
vec![0x5C, 0xFFFD],
// FFFD 单元后跟 "d800" 文本:marker 消费恰 4 字符,不吞后续。
vec![0xFFFD, 0x64, 0x38, 0x30, 0x30],
vec![0x66, 0x66, 0x66, 0x64],
vec![0xD83D, 0xDE00],
];
for units in &cases {
let escaped = source_escape(units);
let key = source_escape_to_key(&escaped);
assert_eq!(&decode_key(&key), units.as_slice(), "roundtrip 失败: escaped={escaped:?}");
}
}
#[test]
fn string_ptr_materializes_units() {
let interner = PermInterner::new();
+5 -3
View File
@@ -9,7 +9,7 @@ use std::sync::Arc;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{to_string_full, NativeResult};
use oxide_runtime_api::{to_units_full, NativeResult};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
@@ -36,10 +36,12 @@ pub fn eval_script(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::undefined());
}
let code = match to_string_full(vm.reg(args[1]), vm) {
Ok(s) => s,
let units = match to_units_full(vm.reg(args[1]), vm) {
Ok(u) => u,
Err(e) => return NativeResult::Err(oxide_builtins::error::create_from_text(vm, &e)),
};
// 动态路径源契约:源码域转义形态传源(见 `create_dynamic_function`)。
let code = oxide_kernel::string_forge::source_escape(&units);
match vm.create_dynamic_function(&[], &code) {
Ok(func) => {
let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject);
+60 -3
View File
@@ -75,7 +75,9 @@ fn canonical_index_units(units: &[u16]) -> Option<u32> {
0x30..=0x39 => u - 0x30,
_ => return None,
};
v = v.checked_mul(10)? + d as u32;
// 两位都须 overflow 检查:`checked_mul(10) + d` 的裸加在 10 位串
// (如 "4294967296")会 mod 2^32 回绕出假规范下标。
v = v.checked_mul(10)?.checked_add(d as u32)?;
}
(v < INT_KEY_COUNT).then_some(v)
}
@@ -1424,7 +1426,14 @@ impl Vm {
// 其它原始值(BigInt 等):ToPropertyKey 一律转字符串并走规范化,避免与
// 数字键区间分裂(`o[5n]` 与 `o["5"]`/`o[5]` 必须同键)。
let units = oxide_runtime_api::to_units_full(val, self)?;
Ok(self.string_key_units(&units))
if std::env::var("OXIDE_KEY_PROBE").is_ok() {
eprintln!("[KEYPROBE] tail fallback units={:?}", units);
}
let si = self.string_key_units(&units);
if std::env::var("OXIDE_KEY_PROBE").is_ok() {
eprintln!("[KEYPROBE] tail result si={}", si);
}
Ok(si)
}
/// 从单元序列推导属性键 si(`property_key_si` 字符串分支的口径抽取):
@@ -2697,6 +2706,11 @@ impl Vm {
/// 动态编译函数(`Function` 构造器路径):把参数列表与函数体 wrap 成匿名函数
/// 源码,走完整编译链后取匿名函数模块追加进 VM 平表,返回对应函数对象。
///
/// # 源契约
/// `params` 与 `body` 须为 `string_forge::source_escape` 产物(源码域转义
/// 形态):孤立 surrogate / FFFD 单元以 `\uXXXX` 转义文本承载,反斜杠
/// 原样透传。
///
/// # 步骤
/// 1. wrap 源码 `function anonymous(p...) { body }`,parse + compile。
/// 2. 取 `sub_modules[0]`(匿名函数模块),把其子树 flat_id 重编号到平表末尾
@@ -2720,7 +2734,10 @@ impl Vm {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, &source)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
let mut module = oxide_compiler::compiler::Compiler::new().compile(&program)?;
// 动态路径源契约:调用方以源码域转义(`string_forge::source_escape`)
// 形态传源;正则字面量源文本常量与静态同路径(`pool_key_plain`)。
let mut module =
oxide_compiler::compiler::Compiler::new().with_source_encoded(true).compile(&program)?;
let anonymous = module.sub_modules.remove(0);
// 形参数以编译结果为准:单个实参 "a,b,c" 拼接后解析为 3 个形参
// (ES 动态函数把非末位实参以逗号连接成参数串再解析)。
@@ -2771,8 +2788,10 @@ impl Vm {
let program = oxide_parser::parse(&allocator, code)
.map_err(|errs| errs.into_iter().map(|e| e.message).collect::<Vec<_>>().join("\n"))?;
// eval 脚本:顶层 var/function 声明落全局属性 configurable:true。
// 动态路径源契约同 `create_dynamic_function`(源码域转义形态传源)。
let module = oxide_compiler::compiler::Compiler::new()
.with_eval_script(true)
.with_source_encoded(true)
.compile(&program)?;
// 根模块 flat_id=0 传 base+1,重编号后落 base(避开 sub_module_index()==0
// 守卫)。make_mut 彼时平表 Arc 强引用唯一持有者是本表,原地扩展不分叉。
@@ -2833,6 +2852,8 @@ impl Vm {
#[cfg(test)]
mod tests {
use super::{opcode, ForOfEntry, JsValue, TryHandler, Vm};
use super::{canonical_index_of, canonical_index_units};
use oxide_types::private_key::INT_KEY_COUNT;
use oxide_bytecode::module::CompiledModule;
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_types::object::NativeFnPtr;
@@ -3389,4 +3410,40 @@ mod tests {
);
assert!(!frame.is_derived_constructor, "普通函数非 derived 构造器");
}
fn units_of(s: &str) -> Vec<u16> {
s.chars().map(|c| c as u16).collect()
}
#[test]
fn canonical_index_str_boundaries() {
assert_eq!(canonical_index_of("0"), Some(0));
assert_eq!(canonical_index_of("1073741823"), Some(INT_KEY_COUNT - 1));
// 2^30 起不再是整数键候选;2^32 边界串(ToUint32 回绕源)必须走字符串键
assert_eq!(canonical_index_of("1073741824"), None);
assert_eq!(canonical_index_of("4294967295"), None);
assert_eq!(canonical_index_of("4294967296"), None);
assert_eq!(canonical_index_of("4294967299"), None);
// 非规范形态:前导零、符号、小数、空串
assert_eq!(canonical_index_of("007"), None);
assert_eq!(canonical_index_of("-1"), None);
assert_eq!(canonical_index_of("1.5"), None);
assert_eq!(canonical_index_of(""), None);
}
#[test]
fn canonical_index_units_boundaries() {
assert_eq!(canonical_index_units(&units_of("0")), Some(0));
assert_eq!(canonical_index_units(&units_of("1073741823")), Some(INT_KEY_COUNT - 1));
assert_eq!(canonical_index_units(&units_of("1073741824")), None);
// 10 位串在 u32 累加尾位溢出:回绕会造出假规范下标,须溢出检查归 None
assert_eq!(canonical_index_units(&units_of("4294967295")), None);
assert_eq!(canonical_index_units(&units_of("4294967296")), None);
assert_eq!(canonical_index_units(&units_of("4294967299")), None);
assert_eq!(canonical_index_units(&units_of("9999999999")), None);
assert_eq!(canonical_index_units(&units_of("007")), None);
assert_eq!(canonical_index_units(&units_of("-1")), None);
assert_eq!(canonical_index_units(&units_of("1.5")), None);
assert_eq!(canonical_index_units(&[]), None);
}
}