mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 复合赋值旧值读改抛错读并补严格隐式全局写检查
This commit is contained in:
Generated
+21
-21
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1258,7 +1258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1300,7 +1300,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_fold"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_bytecode",
|
||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1335,7 +1335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1356,11 +1356,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_net"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1375,7 +1375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1391,7 +1391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1404,7 +1404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1424,7 +1424,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1437,7 +1437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -765,6 +765,18 @@ define_opcodes! {
|
||||
VOID = 0xF3 => "VOID",
|
||||
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
|
||||
pure = true, jump = false, term = false, ic = false,
|
||||
// 严格模式隐式全局写:ext 字 = 键常量池下标(u16)、a 槽 = 值寄存器;
|
||||
// 先 HasProperty 检查(全局环境记录 SetMutableBinding 语义),属性缺失抛
|
||||
// ReferenceError;存在时按 DEFINE_GLOBAL_PROP_C 同形定义。
|
||||
DEFINE_GLOBAL_PROP_C_STRICT = 0xF5 => "DEFINE_GLOBAL_PROP_C_STRICT",
|
||||
def = None, uses = [SlotSpec::Slot(Slot::A)],
|
||||
pure = false, jump = false, term = false, ic = false,
|
||||
// 严格 PutValue 属性写(with 对象引用写回):rd = 基对象、a = 键、b = 值;
|
||||
// 写时点 HasProperty 检查(含原型链),属性缺失抛 ReferenceError(读侧
|
||||
// getter 副作用可能已删除属性);存在时按 SET_PROP_DYNAMIC 同形 Set。
|
||||
SET_PROP_DYNAMIC_STRICT = 0xF4 => "SET_PROP_DYNAMIC_STRICT",
|
||||
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
|
||||
pure = false, jump = false, term = false, ic = false,
|
||||
}
|
||||
|
||||
/// 4 字节定长指令。
|
||||
@@ -833,6 +845,7 @@ pub fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize {
|
||||
| OpCode::DEFINE_ACCESSOR_DYNAMIC
|
||||
| OpCode::DEFINE_PROP_ATTRS
|
||||
| OpCode::DEFINE_GLOBAL_PROP_C
|
||||
| OpCode::DEFINE_GLOBAL_PROP_C_STRICT
|
||||
| OpCode::DEFINE_GLOBAL_PROP_C_IF_ABSENT
|
||||
| OpCode::DELETE_GLOBAL_PROP_C
|
||||
| OpCode::DELETE_PROP_STATIC
|
||||
|
||||
@@ -128,15 +128,13 @@ impl Emitter {
|
||||
let is_tier = self.is_global_tier_name(ctx, name);
|
||||
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
||||
let is_builtin = ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name);
|
||||
if is_implicit && ctx.is_strict {
|
||||
// 严格模式未声明复合写:未解析引用不可 put,值无关抛 ReferenceError。
|
||||
return self.emit_strict_undeclared_write(name, ctx);
|
||||
}
|
||||
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)与未声明名同,
|
||||
// 属性缺失按 undefined(GetBaseValue 语义,不抛);已知 builtin 名(非局部
|
||||
// 遮蔽)读 A 侧属性,属性缺失(delete 真删后)抛 ReferenceError,与裸读
|
||||
// 同形——镜像槽不反映删除,无法自区分缺位与在位 undefined。
|
||||
if is_tier || is_implicit {
|
||||
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)属性缺失按
|
||||
// undefined(GetBaseValue 语义,不抛);已知 builtin 名(非局部遮蔽)
|
||||
// 读 A 侧属性,属性缺失(delete 真删后)抛 ReferenceError,与裸读同形
|
||||
// ——镜像槽不反映删除,无法自区分缺位与在位 undefined。
|
||||
// 隐式全局(未声明名)旧值读是抛错读:复合赋值的赋值引用解析在
|
||||
// 读旧值时抛 ReferenceError(未声明引用不可 GetValue,两模式同)。
|
||||
if is_tier {
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_GLOBAL_TYPEOF,
|
||||
@@ -144,7 +142,9 @@ impl Emitter {
|
||||
Operand::Const(key_idx),
|
||||
Operand::None,
|
||||
));
|
||||
} else if is_builtin {
|
||||
} else if is_implicit || is_builtin {
|
||||
// 隐式全局(未声明名)与已知 builtin 名(非局部遮蔽)同形抛错读:
|
||||
// 属性缺失(delete 真删后)抛 ReferenceError,与裸读同形。
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_GLOBAL,
|
||||
@@ -159,7 +159,14 @@ impl Emitter {
|
||||
// 引擎侧不保留镜像副本)。
|
||||
self.emit_tier_global_write(name, var_reg, ctx);
|
||||
} else if is_implicit {
|
||||
self.emit_implicit_global_write(name, var_reg, ctx);
|
||||
if ctx.is_strict {
|
||||
// 严格隐式全局写:运行期 HasProperty 检查,属性缺失抛
|
||||
// ReferenceError(旧值读侧的 getter 副作用可能已删除属性)。
|
||||
let idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::define_global_prop_c_strict(Operand::Reg(var_reg), idx));
|
||||
} else {
|
||||
self.emit_implicit_global_write(name, var_reg, ctx);
|
||||
}
|
||||
} else if ctx.targets_writable_builtin(name, var_reg) {
|
||||
// 可写内置名:RMW 新值同步落全局对象属性。
|
||||
self.emit_global_put_write(name, var_reg, ctx);
|
||||
@@ -514,12 +521,13 @@ impl Emitter {
|
||||
));
|
||||
} else {
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
// 顶层已声明 var(tier)与未声明名同:旧值读前从全局对象
|
||||
// 属性取当前值,缺失按 undefined(不抛);已知 builtin 名
|
||||
// (非局部遮蔽)读 A 侧属性,属性缺失(delete 真删后)抛
|
||||
// ReferenceError,与裸读同形——镜像槽不反映删除,无法自
|
||||
// 区分缺位与在位 undefined。
|
||||
if self.is_global_tier_name(ctx, name) || ctx.is_implicit_global_reg(var_reg) {
|
||||
// 顶层已声明 var(tier):旧值读前从全局对象属性取当前值,
|
||||
// 缺失按 undefined(不抛);已知 builtin 名(非局部遮蔽)读
|
||||
// A 侧属性,属性缺失(delete 真删后)抛 ReferenceError,与
|
||||
// 裸读同形——镜像槽不反映删除,无法自区分缺位与在位 undefined。
|
||||
// 隐式全局(未声明名)旧值读是抛错读:逻辑赋值的赋值引用
|
||||
// 解析在读旧值时抛 ReferenceError(未声明引用不可 GetValue)。
|
||||
if self.is_global_tier_name(ctx, name) {
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_GLOBAL_TYPEOF,
|
||||
@@ -527,7 +535,11 @@ impl Emitter {
|
||||
Operand::Const(key_idx),
|
||||
Operand::None,
|
||||
));
|
||||
} else if ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name) {
|
||||
} else if ctx.is_implicit_global_reg(var_reg)
|
||||
|| (ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name))
|
||||
{
|
||||
// 隐式全局(未声明名)与已知 builtin 名(非局部遮蔽)同形抛错读:
|
||||
// 属性缺失(delete 真删后)抛 ReferenceError,与裸读同形。
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_GLOBAL,
|
||||
@@ -546,6 +558,15 @@ impl Emitter {
|
||||
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
|
||||
ctx.labels.set_label_pos(store_label, ctx.insts.len());
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
// NamedEvaluation:RHS 是匿名函数/箭头/class 时按 LHS 标识符推断
|
||||
// name(仅短路通过、实际写回时才到达此处,与规范求值点一致)。
|
||||
if crate::is_anonymous_function_definition(&assign.right) {
|
||||
if let Some(sub_mod) = ctx.nested.last_mut() {
|
||||
if sub_mod.function_name.is_none() {
|
||||
sub_mod.function_name = Some(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 短路未通过才写:const 目标在此抛 TypeError(编译期拦截,值无关)。
|
||||
self.emit_const_write_guard(name, ctx)?;
|
||||
// 函数名不可写绑定(sloppy):put 永不成功,跳过写与命名空间写穿,
|
||||
@@ -592,8 +613,10 @@ impl Emitter {
|
||||
let is_tier = self.is_global_tier_name(ctx, name);
|
||||
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
||||
if is_implicit && ctx.is_strict {
|
||||
// 严格模式未声明写:抛 ReferenceError,后续 LOAD_VAR 不可达。
|
||||
let _ = self.emit_strict_undeclared_write(name, ctx)?;
|
||||
// 严格隐式全局写:运行期 HasProperty 检查,属性缺失抛
|
||||
// ReferenceError(旧值读侧的 getter 副作用可能已删除属性)。
|
||||
let idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::define_global_prop_c_strict(Operand::Reg(val_reg), idx));
|
||||
} else if is_tier {
|
||||
// 顶层已声明 var 逻辑赋值:新值写入全局对象属性(顶层 var 的唯一存储,
|
||||
// 引擎侧不保留镜像副本)。
|
||||
@@ -691,12 +714,7 @@ impl Emitter {
|
||||
Operand::Reg(val_reg),
|
||||
));
|
||||
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::SET_PROP_DYNAMIC,
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
Operand::Reg(val_reg),
|
||||
));
|
||||
self.emit_put_prop_dynamic(obj_reg, key_reg, val_reg, ctx);
|
||||
ctx.inst(Inst::jmp(end_label));
|
||||
|
||||
// 对象无该属性:回退静态复合赋值,结果写入同一 val_reg。
|
||||
@@ -846,4 +864,22 @@ impl Emitter {
|
||||
));
|
||||
Ok(val_reg)
|
||||
}
|
||||
|
||||
/// 严格 PutValue 属性写(with 对象引用写回):strict 模式发
|
||||
/// SET_PROP_DYNAMIC_STRICT(写时点 HasProperty 检查,属性缺失抛
|
||||
/// ReferenceError——读侧 getter 副作用可能已删除属性);sloppy 模式发
|
||||
/// 普通 SET_PROP_DYNAMIC(缺失物化属性)。
|
||||
pub(crate) fn emit_put_prop_dynamic(&self, obj_reg: u32, key_reg: u32, val_reg: u32, ctx: &mut CompileCtx) {
|
||||
let op = if ctx.is_strict {
|
||||
OpCode::SET_PROP_DYNAMIC_STRICT
|
||||
} else {
|
||||
OpCode::SET_PROP_DYNAMIC
|
||||
};
|
||||
ctx.inst(Inst::new(
|
||||
op,
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
Operand::Reg(val_reg),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,12 +329,9 @@ impl Emitter {
|
||||
let end_label = ctx.next_label_id();
|
||||
ctx.inst(Inst::jmp_if_false(has_reg, fallback_label));
|
||||
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::SET_PROP_DYNAMIC,
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
Operand::Reg(val_reg),
|
||||
));
|
||||
// 写时点 HasProperty 检查(strict 缺失抛 ReferenceError,读侧 getter
|
||||
// 副作用可能已删除属性),与复合赋值同形。
|
||||
self.emit_put_prop_dynamic(obj_reg, key_reg, val_reg, ctx);
|
||||
ctx.inst(Inst::jmp(end_label));
|
||||
|
||||
ctx.labels.set_label_pos(fallback_label, ctx.insts.len());
|
||||
|
||||
@@ -242,6 +242,19 @@ impl Inst {
|
||||
Self::with_ext(OpCode::DEFINE_GLOBAL_PROP_C, Operand::None, value, Operand::None, &[key_idx as u32])
|
||||
}
|
||||
|
||||
/// 严格模式全局写:ext = [key_idx](键常量池下标),value 放 a 槽。运行期先做
|
||||
/// HasProperty 检查——属性缺失抛 ReferenceError(严格模式未声明标识符写),
|
||||
/// 存在则定义可写/可枚举/可配置数据属性。
|
||||
pub fn define_global_prop_c_strict(value: Operand, key_idx: u16) -> Self {
|
||||
Self::with_ext(
|
||||
OpCode::DEFINE_GLOBAL_PROP_C_STRICT,
|
||||
Operand::None,
|
||||
value,
|
||||
Operand::None,
|
||||
&[key_idx as u32],
|
||||
)
|
||||
}
|
||||
|
||||
/// 顶层 var 声明的全局属性 define-if-absent:target 为全局对象,value/key 为寄存器。
|
||||
/// 既有属性零动作;缺失新建可写/可枚举/不可配置(脚本 GDI 序言,CreateGlobalVarBinding
|
||||
/// 对既有数据描述符零修改)。
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::vm::Vm;
|
||||
use crate::{vm_error, vm_trace};
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_runtime_api as coercion;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::arguments_map::ArgumentsMapState;
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::{encode_symbol_key, make_int_key};
|
||||
use oxide_types::value::{JsType, JsValue};
|
||||
@@ -22,6 +24,28 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// 全局对象属性读(含访问器 getter 调用):命中属性时若为访问器则以
|
||||
/// 全局对象为 receiver 调用 getter(getter 副作用先于后续写生效),返回
|
||||
/// getter 结果;命中数据属性返回槽值;未命中返回 None。getter 抛错经
|
||||
/// `raise_call_error` 就地展开(pc 已改写),调用方须按 pc 变化停行。
|
||||
fn global_prop_read_with_getter(&mut self, global: &P<JsObject>, si: u32) -> Result<Option<JsValue>, String> {
|
||||
let Some(val) = self.resolve_property(global, si) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(getter) = self.resolve_property_getter(global, si) else {
|
||||
return Ok(Some(val));
|
||||
};
|
||||
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
|
||||
let global_val = JsValue::from_js_object(global.as_ptr() as *mut JsObject);
|
||||
match self.call_function_sync(getter, global_val, &[]) {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(err) => {
|
||||
self.raise_call_error(&err)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 未声明标识符读:查 global object 属性存在性,命中取属性值,未命中抛 ReferenceError。
|
||||
///
|
||||
/// # 步骤
|
||||
@@ -44,7 +68,14 @@ impl Vm {
|
||||
let session = self.realm.session.borrow();
|
||||
session.global_object().clone()
|
||||
};
|
||||
if let Some(val) = self.resolve_property(&global, si) {
|
||||
let pc_before = self.pc;
|
||||
let hit = self.global_prop_read_with_getter(&global, si)?;
|
||||
if self.pc != pc_before {
|
||||
// getter 在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 不得续行(结果寄存器不得再写、不得抛未解析引用)。
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(val) = hit {
|
||||
self.regs[rd] = val;
|
||||
Ok(false)
|
||||
} else {
|
||||
@@ -71,9 +102,18 @@ impl Vm {
|
||||
return Err(format!("LOAD_GLOBAL_TYPEOF constant index {idx} is not a string key"));
|
||||
}
|
||||
let si = self.property_key_si(key_val)?;
|
||||
let session = self.realm.session.borrow();
|
||||
let global = session.global_object();
|
||||
self.regs[rd] = self.resolve_property(global, si).unwrap_or(JsValue::undefined());
|
||||
let global = {
|
||||
let session = self.realm.session.borrow();
|
||||
session.global_object().clone()
|
||||
};
|
||||
// typeof 访问器属性同样触发 getter(getter 抛错时 typeof 不豁免,异常照抛)。
|
||||
let pc_before = self.pc;
|
||||
let hit = self.global_prop_read_with_getter(&global, si)?;
|
||||
if self.pc != pc_before {
|
||||
// getter 在途抛错已展开:pc 由处理器接管,不得续行。
|
||||
return Ok(());
|
||||
}
|
||||
self.regs[rd] = hit.unwrap_or(JsValue::undefined());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -105,12 +145,19 @@ impl Vm {
|
||||
self.regs[rd] = val;
|
||||
return Ok(false);
|
||||
}
|
||||
// 回退全局对象属性读(未声明名与 known builtin 名同形 LOAD_GLOBAL)。
|
||||
let global_hit = {
|
||||
// 回退全局对象属性读(未声明名与 known builtin 名同形 LOAD_GLOBAL);
|
||||
// 访问器属性调用 getter(receiver = 全局对象),裸读必须触发 getter。
|
||||
let global = {
|
||||
let session = self.realm.session.borrow();
|
||||
let global = session.global_object().clone();
|
||||
self.resolve_property(&global, si)
|
||||
session.global_object().clone()
|
||||
};
|
||||
let pc_before = self.pc;
|
||||
let global_hit = self.global_prop_read_with_getter(&global, si)?;
|
||||
if self.pc != pc_before {
|
||||
// getter 在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 不得续行(结果寄存器不得再写、不得抛未解析引用)。
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(val) = global_hit {
|
||||
self.regs[rd] = val;
|
||||
Ok(false)
|
||||
@@ -196,6 +243,38 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ToPropertyKey:推导属性键值。Symbol 原样返回;对象经 ToPrimitive(string
|
||||
/// hint) 后 ToString;其余原始值直接 ToString。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 对象键的 toString/valueOf 调用有用户副作用,可能抛异常;抛错经
|
||||
/// `raise_call_error` 就地展开(pc 已改写),调用方须按 pc 变化停行。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 成功路径返回推导后的字符串/Symbol 值;抛错路径经 `unwind` 改写
|
||||
/// pc / 异常通道并返回 undefined(调用方不得再使用)。
|
||||
pub(crate) fn derive_property_key_value(&mut self, val: JsValue) -> Result<JsValue, String> {
|
||||
vm_trace!("derive_property_key_value: {:?}", val);
|
||||
if val.is_symbol() {
|
||||
return Ok(val);
|
||||
}
|
||||
let pc_before = self.pc;
|
||||
let prim = if val.is_object() {
|
||||
coercion::to_primitive(val, coercion::ToPrimitiveHint::String, self)?
|
||||
} else {
|
||||
val
|
||||
};
|
||||
if self.pc != pc_before {
|
||||
// ToPrimitive 在途抛错已展开:pc 由处理器接管,结果值不得使用。
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
if prim.is_symbol() {
|
||||
return Ok(prim);
|
||||
}
|
||||
let units = oxide_runtime_api::to_units_full(prim, self)?;
|
||||
Ok(self.new_string_units_owned(units))
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_load_var(&mut self, rd: usize, a: usize) -> Result<bool, String> {
|
||||
vm_trace!("LOAD_VAR rd={} r{}={:?}", rd, a, self.regs[a]);
|
||||
// a==254 即读 this:derived 构造帧在 super() 成功前读 this 抛 ReferenceError。
|
||||
|
||||
@@ -24,6 +24,7 @@ impl Vm {
|
||||
OpCode::SET_PROP_BATCH => self.dispatch_set_prop_batch(rd, a, b),
|
||||
OpCode::GET_PROP_DYNAMIC => self.dispatch_get_prop_dynamic(rd, a, b),
|
||||
OpCode::SET_PROP_DYNAMIC => self.dispatch_set_prop_dynamic(rd, a, b),
|
||||
OpCode::SET_PROP_DYNAMIC_STRICT => self.dispatch_set_prop_dynamic_strict(rd, a, b),
|
||||
OpCode::SET_ELEM => self.dispatch_set_elem(rd, a, b),
|
||||
OpCode::GET_PRIVATE => self.dispatch_get_private(rd, a, b),
|
||||
OpCode::SET_PRIVATE => self.dispatch_set_private(rd, a, b),
|
||||
@@ -648,8 +649,24 @@ impl Vm {
|
||||
|
||||
fn dispatch_get_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("GET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b);
|
||||
let prop_name_si = self.property_key_si(self.regs[a])?;
|
||||
let receiver = self.regs[rd];
|
||||
// 基 ToObject 先于键 ToPropertyKey(规范 GetV 步序):null/undefined 基在
|
||||
// 对象键 toString 副作用之前抛 TypeError。
|
||||
if receiver.is_null() || receiver.is_undefined() {
|
||||
self.raise_type_error("GET_PROP_DYNAMIC on non-object")?;
|
||||
return Ok(());
|
||||
}
|
||||
let pc_before = self.pc;
|
||||
let key_val = self.derive_property_key_value(self.regs[a])?;
|
||||
if self.pc != pc_before {
|
||||
// 键推导在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 余下读路径不得续行(结果寄存器不得再写)。
|
||||
return Ok(());
|
||||
}
|
||||
// 推导后的键写回键寄存器:复合/逻辑赋值的写侧(SET_PROP_DYNAMIC)
|
||||
// 复用同一键,对象键的 ToPrimitive 副作用只触发一次。
|
||||
self.regs[a] = key_val;
|
||||
let prop_name_si = self.property_key_si(key_val)?;
|
||||
// 数组元素 fast path:整数键 + 真数组 + 无元素 meta(无 hole/accessor/描述符)
|
||||
// + 界内 → 直读元素区,免 primitive_property_get / checked_object_ptr /
|
||||
// ordinary_get 调用链。判定条件与 ordinary_get_inner 数组分支严格等价。
|
||||
@@ -695,7 +712,13 @@ impl Vm {
|
||||
let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else {
|
||||
return Ok(());
|
||||
};
|
||||
let pc_before = self.pc;
|
||||
let prop_name_si = self.property_key_si(self.regs[a])?;
|
||||
if self.pc != pc_before {
|
||||
// 键推导在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 余下写路径不得续行(不得写属性)。
|
||||
return Ok(());
|
||||
}
|
||||
// 数组元素 fast path:整数键 + 真数组 + 无 meta + 界内 → 直写元素区,
|
||||
// 免 __proto__ 查询 / ordinary_set_dispatch / get_own_property_slot 调用链。
|
||||
// 与 ordinary_set_inner 数组分支等价(界内写不触发 length 扩展、无 shadow
|
||||
@@ -731,6 +754,52 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 严格 PutValue 属性写(with 对象引用写回):rd = 基对象、a = 键、b = 值。
|
||||
/// 写时点 HasProperty 检查(含原型链)——属性缺失抛 ReferenceError(读侧
|
||||
/// getter 副作用可能已删除属性);存在时按 SET_PROP_DYNAMIC 同形 Set。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 基对象检查(非对象抛 TypeError)。
|
||||
/// 2. 键推导(ToPropertyKey,在途抛错按 pc 变化停行)。
|
||||
/// 3. HasProperty 检查(在途抛错按 pc 变化停行);缺失抛 ReferenceError。
|
||||
/// 4. 存在走 ordinary_set_dispatch(strict 模式)。
|
||||
fn dispatch_set_prop_dynamic_strict(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("SET_PROP_DYNAMIC_STRICT rd={} a={} b={}", rd, a, b);
|
||||
let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else {
|
||||
return Ok(());
|
||||
};
|
||||
let pc_before = self.pc;
|
||||
let prop_name_si = self.property_key_si(self.regs[a])?;
|
||||
if self.pc != pc_before {
|
||||
// 键推导在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 余下写路径不得续行(不得写属性)。
|
||||
return Ok(());
|
||||
}
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let pc_before = self.pc;
|
||||
let exists = self.has_property(obj, prop_name_si)?;
|
||||
if self.pc != pc_before {
|
||||
// HasProperty 在途抛错已展开:pc 由处理器接管,不得续行。
|
||||
return Ok(());
|
||||
}
|
||||
if !exists {
|
||||
// 错误消息携带属性名(与全局未声明写同形);键推导后为字符串/符号,
|
||||
// 非字符串按空名兜底(emit 侧保证字符串键)。
|
||||
let name = if self.regs[a].is_string() {
|
||||
// SAFETY: 键推导后为字符串值;错误消息走 lossy 文本。
|
||||
unsafe { (*self.regs[a].as_string_ptr()).as_lossy_str() }.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
self.raise_error_kind("ReferenceError", &format!("{name} is not defined"))?;
|
||||
return Ok(());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let value = self.regs[b];
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd], self.current_strict())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_set_elem(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("SET_ELEM rd={} a={} b={}", rd, a, b);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
|
||||
|
||||
@@ -571,6 +571,12 @@ impl Vm {
|
||||
self.dispatch_define_global_prop_c(a, key_idx)?;
|
||||
}
|
||||
|
||||
OpCode::DEFINE_GLOBAL_PROP_C_STRICT => {
|
||||
let key_idx = self.bytecode[self.pc] as u16;
|
||||
self.pc += 1;
|
||||
self.dispatch_define_global_prop_c_strict(a, key_idx)?;
|
||||
}
|
||||
|
||||
OpCode::DEFINE_GLOBAL_PROP_IF_ABSENT => {
|
||||
self.dispatch_define_global_prop_if_absent(rd, a, b)?;
|
||||
}
|
||||
@@ -642,6 +648,7 @@ impl Vm {
|
||||
| OpCode::SET_PROP_BATCH
|
||||
| OpCode::GET_PROP_DYNAMIC
|
||||
| OpCode::SET_PROP_DYNAMIC
|
||||
| OpCode::SET_PROP_DYNAMIC_STRICT
|
||||
| OpCode::SET_ELEM
|
||||
| OpCode::GET_PRIVATE
|
||||
| OpCode::SET_PRIVATE
|
||||
|
||||
@@ -161,6 +161,48 @@ impl Vm {
|
||||
///
|
||||
/// 数组元素区的 hole(删除标记)视同不存在;原型链查找止于
|
||||
/// `MAX_PROTO_CHAIN_DEPTH` 深度上限。
|
||||
/// 解析属性的访问器 getter(与 `resolve_property` 同形):沿 shape 链 +
|
||||
/// 原型链,命中属性为访问器且 get 已定义时返回 getter 函数;数据属性或
|
||||
/// 无 getter 返回 None。代理层保守返 None(与 `resolve_property` 同口径)。
|
||||
pub(crate) fn resolve_property_getter(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
|
||||
if obj.is_proxy_obj() {
|
||||
return None;
|
||||
}
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
let val = obj.get_prop_at(pos);
|
||||
if !val.is_undefined() || obj.prop_vec_len() > pos as usize {
|
||||
return obj
|
||||
.prop_meta_at(pos)
|
||||
.filter(|m| m.is_accessor && !m.get.is_undefined())
|
||||
.map(|m| m.get);
|
||||
}
|
||||
}
|
||||
let mut proto = obj.proto();
|
||||
let mut depth = 0usize;
|
||||
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
|
||||
depth += 1;
|
||||
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
|
||||
if proto_obj.is_proxy_obj() {
|
||||
return None;
|
||||
}
|
||||
if let Some(pos) = self
|
||||
.kernel_core
|
||||
.shape_forge()
|
||||
.lookup_position(proto_obj.shape_id(), prop_name_si)
|
||||
{
|
||||
let val = proto_obj.get_prop_at(pos);
|
||||
if !val.is_undefined() || proto_obj.prop_vec_len() > pos as usize {
|
||||
return proto_obj
|
||||
.prop_meta_at(pos)
|
||||
.filter(|m| m.is_accessor && !m.get.is_undefined())
|
||||
.map(|m| m.get);
|
||||
}
|
||||
}
|
||||
proto = proto_obj.proto();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
|
||||
vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
||||
// Proxy:本方法是不可变签名,无法同步调 get trap;代理无自身属性,
|
||||
|
||||
@@ -424,6 +424,53 @@ impl Vm {
|
||||
return Err(format!("DEFINE_GLOBAL_PROP_C constant index {idx} is not a string key"));
|
||||
}
|
||||
let si = self.property_key_si(key_val)?;
|
||||
self.define_global_prop_c_body(a, si, key_val)
|
||||
}
|
||||
|
||||
/// 严格模式隐式全局写:先 HasProperty 检查(全局环境记录 SetMutableBinding
|
||||
/// 语义)——属性缺失抛 ReferenceError(未解析引用不可 put);存在时按
|
||||
/// [`dispatch_define_global_prop_c`] 同形定义。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 键常量解析为属性键 si(与 DEFINE_GLOBAL_PROP_C 同形)。
|
||||
/// 2. HasProperty 检查(含原型链;代理/deferred 命名空间触发可抛错,
|
||||
/// 抛错经 pc 变化停行)。
|
||||
/// 3. 缺失抛 ReferenceError(`{name} is not defined`);存在走定义体。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 键常量必须是字符串(emit 侧保证);非字符串按错误返回。
|
||||
pub(crate) fn dispatch_define_global_prop_c_strict(&mut self, a: usize, key_idx: u16) -> Result<(), String> {
|
||||
let idx = key_idx as usize;
|
||||
vm_trace!("DEFINE_GLOBAL_PROP_C_STRICT value={} idx={}", a, idx);
|
||||
let key_val = self.immutables().get(idx).copied().unwrap_or(JsValue::undefined());
|
||||
if !key_val.is_string() {
|
||||
return Err(format!("DEFINE_GLOBAL_PROP_C_STRICT constant index {idx} is not a string key"));
|
||||
}
|
||||
let si = self.property_key_si(key_val)?;
|
||||
let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject;
|
||||
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
|
||||
let obj = unsafe { &*global_ptr };
|
||||
let pc_before = self.pc;
|
||||
let exists = self.has_property(obj, si)?;
|
||||
if self.pc != pc_before {
|
||||
// HasProperty 在途抛错已展开到 catch/finally 入口:pc 由处理器接管,
|
||||
// 定义不得续行。
|
||||
return Ok(());
|
||||
}
|
||||
if !exists {
|
||||
// SAFETY: key_val 已校验为字符串值;错误消息走 lossy 文本。
|
||||
let name = unsafe { (*key_val.as_string_ptr()).as_lossy_str() };
|
||||
self.raise_error_kind("ReferenceError", &format!("{name} is not defined"))?;
|
||||
return Ok(());
|
||||
}
|
||||
self.define_global_prop_c_body(a, si, key_val)
|
||||
}
|
||||
|
||||
/// 全局对象属性定义体(DEFINE_GLOBAL_PROP_C 与严格变体共用):既有属性
|
||||
/// 按描述符分流(accessor 走 Set 语义、数据属性按可写性),缺失新建可写/
|
||||
/// 可枚举/可配置属性。
|
||||
fn define_global_prop_c_body(&mut self, a: usize, si: u32, key_val: JsValue) -> Result<(), String> {
|
||||
let _ = key_val;
|
||||
let value = self.regs[a];
|
||||
let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject;
|
||||
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
|
||||
|
||||
@@ -86,17 +86,19 @@ fn sloppy_update_operator_undeclared_write_lands_on_global() {
|
||||
assert!(r.is_bool() && r.as_bool(), "z++ 未声明写应落全局 NaN(undefined+1),实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 复合赋值:未声明名旧值读在 lval 求值步抛 ReferenceError(未解析引用不可
|
||||
// GetValue,两模式同),不物化全局 NaN ──
|
||||
#[test]
|
||||
fn sloppy_compound_operator_undeclared_write_is_nan() {
|
||||
let s = eval_str("w += 1; typeof w");
|
||||
assert_eq!(s, "number", "w += 1 未声明名应为 NaN(typeof number),实际 {s}");
|
||||
fn sloppy_compound_operator_undeclared_write_throws() {
|
||||
let result = eval("w += 1");
|
||||
assert_err_contains(result, "w is not defined");
|
||||
}
|
||||
|
||||
// ── 逻辑赋值:未声明名 ||= 短路建全局 ──
|
||||
// ── 逻辑赋值:未声明名旧值读抛 ReferenceError,短路路径不可达,不物化全局 ──
|
||||
#[test]
|
||||
fn sloppy_logical_assign_undeclared_write_lands_on_global() {
|
||||
let r = eval_many(&["p ||= 5", "globalThis.p === 5"]).unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "p ||= 5 未声明写应落全局为 5,实际 {:?}", r);
|
||||
fn sloppy_logical_assign_undeclared_write_throws() {
|
||||
let result = eval("p ||= 5");
|
||||
assert_err_contains(result, "p is not defined");
|
||||
}
|
||||
|
||||
// ── for-in 左侧未声明标识符:首个键后建全局 ──
|
||||
|
||||
@@ -59,12 +59,12 @@ fn with_fallback_declared_var_writes_outer_binding() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_fallback_compound_undeclared_write_keeps_existing_behavior() {
|
||||
// 复合赋值回退臂走静态复合路径:未声明名旧值按 undefined 读
|
||||
// (GetBaseValue 语义不抛),复合运算结果落隐式全局。
|
||||
assert_eq!(
|
||||
eval("var o={}; with(o){x+=1;} isNaN(globalThis.x)"),
|
||||
"true",
|
||||
"with 回退未声明名复合赋值保持既有读 undefined 行为"
|
||||
fn with_fallback_compound_undeclared_write_throws() {
|
||||
// 复合赋值回退臂走静态复合路径:未声明名旧值读在 lval 求值步抛
|
||||
// ReferenceError(未解析引用不可 GetValue,两模式同),不物化隐式全局。
|
||||
let s = eval("var o={}; with(o){x+=1;}");
|
||||
assert!(
|
||||
s.contains("ReferenceError") && s.contains("x is not defined"),
|
||||
"with 回退未声明名复合赋值应抛 ReferenceError,实际 {s}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user