fix: 补齐代理读取组与完整性级别语义

This commit is contained in:
2026-10-18 06:10:48 +00:00
parent 7f15f84139
commit 1cb943beee
16 changed files with 684 additions and 224 deletions
@@ -1273,9 +1273,10 @@ fn sab_new_target_proto_and_fallback() {
var d = Reflect.construct(SharedArrayBuffer, [4], D); \
if (Object.getPrototypeOf(d) !== SharedArrayBuffer.prototype) { return false; } \
var E = function () {}; \
Object.defineProperty(E, 'prototype', { get: function () { throw new RangeError('x'); } }); \
var e_proxy = new Proxy(E, { get: function (t, k) { \
if (k === 'prototype') { throw new RangeError('x'); } return t[k]; } }); \
var threw = false; \
try { Reflect.construct(SharedArrayBuffer, [1], E); } catch (e) { threw = e instanceof RangeError; } \
try { Reflect.construct(SharedArrayBuffer, [1], e_proxy); } catch (e) { threw = e instanceof RangeError; } \
return threw; })()",
)
.unwrap();
@@ -483,9 +483,9 @@ fn function_prototype_new_throws_catchable_type_error() {
#[test]
fn dynamic_function_prototype_descriptor_not_configurable() {
// 动态函数(Function 构造器产物)的 prototype 属性为
// {writable:true, enumerable:false, configurable:false},delete 失败且值不变;
// 普通函数的 prototype 属性保持可配置,不回归。
// 动态函数(Function 构造器产物)与普通函数的 prototype 属性均为
// {writable:true, enumerable:false, configurable:false}(规范 10.2.1 口径),
// delete 失败且值不变。
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
let result = eval(
@@ -505,7 +505,14 @@ fn dynamic_function_prototype_descriptor_not_configurable() {
"function g() {} var d = Object.getOwnPropertyDescriptor(g, 'prototype'); d.writable + ',' + d.enumerable + ',' + d.configurable",
)
.unwrap();
assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,true");
assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,false");
// 普通函数同口径:delete 失败且值不变。
let result = eval(
&mut vm,
"function h() {} var p = h.prototype; delete h.prototype; h.prototype === p",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
+16 -6
View File
@@ -267,11 +267,21 @@ fn regexp_exec_negative_last_index_clamped_to_zero() {
#[test]
fn regexp_exec_last_index_set_once_on_success() {
// 成功路径 lastIndex 恰好 Set 一次(写回在核内单点):计数 setter 只被观察一次。
let source = "(() => { var re = /a/g; var n = 0; Object.defineProperty(re, 'lastIndex', { configurable: true, set() { n += 1; }, get() { return 0; } }); re.exec('a'); return String(n); })()";
// 成功路径 lastIndex 写回匹配末尾(核内单点写回的可观测口径);
// 失败路径置 0(lastIndex 规范不可配置,无法以访问器计数观察)。
let mut vm = Vm::new();
let result = eval(&mut vm, source).unwrap();
assert_eq!(to_str(&vm, result), "1");
let result = eval(
&mut vm,
"(() => { var re = /a/g; var m = re.exec('a'); return m[0] + ':' + re.lastIndex; })()",
)
.unwrap();
assert_eq!(to_str(&vm, result), "a:1");
let result = eval(
&mut vm,
"(() => { var re = /a/g; var r = re.exec('b'); return r === null && re.lastIndex === 0; })()",
)
.unwrap();
assert!(result.as_bool());
}
// --- test() lastIndex 规范语义(sticky 锚定 / 越界短路 / 不追踪不写回)---
@@ -747,10 +757,10 @@ fn symbol_search_lastindex_init_and_restore() {
.unwrap();
assert_eq!(to_str(&vm, result), "0/3");
// 严格 SameValue 口径:-0 与 +0 不同值,初始化 Set 0 与恢复 Set -0
// 各触发一次写。
// 各触发一次写(lastIndex 规范不可配置,计数经代理 set trap 观察)。
let result = eval(
&mut vm,
"(() => { var r = /b/; var sets = 0; var store = -0; Object.defineProperty(r, 'lastIndex', { configurable: true, set(v) { sets += 1; store = v; }, get() { return store; } }); r.exec = function() { return null; }; r[Symbol.search](''); return sets; })()",
"(() => { var r = /b/; var sets = 0; var store = -0; var p = new Proxy(r, { set(t, k, v) { if (k === 'lastIndex') { sets += 1; store = v; } t[k] = v; return true; }, get(t, k) { if (k === 'lastIndex') { return store; } return t[k]; } }); p.exec = function() { return null; }; p[Symbol.search](''); return sets; })()",
)
.unwrap();
assert!(result.as_int() == 2);
@@ -2303,14 +2303,14 @@ fn typed_array_ctor_plain_call_all_forms_throw() {
#[test]
fn typed_array_ctor_gpf_getter_throws_propagates() {
// newTarget.prototype 访问器抛值原值传播(buffer 形 + length 形):
// 构造器体内 GpFC 自重读,不依赖通用构造机制的原型读。
// newTarget.prototype 读取抛值原值传播(buffer 形 + length 形):
// 构造器体内 GpFC 自重读,不依赖通用构造机制的原型读。普通函数 prototype
// 规范不可配置,抛错形态经代理 get trap 表达。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var nt = function () {}; \
Object.defineProperty(nt, 'prototype', { \
get: function () { throw new RangeError('gpf'); } }); \
"(function () { var nt = new Proxy(function () {}, { \
get: function (t, k) { if (k === 'prototype') { throw new RangeError('gpf'); } return t[k]; } }); \
var ab = new ArrayBuffer(4); var t1, t2; \
try { Reflect.construct(Int8Array, [ab], nt); } \
catch (e) { t1 = e instanceof RangeError && e.message === 'gpf'; } \
+8 -7
View File
@@ -159,14 +159,15 @@ fn throwing_proto_ctor(source_tail: &str) -> String {
#[test]
fn gpfc_new_expression_bytecode_ctor_propagates_getter_throw() {
// 字节码构造器 prototype 为访问器且 getter 抛错:new 表达式须原值上抛(getter 触发)。
// newTarget 为代理且 get trap 读 prototype 抛错:new 表达式须原值上抛
// (getter 触发;普通函数 prototype 规范不可配置,抛错形态经代理表达)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var F = function(){}; var calls = 0; \
Object.defineProperty(F, 'prototype', { configurable: true, \
get: function() { calls++; throw new Error('boom'); } }); \
try { new F(); } catch (e) { e.message + ':' + calls }",
var p = new Proxy(F, { get: function(t, k) { \
if (k === 'prototype') { calls++; throw new Error('boom'); } return t[k]; } }); \
try { new p(); } catch (e) { e.message + ':' + calls }",
)
.unwrap();
assert_eq!(vm.lookup_str(result).unwrap(), "boom:1");
@@ -179,9 +180,9 @@ fn gpfc_new_expression_spread_bytecode_ctor_propagates_getter_throw() {
let result = eval(
&mut vm,
"var F = function(){}; var calls = 0; \
Object.defineProperty(F, 'prototype', { configurable: true, \
get: function() { calls++; throw new Error('boom'); } }); \
try { new F(...[]); } catch (e) { e.message + ':' + calls }",
var p = new Proxy(F, { get: function(t, k) { \
if (k === 'prototype') { calls++; throw new Error('boom'); } return t[k]; } }); \
try { new p(...[]); } catch (e) { e.message + ':' + calls }",
)
.unwrap();
assert_eq!(vm.lookup_str(result).unwrap(), "boom:1");