Files
OxideJS-New/crates/oxide_vm/tests/native_pack_window_tests.rs

271 lines
9.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! native pack 实参区 × 恢复边界镜像重载的引擎钉:native pack 协议在飞
//! (`.call`/`.apply` 转发、直接成员调用)期间,pack 与实参读取之间若存在用户
//! 内联窗(length getter / 回调),窗口拷回后的调用方模块镜像重载会把顶层
//! 镜像名集(Array/Object 着落于 pack 区寄存器)重写回寄存器,覆写外层
//! builtin 尚未读取的实参值。期望值一律取 node v20.19.2 实测值。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
Compiler::new().compile(&program).expect("compile")
}
/// 运行脚本并取最终值(钉的期望值统一为字符串形态)。
fn eval_str(source: &str) -> String {
let module = compile(source);
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module)).expect("run");
vm.lookup_str(result).expect("结果应为字符串").to_string()
}
// 共用的 length getter 装置:基元/对象 this 的 length 走用户 accessor,
// accessor 内联窗在 pack 与实参读取之间打开。
const LENGTH_GETTER: &str = "var savedLen = 1; \
Object.defineProperty(Number.prototype, 'length', { \
configurable: true, \
get: function () { return savedLen; }, \
set: function (v) { savedLen = v; } }); \
";
/// 在 body 脚本前拼入 length getter 装置,返回完整脚本。
fn with_length_getter(body: &str) -> String {
format!("{LENGTH_GETTER}{body}")
}
/// `.call` 转发:splice 的 deleteCount 落入 reg 1(顶层镜像着色域),
/// length getter 内联窗先于实参读取,窗口拷回后的镜像重载不得覆写实参区。
#[test]
fn splice_call_length_getter_keeps_delete_count() {
assert_eq!(
eval_str(&with_length_getter(
"var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
)),
"1|2"
);
}
/// 对象 this 同形态:装箱非触发条件,覆写对对象接收者同样成立。
#[test]
fn splice_object_this_length_getter_keeps_delete_count() {
assert_eq!(
eval_str(
"var savedLen = 1; \
var o = {}; \
Object.defineProperty(o, 'length', { \
configurable: true, \
get: function () { return savedLen; }, \
set: function (v) { savedLen = v; } }); \
var r = Array.prototype.splice.call(o, 0, 1, 'a', 'b'); \
[r.length, savedLen].join('|')"
),
"1|2"
);
}
/// 最小 getter 体(无副作用):覆写与 getter 复杂度无关,是最小判别形。
#[test]
fn splice_minimal_getter_keeps_delete_count() {
assert_eq!(
eval_str(
"var savedLen = 1; \
Object.defineProperty(Number.prototype, 'length', { \
configurable: true, \
get: function () { return 1; }, \
set: function (v) { savedLen = v; } }); \
var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
),
"1|2"
);
}
/// 2 实参 pack 区 [0, 2) 恰好覆盖第一个镜像寄存器:deleteCount 读值不得失真。
#[test]
fn splice_two_arg_pack_keeps_delete_count() {
assert_eq!(
eval_str(&with_length_getter(
"var r = Array.prototype.splice.call(5, 0, 1); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
)),
"1|0"
);
}
/// 大 getter 体(字符串 churn、寄存器需求上升):覆写与窗宽无关,窗宽充分。
#[test]
fn splice_churn_getter_keeps_delete_count() {
assert_eq!(
eval_str(
"var savedLen = 1; \
Object.defineProperty(Number.prototype, 'length', { \
configurable: true, \
get: function () { \
var s = ''; \
for (var i = 0; i < 200; i++) { s = s + 'churn' + i; } \
globalThis.__churn = s; \
return savedLen; \
}, \
set: function (v) { savedLen = v; } }); \
var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
),
"1|2"
);
}
/// fill 的 start/end 落 pack 区 reg 1/2:区间两端读值不得折算失真,
/// 填充范围与未填充位保持 node 语义(length getter 开窗)。
#[test]
fn fill_arraylike_length_getter_keeps_range() {
assert_eq!(
eval_str(
"var o = {}; \
Object.defineProperty(o, 'length', { \
configurable: true, \
get: function () { return 2; }, \
set: function (v) {} }); \
var r = Array.prototype.fill.call(o, 'v', 0, 1); \
[r[0] === 'v' ? 1 : 0, r[1] === undefined ? 1 : 0].join('|')"
),
"1|1"
);
}
/// 5 实参 pack 区 [0, 5) 全量覆盖镜像着色集:全部实参存活,结果与插入区
/// 逐位保持。
#[test]
fn splice_five_arg_pack_keeps_all_args() {
assert_eq!(
eval_str(&with_length_getter(
"var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b', 'c'); \
delete Number.prototype.length; \
[r.length, r[0], r[1], r[2], savedLen].join('|')"
)),
"1||||3"
);
}
/// getter 自读镜像(`Array === Array`):内联入口重载自愈,修后保真——
/// 入口重载语义不动,恢复边界 gate 不影响入口侧镜像可见性。
#[test]
fn splice_getter_mirror_self_read_stays_true() {
assert_eq!(
eval_str(
"var savedLen = 1; \
Object.defineProperty(Number.prototype, 'length', { \
configurable: true, \
get: function () { return Array === Array && Object === Object ? 1 : 0; }, \
set: function (v) { savedLen = v; } }); \
var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
),
"1|2"
);
}
/// getter 内嵌套用户 CALL(帧路径):帧返回后的恢复边界同门控,帧路径
/// 结果与无嵌套时逐位一致。
#[test]
fn splice_getter_nested_call_keeps_result() {
assert_eq!(
eval_str(
"var savedLen = 1; \
function probe() { return 42; } \
Object.defineProperty(Number.prototype, 'length', { \
configurable: true, \
get: function () { var x = probe(); return savedLen; }, \
set: function (v) { savedLen = v; } }); \
var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
),
"1|2"
);
}
/// indexOf 的 fromIndex 落 pack 区 reg 1:arraylike 接收者 length getter
/// 开窗后 fromIndex 读值不得折算为 0。
#[test]
fn index_of_arraylike_length_getter_keeps_from_index() {
assert_eq!(
eval_str(
"var savedLen = 3; \
var a = { 0: 10, 1: 20, 2: 10 }; \
Object.defineProperty(a, 'length', { \
configurable: true, \
get: function () { return savedLen; }, \
set: function (v) { savedLen = v; } }); \
String(Array.prototype.indexOf.call(a, 10, 1))"
),
"2"
);
}
/// reduce 的 init 落 pack 区 reg 2:arraylike length getter 开窗后
/// init 读值不得被镜像值(Object 内置函数)替换。
#[test]
fn reduce_arraylike_length_getter_keeps_init() {
assert_eq!(
eval_str(
"var savedLen = 1; \
var a = { 0: 12 }; \
Object.defineProperty(a, 'length', { \
configurable: true, \
get: function () { return savedLen; }, \
set: function (v) { savedLen = v; } }); \
String(Array.prototype.reduce.call(a, function (x, y) { return x + y; }, 1))"
),
"13"
);
}
/// `.apply` 转发同形态:TailCall 链 pack_end 嵌套存还后实参区保持。
#[test]
fn splice_apply_forward_keeps_delete_count() {
assert_eq!(
eval_str(&with_length_getter(
"var r = Array.prototype.splice.apply(5, [0, 1, 'a', 'b']); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
)),
"1|2"
);
}
/// 无用户内联窗回归:无窗时 pack 区无覆写风险点,结果保持 node 语义。
#[test]
fn splice_no_window_result_unchanged() {
assert_eq!(
eval_str(
"var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \
[r.length, r[0], r[1]].join('|')"
),
"0||"
);
}
/// 1 实参 pack 区 [0, 1) 不与镜像着色集求交:reg 0 恒为结果槽,此形无风险。
#[test]
fn splice_one_arg_pack_stays_safe() {
assert_eq!(
eval_str(&with_length_getter(
"var r = Array.prototype.splice.call(5, 0); \
delete Number.prototype.length; \
[r.length, savedLen].join('|')"
)),
"1|0"
);
}