fix: 顶层声明撞不可定义全局按规范抛 TypeError

This commit is contained in:
2026-09-14 14:32:39 +00:00
parent 91946f3fe3
commit d161b7cfe3
11 changed files with 332 additions and 57 deletions
+13
View File
@@ -572,6 +572,19 @@ define_opcodes! {
DELETE_GLOBAL_PROP_C = 0x9C => "DELETE_GLOBAL_PROP_C",
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::Slot(Slot::A)],
pure = false, jump = false, term = false, ic = false,
// 全局函数绑定声明检查(GDI step 9,脚本顶层函数声明):rd=目标对象(顶层
// This=全局对象)、b=键寄存器。CanDeclareGlobalFunction 三臂:缺失 →
// IsExtensible、c:true → 过、data{w:true,e:true} → 过,余抛 TypeError。
// 检查阶段发射于任何绑定创建之前(先于 GDI var 序言),失败即无部分绑定。
CAN_DECLARE_GLOBAL_FUNC = 0x9D => "CAN_DECLARE_GLOBAL_FUNC",
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::B)],
pure = false, jump = false, term = false, ic = false,
// 创建全局函数绑定(CreateGlobalFunctionBinding,脚本面 deletable=false):
// rd=目标对象、a=值、b=键,与 DEFINE_GLOBAL_PROP 同形。缺失或既有 c:true →
// 定义可写/可枚举/不可配置数据属性;既有 c:false 可写数据 → 仅更新值保描述符。
DEFINE_GLOBAL_FUNC_BIND = 0x9E => "DEFINE_GLOBAL_FUNC_BIND",
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
pure = false, jump = false, term = false, ic = false,
// ── 成员复合赋值:位/移位 (0x64-0x69) ──
COMPOUND_MEMBER_BIT_AND = 0x64 => "COMPOUND_MEMBER_BIT_AND",
+14
View File
@@ -163,6 +163,20 @@ impl Emitter {
names
}
/// 同 [`collect_top_level_function_names`] 的收集面,但按**声明序**返回 Vec
/// (供 GDI 检查阶段逆序遍历逐名去重,规范"逆序首见 = 源序最后声明")。
pub(crate) fn collect_top_level_function_names_ordered(&self, stmts: &[Statement]) -> Vec<String> {
let mut names = Vec::new();
for stmt in stmts {
if let Statement::FunctionDeclaration(fd) = stmt {
if let Some(id) = &fd.id {
names.push(id.name.to_string());
}
}
}
names
}
/// 收集当前函数作用域声明的绑定名(参数 + 变量/函数声明,含嵌套 block,不含嵌套函数体)。
pub(crate) fn collect_own_binding_names(&self, param_names: &[&str], stmts: &[Statement]) -> HashSet<String> {
let mut names = HashSet::new();
+47
View File
@@ -1646,6 +1646,21 @@ impl Emitter {
}
}
/// 顶层函数声明的 A 侧同步写:eval 脚本沿用 [`emit_global_prop_write`] 的
/// 0x98 分支(属性可配置,创建检查面不覆盖 eval 动态臂);普通脚本经顶层
/// This 走 0x9E(CreateGlobalFunctionBinding 三臂,声明检查由 GDI 序言的
/// 0x9D 段预先完成)。
pub(crate) fn emit_global_func_bind_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
if ctx.is_eval_script {
self.emit_global_prop_write(name, val_reg, ctx);
return;
}
let idx = ctx.add_constant(Constant::String(name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_func_bind(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg)));
}
/// 顶层 var 声明带初始化的 A 侧同步:PutValue 语义——既有不可写数据属性
/// strict 抛 TypeError / sloppy 静默 no-op;可写照原描述符仅更值。脚本与
/// eval 均经 session 解析全局对象,不依赖顶层 this。
@@ -1699,6 +1714,31 @@ impl Emitter {
}
}
/// GDI step 9 检查阶段:顶层函数声明名按声明逆序逐名去重,每名发一条 0x9D
/// (CanDeclareGlobalFunction 运行期判定,撞既有不可配置非可写数据属性或
/// 不可扩展上的缺失名抛 TypeError)。发射位必须位于 GDI var 序言之先——
/// 检查失败时任何绑定(含 var 序言新建属性)不得实例化。
///
/// # 边界与前提
/// - 仅普通脚本调用(`is_eval_script` 面由调用点门控);eval 动态臂与
/// 80 门禁(三常量编译期面)零触碰。
/// - 仅遍历语句列表直接子级具名函数声明(生成器/异步声明同节点类型,天然
/// 覆盖);块内函数声明与 `export default function` 不入全局检查面。
pub(crate) fn emit_gdi_func_decl_checks(&self, stmts: &[Statement], ctx: &mut CompileCtx) {
let names = self.collect_top_level_function_names_ordered(stmts);
let mut seen = HashSet::new();
for name in names.iter().rev() {
// 逆序首见即源序最后声明者(规范去重口径),重名只查一次。
if !seen.insert(name.as_str()) {
continue;
}
let idx = ctx.add_constant(Constant::String(name.clone()));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::can_declare_global_func(Operand::This, Operand::Reg(key_reg)));
}
}
/// 把完整程序编译为顶层模块体的 IRFunction。
///
/// 调用方为 `oxide_compiler::Compiler::compile`:本函数完成 emit 半程,
@@ -1739,6 +1779,13 @@ impl Emitter {
}
}
// GDI step 9 函数臂检查阶段(运行期,先于任何绑定实例化):普通脚本
// 顶层函数声明撞既有不可配置全局属性抛 TypeError,sloppy/strict 同形。
// eval 面零触碰:静态三常量面由上方 80 门禁编译期拦,动态臂另归口。
if !ctx.is_eval_script {
self.emit_gdi_func_decl_checks(&program.body, &mut ctx);
}
self.predeclare_function_declarations(&program.body, &mut ctx);
// 预注册 builtin 引用(先于任何临时寄存器),builtin 槽不进入临时寄存器池。
@@ -73,7 +73,7 @@ impl Emitter {
// 严格 eval 代码函数声明绑定 eval 自身 lexical 环境、不落全局对象:抑制 A
// 侧写(局部绑定读面未暴露),同时避免不可写全局内置在 0x98 上误抛 strict。
if ctx.is_global_scope && ctx.scopes.symbols.scopes.len() == 1 && !(ctx.is_eval_script && ctx.is_strict) {
self.emit_global_prop_write(&name, var_reg, ctx);
self.emit_global_func_bind_write(&name, var_reg, ctx);
}
Ok(None)
}
+8 -1
View File
@@ -740,11 +740,18 @@ mod tests {
assert_group(&[OpCode::NEW_OBJECT, OpCode::NEW_SESSION_OBJECT, OpCode::NEW_ARRAY], Some(1), &[], true);
// define 语义属性写入 / arguments / rest
assert_group(
&[OpCode::DEFINE_PROP, OpCode::DEFINE_GLOBAL_PROP, OpCode::DEFINE_GLOBAL_PROP_IF_ABSENT],
&[
OpCode::DEFINE_PROP,
OpCode::DEFINE_GLOBAL_PROP,
OpCode::DEFINE_GLOBAL_PROP_IF_ABSENT,
OpCode::DEFINE_GLOBAL_FUNC_BIND,
],
None,
&[1, 2, 3],
false,
);
// 全局函数绑定声明检查:a 槽无操作数,use 集只含 rd/b。
assert_contract(OpCode::CAN_DECLARE_GLOBAL_FUNC, None, &[1, 3], false);
assert_group(&[OpCode::DEFINE_GLOBAL_PROP_C, OpCode::DEFINE_GLOBAL_PROP_C_IF_ABSENT], None, &[2], false);
// delete 全局内置:结果写 rd、镜像槽寄存器是 a 槽 use
assert_contract(OpCode::DELETE_GLOBAL_PROP_C, Some(1), &[2], false);
+14
View File
@@ -255,6 +255,20 @@ impl Inst {
)
}
/// 顶层函数声明的全局绑定声明检查(GDI step 9):target 为全局对象,key 为
/// 声明名寄存器。运行期按 CanDeclareGlobalFunction 判定——撞既有不可配置
/// 非可写数据属性(或不可扩展上的缺失名)抛 TypeError。
pub fn can_declare_global_func(target: Operand, key: Operand) -> Self {
Self::new(OpCode::CAN_DECLARE_GLOBAL_FUNC, target, Operand::None, key)
}
/// 创建全局函数绑定(CreateGlobalFunctionBinding,脚本面 deletable=false):
/// target 为全局对象,value/key 为寄存器。缺失或既有可配置属性定义可写/可枚举/
/// 不可配置数据属性;既有不可配置可写数据属性仅更新值保描述符。
pub fn define_global_func_bind(target: Operand, value: Operand, key: Operand) -> Self {
Self::new(OpCode::DEFINE_GLOBAL_FUNC_BIND, target, value, key)
}
/// 静态删除属性:obj 同时放 rd/a 槽,const_idx 为属性名常量下标。
pub fn delete_prop_static(obj: Operand, const_idx: u32) -> Self {
Self::with_ext(OpCode::DELETE_PROP_STATIC, obj, obj, Operand::None, &[const_idx])
+8
View File
@@ -2195,6 +2195,14 @@ impl Vm {
self.dispatch_delete_global_prop_c(rd, a, key_idx)?;
}
OpCode::CAN_DECLARE_GLOBAL_FUNC => {
self.dispatch_can_declare_global_func(rd, b)?;
}
OpCode::DEFINE_GLOBAL_FUNC_BIND => {
self.dispatch_define_global_func_bind(rd, a, b)?;
}
OpCode::DEFINE_PROP_ATTRS => {
let attrs = self.bytecode[self.pc] as u8;
self.pc += 1;
+117
View File
@@ -437,6 +437,123 @@ impl Vm {
}
}
/// 顶层函数声明的全局绑定声明检查(GDI step 9,CanDeclareGlobalFunction):
/// rd=全局对象,b=声明名寄存器。
///
/// 三臂判定(与规范同形,sloppy/strict 无区分):
/// - 属性缺失:全局对象不可扩展 → TypeError。
/// - 既有可配置属性(数据或 accessor):通过。
/// - 既有不可配置属性:仅可写且可枚举的数据描述符通过,余(不可写数据 /
/// 不可枚举数据 / accessor)抛 TypeError。
///
/// # 边界与前提
/// - rd 槽固定为顶层 `this`:本形以所有脚本(含严格模式)顶层 this 即全局
/// 对象为成立前提。
/// - rd 非对象(非对象 this)时按 no-op 处理,不抛错(同 0x99 族)。
/// - 检查阶段发射于任何绑定实例化之前,抛错即整脚本无部分绑定。
pub(crate) fn dispatch_can_declare_global_func(&mut self, rd: usize, b: usize) -> Result<(), String> {
vm_trace!("CAN_DECLARE_GLOBAL_FUNC rd={} key={}", rd, b);
let obj_val = self.regs[rd];
if !obj_val.is_object() {
return Ok(());
}
let key_val = self.regs[b];
if !key_val.is_string() {
return Err(format!("CAN_DECLARE_GLOBAL_FUNC key register {b} is not a string"));
}
let si = self.property_key_si(key_val)?;
// SAFETY: key_val 已校验为字符串值;错误消息走 lossy 文本(不可解析名
// 含孤立 surrogate 时映射为 FFFD,仅为展示用途)。
let name = unsafe { (*key_val.as_string_ptr()).as_lossy_str() };
// SAFETY: rd 持当前帧存活对象值(顶层 this 为全局对象),指针在本指令
// 派发期间有效。
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), si) {
if let Some(current) = obj.prop_meta_at(pos) {
if !current.attributes.configurable()
&& (current.is_accessor || !current.attributes.writable() || !current.attributes.enumerable())
{
return self.raise_error_kind(
"TypeError",
&format!("Cannot declare function '{name}': global property is not configurable"),
);
}
}
// 无 meta 的 shape 槽按可配置缺省通过。
return Ok(());
}
// 属性缺失:唯一失败面是全局对象不可扩展(两模式均抛 TypeError)。
if !obj.is_extensible() {
return self.raise_error_kind(
"TypeError",
&format!("Cannot declare function '{name}': global object is not extensible"),
);
}
Ok(())
}
/// 创建全局函数绑定(CreateGlobalFunctionBinding,脚本面 deletable=false):
/// rd=全局对象,a=函数值,b=声明名寄存器。
///
/// 三臂(与规范同形):
/// - 属性缺失:全局对象不可扩展 → TypeError(两模式均抛);否则新建可写/
/// 可枚举/不可配置数据属性。
/// - 既有可配置属性(数据或 accessor):全重配为 {value, 可写, 可枚举,
/// 不可配置}(c:true 定义必成)。
/// - 既有不可配置属性:可写数据属性仅更新值保描述符;不可写数据或 accessor
/// → TypeError(对称防御——正常面已由 0x9D 检查阶段预先排除)。
///
/// # 边界与前提
/// - rd 槽固定为顶层 `this`(同 0x99 族前提);rd 非对象时 no-op。
pub(crate) fn dispatch_define_global_func_bind(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("DEFINE_GLOBAL_FUNC_BIND rd={} value={} key={}", rd, a, b);
let obj_val = self.regs[rd];
if !obj_val.is_object() {
return Ok(());
}
let key_val = self.regs[b];
if !key_val.is_string() {
return Err(format!("DEFINE_GLOBAL_FUNC_BIND key register {b} is not a string"));
}
let si = self.property_key_si(key_val)?;
let value = self.regs[a];
// SAFETY: key_val 已校验为字符串值;错误消息走 lossy 文本(不可解析名
// 含孤立 surrogate 时映射为 FFFD,仅为展示用途)。
let name = unsafe { (*key_val.as_string_ptr()).as_lossy_str() };
// SAFETY: rd 持当前帧存活对象值(顶层 this 为全局对象),指针在本指令
// 派发期间有效。
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), si) {
if let Some(current) = obj.prop_meta_at(pos) {
if current.attributes.configurable() {
// 既有可配置属性:全重配 {可写, 可枚举, 不可配置}。
return match self.define_data_property(obj, si, value, PropAttributes::new(true, true, false)) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
};
}
if !current.is_accessor && current.attributes.writable() {
// 不可配置可写数据:保描述符仅更值(0x98 同形)。
return match self.define_data_property(obj, si, value, current.attributes) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
};
}
// 不可写数据 / accessor:检查阶段后不可达,对称防御抛 TypeError。
return self.raise_error_kind(
"TypeError",
&format!("Cannot declare function '{name}': global property is not writable"),
);
}
// 无 meta 的 shape 槽按可配置缺省走全重配。
}
// 属性缺失:新建可写/可枚举/不可配置;唯一失败面是全局对象不可扩展。
match self.define_data_property(obj, si, value, PropAttributes::new(true, true, false)) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
}
}
/// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。
/// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。
/// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally
@@ -188,13 +188,13 @@ fn control_shapes_unchanged() {
#[test]
fn function_declaration_builtin_name() {
// 边界:function Math(){}——顶层函数声明取函数值(规范 var 绑定语义),
// 描述符保 e:false/c:true、值可调用。
// 边界:function Math(){}——顶层函数声明取函数值(CreateGlobalFunctionBinding
// 重配臂:既有 c:true 属性重配为可写/可枚举/不可配置),值可调用。
run_truthy(
"function Math(){ return 1; } \
var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
Math() === 1 && d.value === Math && d.writable === true \
&& d.enumerable === false && d.configurable === true",
&& d.enumerable === true && d.configurable === false",
);
}
@@ -1,10 +1,12 @@
//! 顶层函数声明撞不可写全局内置(undefined/NaN/Infinity):脚本面声明不更新绑定
//! (A 侧保留原常量,读/调/typeof 面收敛全局对象,用户名与模块面不受影响);
//! eval 代码面——sloppy 函数声明在建立全局绑定前抛 TypeError(程序首指令 abrupt,
//! 无部分创建,A 侧保留),strict 函数声明为局部绑定不抛、不落全局。
//! 顶层函数声明撞不可配置全局属性(三常量 c:false):脚本面 GDI 检查阶段在任何
//! 绑定实例化前抛 TypeError(sloppy/strict 同形),A 侧保留原常量、无部分 var
//! 绑定,用户名与模块面不受影响;eval 代码面——sloppy 函数声明在建立全局绑定前抛
//! TypeError(程序首指令 abrupt,无部分创建,A 侧保留),strict 函数声明为局部
//! 绑定不抛、不落全局。
use std::sync::Arc;
use oxide_bytecode::module::CompiledModule;
use oxide_compiler::compiler::Compiler;
use oxide_emit::module::{ModuleSourceLoader, ResolvedModule};
use oxide_types::value::JsValue;
@@ -45,6 +47,33 @@ fn script_str(vm: &mut Vm, source: &str) -> Option<String> {
vm.lookup_str(r)
}
/// 单脚本未捕获异常文本(脚本正常完成时 panic)。
fn script_err_text(source: &str) -> String {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let mut vm = Vm::new();
match vm.run(&Arc::new(module)) {
Err(e) => e,
Ok(v) => panic!("expected uncaught error, got: {v:?}\nsource: {source}"),
}
}
/// 声明脚本跑完(必须抛)后续跑探针脚本,返回探针的字符串化完成值——抛后
/// A 侧保留面的判别探针。
fn err_then_script_str(decl: &str, probe: &str) -> String {
let compile = |src: &str| -> Arc<CompiledModule> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, src).expect("parse");
Arc::new(Compiler::new().compile(&program).expect("compile"))
};
let mut vm = Vm::new();
let err = vm.run(&compile(decl));
assert!(err.is_err(), "声明脚本应抛未捕获异常,实际: {err:?}\ndecl: {decl}");
let result = vm.run(&compile(probe)).expect("probe run");
vm.lookup_str(result).unwrap_or_else(|| format!("{result}"))
}
/// 无依赖模块加载器:本组测试模块源无 import,resolve 不被调用。
struct NoopLoader;
@@ -56,57 +85,61 @@ impl ModuleSourceLoader for NoopLoader {
}
}
// ── 脚本面:三常量函数声明静默 no-op,绑定不更新 ──
// ── 脚本面:三常量函数声明 GDI 检查阶段抛 TypeError,A 侧保留 ──
#[test]
fn script_fn_decl_three_constants_sloppy_typeof_keeps_original_type() {
assert_eq!(eval_str("function undefined(){} typeof undefined"), "undefined");
assert_eq!(eval_str("function NaN(){} typeof NaN"), "number");
assert_eq!(eval_str("function Infinity(){} typeof Infinity"), "number");
fn script_fn_decl_three_constants_sloppy_throws_type_error() {
// GDI step 9 检查阶段先于任何代码运行:撞三常量(c:false)抛声明期 TypeError,
// sloppy 面。
let err = script_err_text("function undefined(){} typeof undefined");
assert!(err.contains("TypeError"), "sloppy undefined 声明应抛 TypeError,实际: {err}");
let err = script_err_text("function NaN(){} typeof NaN");
assert!(err.contains("TypeError"), "sloppy NaN 声明应抛 TypeError,实际: {err}");
let err = script_err_text("function Infinity(){} typeof Infinity");
assert!(err.contains("TypeError"), "sloppy Infinity 声明应抛 TypeError,实际: {err}");
// A 侧保留:抛后探针脚本读三常量仍见原类型。
assert_eq!(
err_then_script_str(
"function undefined(){} typeof undefined",
"typeof undefined + ':' + typeof NaN + ':' + typeof Infinity",
),
"undefined:number:number",
);
}
#[test]
fn script_fn_decl_three_constants_strict_no_throw_typeof_keeps_original_type() {
assert_eq!(eval_str("'use strict'; function undefined(){} typeof undefined"), "undefined");
assert_eq!(eval_str("'use strict'; function NaN(){} typeof NaN"), "number");
assert_eq!(eval_str("'use strict'; function Infinity(){} typeof Infinity"), "number");
fn script_fn_decl_three_constants_strict_throws_type_error() {
// GDI 无严格性区分:strict 面同形抛 TypeError。
let err = script_err_text("'use strict'; function undefined(){} typeof undefined");
assert!(err.contains("TypeError"), "strict undefined 声明应抛 TypeError,实际: {err}");
let err = script_err_text("'use strict'; function NaN(){} typeof NaN");
assert!(err.contains("TypeError"), "strict NaN 声明应抛 TypeError,实际: {err}");
let err = script_err_text("'use strict'; function Infinity(){} typeof Infinity");
assert!(err.contains("TypeError"), "strict Infinity 声明应抛 TypeError,实际: {err}");
}
#[test]
fn script_fn_decl_nan_infinity_self_equality_preserved() {
let r = eval("'use strict'; function NaN(){}; NaN !== NaN").unwrap();
assert!(r.is_bool() && r.as_bool(), "声明后 NaN 自反不等性应保留,实际 {r:?}");
let r = eval("'use strict'; function Infinity(){}; Infinity === Infinity").unwrap();
assert!(r.is_bool() && r.as_bool(), "声明后 Infinity 自相等性应保留,实际 {r:?}");
// 抛点在前置检查阶段,函数体与尾表达式均不可达;A 侧值不动,探针脚本的
// 自反不等/自相等性仍成立。
let err = script_err_text("'use strict'; function NaN(){}; NaN !== NaN");
assert!(err.contains("TypeError"), "NaN 声明应抛 TypeError,实际: {err}");
assert_eq!(err_then_script_str("function NaN(){}", "NaN !== NaN && Infinity === Infinity",), "true",);
}
#[test]
fn script_fn_decl_global_descriptor_value_and_writability_preserved() {
// undefined:值保留原常量(=== undefined),writable 保持 false。
let r = eval(
"function undefined(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'undefined'); d.value === undefined && d.writable === false",
)
.unwrap();
assert!(r.is_bool() && r.as_bool(), "undefined 描述符应保留原常量值与不可写,实际 {r:?}");
// NaN:值保留原常量(自反不等),writable 保持 false。
let r = eval(
"function NaN(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'NaN'); d.value !== d.value && d.writable === false",
)
.unwrap();
assert!(r.is_bool() && r.as_bool(), "NaN 描述符应保留原常量值与不可写,实际 {r:?}");
// Infinity:值保留原常量,writable 保持 false。
let r = eval(
"function Infinity(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'Infinity'); d.value === Infinity && d.writable === false",
)
.unwrap();
assert!(r.is_bool() && r.as_bool(), "Infinity 描述符应保留原常量值与不可写,实际 {r:?}");
// 声明不改变描述符:三常量属性不可枚举,Object.keys 不新增名字。
let r = eval(
"function undefined(){} function NaN(){} function Infinity(){} \
Object.keys(globalThis).filter(function (k) { return k === 'undefined' || k === 'NaN' || k === 'Infinity'; }).length === 0",
)
.unwrap();
assert!(r.is_bool() && r.as_bool(), "声明不应新增可枚举全局名,实际 {r:?}");
// 抛后 A 侧描述符不变:值原常量、不可写/不可枚举/不可配置;同脚本 var 绑定
// 无部分创建(检查阶段先于 GDI 序言);Object.keys 不新增三常量名。
let s = err_then_script_str(
"var x; function undefined(){}",
"var d = Object.getOwnPropertyDescriptor(globalThis, 'undefined'); \
(d.value === undefined && d.writable === false && d.enumerable === false \
&& d.configurable === false) \
&& Object.getOwnPropertyDescriptor(globalThis, 'x') === undefined \
&& Object.keys(globalThis).filter(function (k) { return k === 'undefined' || k === 'NaN' || k === 'Infinity'; }).length === 0",
);
assert_eq!(s, "true", "A 侧描述符应保留且无部分 var 绑定,实际: {s}");
}
#[test]
@@ -64,6 +64,18 @@ fn phase2_err_text(phase1: &str, phase2: &str) -> String {
}
}
/// phase2 必须抛未捕获异常的变体;返回同 VM 续跑探针脚本的字符串化完成值
/// (抛后 A 侧保留面的判别探针)。非字符串完成值走 Display(布尔形探针返回
/// "true"/"false")。
fn phase2_err_then_probe(phase1: &str, phase2: &str, probe: &str) -> String {
let mut vm = Vm::new();
vm.run(&Arc::new(compile(phase1))).expect("run1");
vm.reset();
assert!(vm.run(&Arc::new(compile(phase2))).is_err(), "phase2 应抛未捕获异常: {phase2}");
let result = vm.run(&Arc::new(compile(probe))).expect("probe run");
vm.lookup_str(result).unwrap_or_else(|| format!("{result}"))
}
#[test]
fn top_level_var_lands_on_global_this() {
eval_truthy("var x = 5; globalThis.x === 5");
@@ -425,13 +437,23 @@ fn top_level_function_shadow_and_var_no_init() {
eval_truthy("function f(){return 1;} var f = 5; f === 5 && globalThis.f === 5");
}
/// 声明处描述符感知写:既有不可写不可配置属性声明不更值(定义不变量静默
/// no-op),裸读与反射一致见既有值。
/// 函数臂撞既有不可配置不可写数据属性:GDI 检查阶段抛 TypeError(两模式同形),
/// 无部分创建——抛后值与描述符均保留(corpus 无部分绑定形)。
#[test]
fn top_level_function_decl_respects_nonwritable_property() {
assert_two_phases_truthy(
let err = phase2_err_text(
"Object.defineProperty(globalThis, 'f', {value: 1, writable: false, configurable: false})",
"function f(){} f === 1 && globalThis.f === 1 && typeof f === 'number'",
"function f(){}",
);
assert!(err.contains("TypeError"), "声明应抛 TypeError,实际: {err}");
assert_eq!(
phase2_err_then_probe(
"Object.defineProperty(globalThis, 'f', {value: 1, writable: false, configurable: false})",
"function f(){}",
"f === 1 && globalThis.f === 1 && typeof f === 'number'",
),
"true",
"抛后 A 侧应保留原值与描述符",
);
}
@@ -445,18 +467,18 @@ fn top_level_function_survives_reset() {
));
}
/// builtin 名形零污染:可写 builtin 名(Math)声明更值保描述符;不可写
/// builtin 名(undefined)声明不更新,裸读与反射一致见既有值。
/// builtin 名形:c:true builtin 名(Math)声明经 CreateGlobalFunctionBinding
/// 重配臂落标准函数描述符(可写/可枚举/不可配置,值可调用);不可配置
/// builtin 名(undefined,c:false)声明在 GDI 检查阶段抛 TypeError。
#[test]
fn top_level_function_decl_builtin_name_zero_pollution() {
eval_truthy("function Math(){ return 7; } Math() === 7 && globalThis.Math() === 7 && Math === globalThis.Math");
eval_truthy(
"function Math(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
d.writable === true && d.enumerable === false && d.configurable === true",
);
eval_truthy(
"function undefined(){ return 1; } typeof undefined === 'undefined' && globalThis.undefined === undefined",
d.writable === true && d.enumerable === true && d.configurable === false",
);
let err = phase2_err_text("0", "function undefined(){ return 1; }");
assert!(err.contains("TypeError"), "undefined 声明应抛 TypeError,实际: {err}");
}
// ── 声明带初始化的 A 侧写是 PutValue:strict 撞既有不可写用户属性抛 TypeError ──