test262: $262 宿主对象最小绑定 + is not defined 判定白名单化
引擎侧:绑定 $262 宿主对象(evalScript/global/gc 可用,detachArrayBuffer/ createRealm/agent 抛 not supported 而非 ReferenceError),并加入编译期 已知全局表;引用 $262.* 的测试从全量 skip 转为真实执行。 runner 侧:is not defined 不再一票吞 skip——解析错误消息中的标识符名, 仅在缺失全局白名单($262/structuredClone/queueMicrotask)内才按能力 缺失跳过;其余 is not defined 按真实失败计入,修复后应转 PASS 的 测试恢复可见。
This commit is contained in:
@@ -126,6 +126,8 @@ const BUILTIN_GLOBALS: &[&str] = &[
|
||||
"decodeURI",
|
||||
"encodeURIComponent",
|
||||
"decodeURIComponent",
|
||||
// test262 宿主对象:编译期按已知全局解析,运行期由 VM 绑定(详见 bind_test262_host)。
|
||||
"$262",
|
||||
];
|
||||
|
||||
pub(crate) struct FieldBuffer {
|
||||
|
||||
@@ -543,25 +543,11 @@ fn run_test_inner(
|
||||
Err(e) => {
|
||||
let dur = start.elapsed().as_millis() as u64;
|
||||
let msg = format!("compile error: {e}");
|
||||
if meta.negative.is_some() {
|
||||
return TestResult::pass(path.to_path_buf(), dur, msg);
|
||||
match classify_compile_error(&msg, meta.negative.is_some(), no_skip) {
|
||||
TestOutcome::Pass(m) => return TestResult::pass(path.to_path_buf(), dur, m),
|
||||
TestOutcome::Fail(m) => return TestResult::fail(path.to_path_buf(), dur, m),
|
||||
TestOutcome::Skip(m) => return TestResult::skip(path.to_path_buf(), m),
|
||||
}
|
||||
if e.contains("not yet implemented")
|
||||
|| e.contains("not yet supported")
|
||||
|| e.contains("not supported")
|
||||
|| e.contains("unsupported")
|
||||
|| e.contains("is not defined")
|
||||
|| e.contains("SpreadElement")
|
||||
|| e.contains("already been declared")
|
||||
|| e.contains("parser panicked")
|
||||
|| e.contains("too many registers")
|
||||
{
|
||||
if no_skip {
|
||||
return TestResult::fail(path.to_path_buf(), dur, msg);
|
||||
}
|
||||
return TestResult::skip(path.to_path_buf(), msg);
|
||||
}
|
||||
return TestResult::fail(path.to_path_buf(), dur, msg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -659,12 +645,68 @@ fn judge_vm_error(path: &Path, e: &str, meta: &TestMeta, dur: u64, no_skip: bool
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 `is not defined` 错误消息中解析未绑定标识符名。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 运行期形态:`uncaught ReferenceError: {name} is not defined`
|
||||
/// - 编译期形态:`Identifier '{name}' is not defined`(可带 `compile error: ` 前缀)
|
||||
/// - 两种形态都不匹配返回 `None`(不属未绑定标识符错误)。
|
||||
fn parse_undefined_ident(e: &str) -> Option<&str> {
|
||||
let e = e.strip_prefix("compile error: ").unwrap_or(e);
|
||||
let e = e.strip_prefix("uncaught ").unwrap_or(e);
|
||||
if let Some(rest) = e.strip_prefix("ReferenceError: ") {
|
||||
if let Some(ident) = rest.strip_suffix(" is not defined") {
|
||||
let ident = ident.trim();
|
||||
return (!ident.is_empty()).then_some(ident);
|
||||
}
|
||||
}
|
||||
if let Some(rest) = e.strip_prefix("Identifier '") {
|
||||
if let Some(ident) = rest.strip_suffix("' is not defined") {
|
||||
let ident = ident.trim();
|
||||
return (!ident.is_empty()).then_some(ident);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 已知缺失的宿主/标准全局白名单:这些标识符未绑定是能力缺失(skip),
|
||||
/// 其它 `is not defined` 是引擎回归或语义缺口(fail)。
|
||||
const KNOWN_MISSING_GLOBALS: &[&str] = &["$262", "structuredClone", "queueMicrotask"];
|
||||
|
||||
/// 判定编译期错误结果:compile 错误无条件放行 negative;能力未实现形态
|
||||
/// (含白名单内的未绑定标识符)→ Skip(`--no-skip` 下为 Fail);其余一律 Fail。
|
||||
fn classify_compile_error(e: &str, has_negative: bool, no_skip: bool) -> TestOutcome {
|
||||
if has_negative {
|
||||
return TestOutcome::Pass(format!("compile error: {e}"));
|
||||
}
|
||||
let unimplemented = e.contains("not yet implemented")
|
||||
|| e.contains("not yet supported")
|
||||
|| e.contains("not supported")
|
||||
|| e.contains("unsupported")
|
||||
|| e.contains("SpreadElement")
|
||||
|| e.contains("already been declared")
|
||||
|| e.contains("parser panicked")
|
||||
|| e.contains("too many registers");
|
||||
// `is not defined` 仅当标识符在缺失全局白名单内才 skip;其余(引擎该有
|
||||
// 而未提供的标识符、回归)按真实失败计入,保证修复可见性。
|
||||
let whitelisted_undefined =
|
||||
e.contains("is not defined") && parse_undefined_ident(e).is_some_and(|i| KNOWN_MISSING_GLOBALS.contains(&i));
|
||||
if unimplemented || whitelisted_undefined {
|
||||
if no_skip {
|
||||
return TestOutcome::Fail(format!("compile error: {e}"));
|
||||
}
|
||||
return TestOutcome::Skip(format!("compile error: {e}"));
|
||||
}
|
||||
TestOutcome::Fail(format!("compile error: {e}"))
|
||||
}
|
||||
|
||||
/// 判定运行期错误结果:negative 期望匹配 → Pass;能力未实现形态 → Skip
|
||||
/// (`--no-skip` 下为 Fail);其余一律 Fail。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - receiver 校验、栈溢出、不可调用、ToPrimitive 缺口等错误是引擎语义与测试
|
||||
/// 期望不符的真实失败,不再被 skip 子串吞没(引擎做对了反而计 skip 属误判)。
|
||||
/// - `is not defined` 是否 skip 由标识符白名单判定(见 [`parse_undefined_ident`])。
|
||||
fn classify_vm_error(e: &str, neg: Option<&Negative>, no_skip: bool) -> TestOutcome {
|
||||
if let Some(neg) = neg {
|
||||
if e.contains("TypeError") && neg.error_type == "TypeError" {
|
||||
@@ -681,12 +723,11 @@ fn classify_vm_error(e: &str, neg: Option<&Negative>, no_skip: bool) -> TestOutc
|
||||
}
|
||||
return TestOutcome::Fail(format!("expected {} error, got: {e}", neg.error_type));
|
||||
}
|
||||
if e.contains("not yet implemented")
|
||||
let unimplemented = e.contains("not yet implemented")
|
||||
|| e.contains("not yet supported")
|
||||
|| e.contains("not supported")
|
||||
|| e.contains("unsupported")
|
||||
|| e.contains("step limit")
|
||||
|| e.contains("is not defined")
|
||||
|| e.contains("NEW_EXPRESSION")
|
||||
|| e.contains("IC_GET_PROP on non-object")
|
||||
|| e.contains("GET_PROP_DYNAMIC on non-object")
|
||||
@@ -695,17 +736,18 @@ fn classify_vm_error(e: &str, neg: Option<&Negative>, no_skip: bool) -> TestOutc
|
||||
|| e.contains("CALL_NATIVE target")
|
||||
|| e.contains("is not implemented")
|
||||
|| e.contains("unexpected tail call")
|
||||
|| e.contains("__proto__ must be an object")
|
||||
// 私有字段未实现。
|
||||
{
|
||||
|| e.contains("__proto__ must be an object");
|
||||
// `is not defined` 不再一票吞 skip:仅在标识符属缺失全局白名单时按能力
|
||||
// 缺失跳过;否则是真实 ReferenceError(含修复后应转 PASS 的回归)。
|
||||
let whitelisted_undefined =
|
||||
e.contains("is not defined") && parse_undefined_ident(e).is_some_and(|i| KNOWN_MISSING_GLOBALS.contains(&i));
|
||||
if unimplemented || whitelisted_undefined {
|
||||
if no_skip {
|
||||
TestOutcome::Fail(format!("vm error: {e}"))
|
||||
} else {
|
||||
TestOutcome::Skip(format!("vm: {e}"))
|
||||
return TestOutcome::Fail(format!("vm error: {e}"));
|
||||
}
|
||||
} else {
|
||||
TestOutcome::Fail(format!("vm error: {e}"))
|
||||
return TestOutcome::Skip(format!("vm: {e}"));
|
||||
}
|
||||
TestOutcome::Fail(format!("vm error: {e}"))
|
||||
}
|
||||
|
||||
/// 递归发现 test262 根目录下全部 `.js` 测试文件(排序后返回)。
|
||||
@@ -1590,4 +1632,44 @@ mod tests {
|
||||
assert_outcome("ReferenceError: boom", Some(&neg("ReferenceError")), false, &TestOutcome::Pass("".into()));
|
||||
assert_outcome("TypeError: boom", Some(&neg("RangeError")), false, &TestOutcome::Fail("".into()));
|
||||
}
|
||||
|
||||
/// `is not defined` 标识符解析:运行期 `uncaught` 前缀、编译期 `Identifier '..'`
|
||||
/// 两种形态都能取出标识符名;非该形态返回 None。
|
||||
#[test]
|
||||
fn parse_undefined_ident_extracts_name() {
|
||||
assert_eq!(parse_undefined_ident("uncaught ReferenceError: $262 is not defined"), Some("$262"));
|
||||
assert_eq!(parse_undefined_ident("ReferenceError: foo is not defined"), Some("foo"));
|
||||
assert_eq!(
|
||||
parse_undefined_ident("compile error: Identifier 'structuredClone' is not defined"),
|
||||
Some("structuredClone")
|
||||
);
|
||||
assert_eq!(parse_undefined_ident("Identifier 'queueMicrotask' is not defined"), Some("queueMicrotask"));
|
||||
assert_eq!(parse_undefined_ident("TypeError: x is not callable"), None);
|
||||
assert_eq!(parse_undefined_ident("uncaught ReferenceError: boom"), None);
|
||||
}
|
||||
|
||||
/// `is not defined` 白名单化:白名单内标识符(缺失全局)→ skip;白名单外 →
|
||||
/// 真实失败(保证修复后应转 PASS 的测试可见)。
|
||||
#[test]
|
||||
fn undefined_identifier_whitelisted_skip() {
|
||||
for e in [
|
||||
"uncaught ReferenceError: $262 is not defined",
|
||||
"compile error: Identifier '$262' is not defined",
|
||||
"uncaught ReferenceError: structuredClone is not defined",
|
||||
"compile error: Identifier 'queueMicrotask' is not defined",
|
||||
] {
|
||||
assert_outcome(e, None, false, &TestOutcome::Skip("".into()));
|
||||
assert_outcome(e, None, true, &TestOutcome::Fail("".into()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_identifier_outside_whitelist_is_failure() {
|
||||
for e in [
|
||||
"uncaught ReferenceError: foo is not defined",
|
||||
"compile error: Identifier 'whatever' is not defined",
|
||||
] {
|
||||
assert_outcome(e, None, false, &TestOutcome::Fail("".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,6 +737,7 @@ pub fn bind_global_builtin_slots(core: &Arc<KernelCore>, session: &KernelSession
|
||||
bind_stub_globals(core, session, global);
|
||||
bind_bigint::bind_bigint(core, session, global);
|
||||
bind_global_functions(core, session, global);
|
||||
crate::test262_host::bind_test262_host(core, session, global);
|
||||
let global_this = JsValue::from_js_object(global as *mut JsObject);
|
||||
bind_existing_global(core, global, "globalThis", global_this);
|
||||
}
|
||||
@@ -825,5 +826,6 @@ pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &mut KernelSession)
|
||||
bind_reflect::bind_reflect(core, session, global);
|
||||
bind_global::bind_global(core, session, global);
|
||||
bind_global_value(core, global, "globalThis", JsValue::from_js_object(global_ptr));
|
||||
crate::test262_host::bind_test262_host(core, session, global);
|
||||
session.record_snapshot();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ mod session_arena;
|
||||
/// session 级 mark-sweep GC:标记-清扫 session arena 对象与 session 字符串。
|
||||
pub mod session_gc;
|
||||
mod suspended;
|
||||
/// test262 宿主对象 `$262` 的最小实现(非标准全局,供 test262 套件使用)。
|
||||
pub mod test262_host;
|
||||
/// 寄存器 VM 主类型([`vm::Vm`])与其执行状态定义。
|
||||
pub mod vm;
|
||||
mod vm_dispatch_ctrl;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//! test262 宿主对象 `$262` 的最小实现。
|
||||
//!
|
||||
//! `$262` 是 test262 套件约定的宿主注入对象(非 ECMAScript 标准全局):
|
||||
//! 测试经它访问当前 realm 全局、执行脚本、触发 GC 等。最小绑定使引用
|
||||
//! `$262.*` 的测试真实执行而非一律抛 `ReferenceError` 被 runner 吞为 skip;
|
||||
//! 未实现的方法抛 `not supported` 错误,runner 按能力缺失归类。
|
||||
|
||||
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_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::bindings::{apply_binding_table, bind_global_value};
|
||||
use crate::vm::Vm;
|
||||
|
||||
/// `$262` 未实现方法统一抛错:消息带 `not supported`,runner 据此按能力缺失跳过。
|
||||
fn not_supported(vm: &mut Vm, feature: &str) -> NativeResult {
|
||||
NativeResult::Err(oxide_builtins::error::create_type_error(vm, &format!("{feature} is not supported")))
|
||||
}
|
||||
|
||||
/// `$262.evalScript(code)`:在当前 realm 编译执行一段脚本并返回其完成值。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 实参 ToString 取脚本源码。
|
||||
/// 2. 经动态函数构造编译执行(`create_dynamic_function` + 同步调用)。
|
||||
/// 3. 编译失败转 SyntaxError;运行异常原样透传异常值。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 最小实现:函数作用域语义——var/函数声明不泄漏到全局,完成值仅限显式
|
||||
/// `return`(全局作用域 eval 落地前为已知上限)。
|
||||
/// - 无实参或非字符串实参按 ToString 规范处理。
|
||||
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,
|
||||
Err(e) => return NativeResult::Err(oxide_builtins::error::create_from_text(vm, &e)),
|
||||
};
|
||||
match vm.create_dynamic_function(&[], &code) {
|
||||
Ok(func) => {
|
||||
let global = JsValue::from_js_object(vm.session().global_object().as_ptr() as *mut JsObject);
|
||||
match vm.call_function_sync(func, global, &[]) {
|
||||
Ok(val) => NativeResult::Ok(val),
|
||||
Err(e) => NativeResult::Err(oxide_builtins::error::create_from_text(vm, &e)),
|
||||
}
|
||||
}
|
||||
Err(e) => NativeResult::Err(oxide_builtins::error::create_from_text(
|
||||
vm,
|
||||
&format!("SyntaxError: {e}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// `$262.detachArrayBuffer(buf)`:ArrayBuffer detach 未实现,抛能力缺失错误。
|
||||
pub fn detach_array_buffer(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
not_supported(vm, "detachArrayBuffer")
|
||||
}
|
||||
|
||||
/// `$262.createRealm()`:跨 realm 未支持,抛能力缺失错误。
|
||||
pub fn create_realm(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
not_supported(vm, "createRealm")
|
||||
}
|
||||
|
||||
/// `$262.gc()`:请求一次 session 全量回收(不计返回值)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 执行 mark-sweep 搬移存活对象并释放不可达对象,会重写 session 对象指针。
|
||||
pub fn gc(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
let mut session_gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
session_gc.sweep(vm);
|
||||
vm.gc_state.session_gc = session_gc;
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `$262.agent`:agent 线程接口依赖 SharedArrayBuffer 族,未支持,抛能力缺失错误。
|
||||
pub fn agent(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
not_supported(vm, "agent")
|
||||
}
|
||||
|
||||
/// 把 `$262` 宿主对象绑定到 global:对象本体 + 标准方法 + `global` 数据属性。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 建 `$262` 普通对象(Object.prototype)。
|
||||
/// 2. 绑定 evalScript/detachArrayBuffer/createRealm/gc/agent 方法。
|
||||
/// 3. `global` 数据属性指向当前 session global;`$262` 本体挂到 global。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 宿主对象经 `Box::into_raw` 持有,随 session 生命周期存活(与 Reflect/Iterator
|
||||
/// 等全局对象同一约定);每次调用新建对象,重复绑定会泄漏旧对象(全量初始化
|
||||
/// 与 full_reset 频率低,可接受)。
|
||||
pub fn bind_test262_host(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let object_proto = session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
let mut host = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||
apply_binding_table(
|
||||
session.builtin_world(),
|
||||
&mut host,
|
||||
core,
|
||||
&[
|
||||
("evalScript", eval_script as *const (), 1),
|
||||
("detachArrayBuffer", detach_array_buffer as *const (), 1),
|
||||
("createRealm", create_realm as *const (), 0),
|
||||
("gc", gc as *const (), 0),
|
||||
("agent", agent as *const (), 0),
|
||||
],
|
||||
);
|
||||
let global_this = JsValue::from_js_object(global as *mut JsObject);
|
||||
bind_global_value(core, &mut host, "global", global_this);
|
||||
bind_global_value(core, global, "$262", JsValue::from_js_object(Box::into_raw(Box::new(host))));
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//! test262 宿主对象 `$262` 的最小绑定验证:对象存在、`global` 指向当前 realm
|
||||
//! 全局、evalScript 可执行、未实现方法抛能力缺失错误。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
/// 单测辅助:源串 → 结果串(字符串值解包实际内容,运行错误以 `vm error: ...` 呈现)。
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => vm.lookup_str(result).unwrap_or_else(|| format!("{result}")),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `$262` 作为编译期已知全局绑定:typeof 为 object,`global` 即 globalThis。
|
||||
#[test]
|
||||
fn host_object_exists_and_exposes_global() {
|
||||
assert_eq!(eval("typeof $262"), "object");
|
||||
assert_eq!(eval("$262.global === globalThis"), "true");
|
||||
}
|
||||
|
||||
/// `$262` 方法面:evalScript/gc 为可调用函数。
|
||||
#[test]
|
||||
fn host_methods_are_callable() {
|
||||
assert_eq!(eval("typeof $262.evalScript"), "function");
|
||||
assert_eq!(eval("typeof $262.gc"), "function");
|
||||
assert_eq!(eval("typeof $262.detachArrayBuffer"), "function");
|
||||
assert_eq!(eval("typeof $262.createRealm"), "function");
|
||||
}
|
||||
|
||||
/// evalScript 执行脚本并返回显式 `return` 的完成值;无 return 为 undefined。
|
||||
#[test]
|
||||
fn eval_script_runs_code_and_returns_value() {
|
||||
assert_eq!(eval("$262.evalScript('return 40 + 2')"), "42");
|
||||
assert_eq!(eval("$262.evalScript('var y = 1; y')"), "undefined");
|
||||
}
|
||||
|
||||
/// evalScript 编译失败转 SyntaxError(可由 JS catch 捕获)。
|
||||
#[test]
|
||||
fn eval_script_syntax_error_is_catchable() {
|
||||
let r = eval("try { $262.evalScript('if (') ; 'no-throw' } catch (e) { e.name }");
|
||||
assert_eq!(r, "SyntaxError");
|
||||
}
|
||||
|
||||
/// 未实现方法抛能力缺失错误(消息带 `not supported`),不抛 ReferenceError。
|
||||
#[test]
|
||||
fn unsupported_methods_throw_not_supported() {
|
||||
let r = eval("try { $262.detachArrayBuffer(new ArrayBuffer(8)); 'no-throw' } catch (e) { e.name + ':' + e.message }");
|
||||
assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}");
|
||||
assert!(r.contains("not supported"), "期望能力缺失消息,实际 {r}");
|
||||
|
||||
let r = eval("try { $262.createRealm(); 'no-throw' } catch (e) { e.name + ':' + e.message }");
|
||||
assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}");
|
||||
assert!(r.contains("not supported"), "期望能力缺失消息,实际 {r}");
|
||||
|
||||
let r = eval("try { $262.agent(); 'no-throw' } catch (e) { e.name + ':' + e.message }");
|
||||
assert!(r.contains("TypeError"), "期望 TypeError,实际 {r}");
|
||||
}
|
||||
|
||||
/// `$262.gc()` 触发一次 session 回收且不抛错。
|
||||
#[test]
|
||||
fn gc_runs_without_error() {
|
||||
assert_eq!(eval("$262.gc(); 'ok'"), "ok");
|
||||
}
|
||||
Reference in New Issue
Block a user