mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: class 私有成员语义(字段/方法/访问器 + brand 检查 + 契约修复)
This commit is contained in:
@@ -362,10 +362,7 @@ pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
/// # 副作用
|
||||
/// - 修改 obj 的 shape 链、属性表与 generation
|
||||
fn define_from_descriptor<H: VmHost>(
|
||||
vm: &mut H,
|
||||
obj_ptr: *mut JsObject,
|
||||
key_si: u32,
|
||||
desc_val: JsValue,
|
||||
vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue,
|
||||
) -> Result<(), String> {
|
||||
if !desc_val.is_object() {
|
||||
return Err("Property description must be an object".to_string());
|
||||
@@ -477,9 +474,7 @@ fn define_from_descriptor<H: VmHost>(
|
||||
/// # 副作用
|
||||
/// - 修改 target 的 shape 链、属性表与 generation
|
||||
fn define_all_from_properties<H: VmHost>(
|
||||
vm: &mut H,
|
||||
target_ptr: *mut JsObject,
|
||||
props_val: JsValue,
|
||||
vm: &mut H, target_ptr: *mut JsObject, props_val: JsValue,
|
||||
) -> Result<(), String> {
|
||||
if !props_val.is_object() {
|
||||
return Err("Property description must be an object".to_string());
|
||||
@@ -495,11 +490,7 @@ fn define_all_from_properties<H: VmHost>(
|
||||
for (key_si, offset) in prop_keys {
|
||||
let props = unsafe { &*props_ptr };
|
||||
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
|
||||
if props
|
||||
.prop_meta_at(offset)
|
||||
.map(|m| !m.attributes.enumerable())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if props.prop_meta_at(offset).map(|m| !m.attributes.enumerable()).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let desc_val = vm.ordinary_get(props, key_si, props_val)?;
|
||||
|
||||
@@ -224,12 +224,9 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let group_count = m.captures.len();
|
||||
let n = 1 + group_count;
|
||||
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.epoch().alloc(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto),
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
let arr =
|
||||
vm.epoch()
|
||||
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
|
||||
unsafe {
|
||||
(*arr).set_prop_at(0, vm.new_string(&haystack[range.start..range.end]));
|
||||
// 捕获组:未参与匹配的组为 undefined。
|
||||
@@ -545,4 +542,3 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn);
|
||||
NativeResult::Ok(JsValue::from_js_object(wrapper))
|
||||
}
|
||||
|
||||
|
||||
@@ -973,10 +973,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
let pos = if args.len() > 1 {
|
||||
let pos_val = vm.reg(args[1]);
|
||||
if pos_val.is_symbol() {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Cannot convert a Symbol value to a number",
|
||||
));
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a number"));
|
||||
}
|
||||
match vm.coerce_number_bounded(pos_val) {
|
||||
Ok(n) => n,
|
||||
@@ -985,10 +982,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Cannot convert argument to a number",
|
||||
));
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert argument to a number"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1264,6 +1258,3 @@ pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let result = s.replace(&pattern, &replacement);
|
||||
NativeResult::Ok(vm.new_string(&result))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,15 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
|
||||
// 跳转的 fallthrough 后继是块头:条件跳转否则 cond+then 融块、条件边丢失;
|
||||
// 无条件 JMP 同样必须切块头——JMP 不 fallthrough,但其后继位置需成块边界,
|
||||
// 否则 JMP 与后续指令融块、块尾判定错、Jump 边丢失(DCE 可达性误删目标块)。
|
||||
if matches!(inst.op, OpCode::JMP | OpCode::BREAK | OpCode::CONTINUE | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH) {
|
||||
if matches!(
|
||||
inst.op,
|
||||
OpCode::JMP
|
||||
| OpCode::BREAK
|
||||
| OpCode::CONTINUE
|
||||
| OpCode::JMP_IF_TRUE
|
||||
| OpCode::JMP_IF_FALSE
|
||||
| OpCode::JMP_IF_NULLISH
|
||||
) {
|
||||
heads[i + 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,13 @@ fn assign_ids(module: &mut CompiledModule, next_id: &mut u32) {
|
||||
if opcode::opcode(*instr) == OpCode::CREATE_CLOSURE {
|
||||
let rel = opcode::imm16(*instr) as usize;
|
||||
// rel 应为 1-based 子模块下标;0 表示无引用(异常输入),按未解析处理。
|
||||
let flat = if rel > 0 { module.sub_modules.get(rel - 1).map(|s| s.flat_id).unwrap_or(0) } else { 0 };
|
||||
let flat = if rel > 0 {
|
||||
module.sub_modules.get(rel - 1).map(|s| s.flat_id).unwrap_or(0)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let rd = opcode::rd(*instr);
|
||||
*instr = opcode::encode(
|
||||
OpCode::CREATE_CLOSURE,
|
||||
rd,
|
||||
(flat & 0xFF) as u8,
|
||||
((flat >> 8) & 0xFF) as u8,
|
||||
);
|
||||
*instr = opcode::encode(OpCode::CREATE_CLOSURE, rd, (flat & 0xFF) as u8, ((flat >> 8) & 0xFF) as u8);
|
||||
}
|
||||
}
|
||||
for sub in &mut module.sub_modules {
|
||||
|
||||
@@ -537,7 +537,10 @@ fn compile_call_spread_emits_spread_opcodes() {
|
||||
"f(...args) should emit CALL_SPREAD"
|
||||
);
|
||||
assert!(
|
||||
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD),
|
||||
module
|
||||
.bytecode
|
||||
.iter()
|
||||
.any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD),
|
||||
"new f(...args) should emit NEW_EXPRESSION_SPREAD"
|
||||
);
|
||||
// 无 spread 的调用不发 spread 变体
|
||||
|
||||
@@ -14,7 +14,7 @@ impl Emitter {
|
||||
let elements = &class.body.body;
|
||||
let mut constructor_method = None;
|
||||
let mut instance_field_indices = Vec::new();
|
||||
let mut private_names = Vec::<(String, u32)>::new();
|
||||
let mut private_names = Vec::<(String, u32, Option<MethodDefinitionKind>, bool)>::new();
|
||||
let is_derived = class.super_class.is_some();
|
||||
|
||||
for (idx, element) in elements.iter().enumerate() {
|
||||
@@ -23,12 +23,18 @@ impl Emitter {
|
||||
let method = method.as_ref();
|
||||
if let PropertyKey::PrivateIdentifier(private) = &method.key {
|
||||
let name = private.name.as_str().to_string();
|
||||
if private_names.iter().any(|(existing, _)| existing == &name) {
|
||||
return Err(format!("duplicate private name #{name}"));
|
||||
let kind = method.kind;
|
||||
match private_names.iter().find(|(n, _, _, _)| n == &name) {
|
||||
// 同名 getter/setter 构成访问器对,复用同一私有名。
|
||||
Some((_, _, Some(MethodDefinitionKind::Get), _)) if kind == MethodDefinitionKind::Set => {}
|
||||
Some((_, _, Some(MethodDefinitionKind::Set), _)) if kind == MethodDefinitionKind::Get => {}
|
||||
Some(_) => return Err(format!("duplicate private name #{name}")),
|
||||
None => {
|
||||
let id = ctx.scopes.next_private_name_id;
|
||||
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
|
||||
private_names.push((name, id, Some(kind), method.r#static));
|
||||
}
|
||||
}
|
||||
let id = ctx.scopes.next_private_name_id;
|
||||
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
|
||||
private_names.push((name, id));
|
||||
}
|
||||
if method.kind == MethodDefinitionKind::Constructor {
|
||||
if constructor_method.is_some() {
|
||||
@@ -41,12 +47,12 @@ impl Emitter {
|
||||
let prop = prop.as_ref();
|
||||
if let PropertyKey::PrivateIdentifier(private) = &prop.key {
|
||||
let name = private.name.as_str().to_string();
|
||||
if private_names.iter().any(|(existing, _)| existing == &name) {
|
||||
if private_names.iter().any(|(existing, _, _, _)| existing == &name) {
|
||||
return Err(format!("duplicate private name #{name}"));
|
||||
}
|
||||
let id = ctx.scopes.next_private_name_id;
|
||||
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
|
||||
private_names.push((name, id));
|
||||
private_names.push((name, id, None, prop.r#static));
|
||||
}
|
||||
if !prop.r#static {
|
||||
instance_field_indices.push(idx);
|
||||
@@ -85,8 +91,26 @@ impl Emitter {
|
||||
let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default();
|
||||
let saved_derived = ctx.in_derived_constructor;
|
||||
let saved_private_names = ctx.scopes.private_name_map.clone();
|
||||
let saved_private_kinds = ctx.scopes.private_element_kinds.clone();
|
||||
let saved_brand_id = ctx.scopes.private_brand_id;
|
||||
ctx.in_derived_constructor = is_derived;
|
||||
ctx.scopes.private_name_map = private_names.clone();
|
||||
ctx.scopes.private_name_map = private_names.iter().map(|(n, id, _, _)| (n.clone(), *id)).collect();
|
||||
ctx.scopes.private_element_kinds = private_names
|
||||
.iter()
|
||||
.map(|(n, _, kind, is_static)| (n.clone(), *kind, *is_static))
|
||||
.collect();
|
||||
|
||||
// 类 brand:有私有元素时分配 brand 私有名 id,并创建 brand 对象(= 类原型)。
|
||||
// 构造器把 brand 槽写入实例 own;私有方法/访问器/静态字段访问(GET/SET)据
|
||||
// brand 对象同一性做检查;instance 字段走 PrivateFieldFind 原型链查找,不检查。
|
||||
let private_brand_id = if !private_names.is_empty() {
|
||||
let id = ctx.scopes.next_private_name_id;
|
||||
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ctx.scopes.private_brand_id = private_brand_id;
|
||||
|
||||
// 实例公有字段 computed key 求值于构造器帧外,须类定义期存入数组。构造器以
|
||||
// upvalue 捕获该数组:父作用域登记 `@@field_keys`(cell_idx 取现有最大 +1)。
|
||||
@@ -101,6 +125,12 @@ impl Emitter {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// 类 brand 对象(= proto)由类定义期 MAKE_CELL 写入,构造器经 upvalue 捕获。
|
||||
let brand_cell: Option<u8> = private_brand_id.map(|_| {
|
||||
let cell_idx = ctx.captured_bindings.values().copied().max().map_or(0, |m| m.saturating_add(1));
|
||||
ctx.captured_bindings.insert("@@class_brand".to_string(), cell_idx);
|
||||
cell_idx
|
||||
});
|
||||
// 字段值表达式运行于构造器帧:其自由变量须纳入构造器 upvalue 捕获。
|
||||
let field_value_exprs: Vec<&Expression> = instance_field_indices
|
||||
.iter()
|
||||
@@ -109,11 +139,44 @@ impl Emitter {
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let extra_upvalue_names: Vec<(&str, u8)> = field_key_cell.map(|c| ("@@field_keys", c)).into_iter().collect();
|
||||
let mut extra_upvalue_names: Vec<(&str, u8)> =
|
||||
field_key_cell.map(|c| ("@@field_keys", c)).into_iter().collect();
|
||||
if let Some(c) = brand_cell {
|
||||
extra_upvalue_names.push(("@@class_brand", c));
|
||||
}
|
||||
|
||||
let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> {
|
||||
// 先写 brand 槽(私有方法/访问器的 brand 检查依据),再初始化字段。
|
||||
if let Some(bid) = private_brand_id {
|
||||
if brand_cell.is_none() {
|
||||
return Err("class brand cell missing".into());
|
||||
}
|
||||
let brand_reg = field_ctx.alloc_reg();
|
||||
let uv_idx = field_ctx
|
||||
.current_upvalue_captures
|
||||
.iter()
|
||||
.position(|u| u.name == "@@class_brand")
|
||||
.ok_or("class brand upvalue missing")? as u16;
|
||||
field_ctx.inst(Inst::new(
|
||||
OpCode::LOAD_UPVALUE,
|
||||
Operand::Reg(brand_reg),
|
||||
Operand::Imm(uv_idx),
|
||||
Operand::None,
|
||||
));
|
||||
let key_idx = field_ctx.add_constant(Constant::Int(bid as i32));
|
||||
let brand_key_reg = field_ctx.alloc_reg();
|
||||
field_ctx.inst(Inst::load_const(Operand::Reg(brand_key_reg), key_idx));
|
||||
field_ctx.inst(Inst::init_private(
|
||||
Operand::This,
|
||||
Operand::Reg(brand_reg),
|
||||
Operand::Reg(brand_key_reg),
|
||||
false,
|
||||
));
|
||||
}
|
||||
for &idx in &instance_field_indices {
|
||||
let ClassElement::PropertyDefinition(field) = &elements[idx] else { unreachable!() };
|
||||
let ClassElement::PropertyDefinition(field) = &elements[idx] else {
|
||||
unreachable!()
|
||||
};
|
||||
let field = field.as_ref();
|
||||
if let PropertyKey::PrivateIdentifier(private) = &field.key {
|
||||
compiler.emit_private_field_init(
|
||||
@@ -170,7 +233,8 @@ impl Emitter {
|
||||
module.insts.push(Inst::super_call(Operand::None, Operand::None, 0));
|
||||
// 字段初始化直接重发:构造器 upvalue/内置槽引用须与首轮编译产物对齐。
|
||||
let mut field_ctx = CompileCtx::new();
|
||||
field_ctx.scopes.private_name_map = private_names.clone();
|
||||
field_ctx.scopes.private_name_map =
|
||||
private_names.iter().map(|(n, id, _, _)| (n.clone(), *id)).collect();
|
||||
field_ctx.scopes.builtin_reg_map = module.builtin_reg_map.clone();
|
||||
field_ctx.current_upvalue_captures = module.upvalue_captures.clone();
|
||||
field_ctx.field_keys_uv = module
|
||||
@@ -238,10 +302,31 @@ impl Emitter {
|
||||
}
|
||||
|
||||
self.emit_class_prototype(ctor_reg, proto_reg, super_reg, ctor_sub_idx, ctx)?;
|
||||
// brand 对象即类原型:构造器对象(静态私有访问)与实例(实例私有访问)各写
|
||||
// brand 槽;构造器经 @@class_brand upvalue 捕获原型对象。
|
||||
if let (Some(bid), Some(cell_idx)) = (private_brand_id, brand_cell) {
|
||||
let key_idx = ctx.add_constant(Constant::Int(bid as i32));
|
||||
let brand_key_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(brand_key_reg), key_idx));
|
||||
ctx.inst(Inst::init_private(
|
||||
Operand::Reg(ctor_reg),
|
||||
Operand::Reg(proto_reg),
|
||||
Operand::Reg(brand_key_reg),
|
||||
false,
|
||||
));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::MAKE_CELL,
|
||||
Operand::Reg(proto_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
Operand::None,
|
||||
));
|
||||
}
|
||||
self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, &key_slots, ctx)?;
|
||||
self.emit_class_static_elements(&class.body.body, ctor_reg, &key_slots, ctx)?;
|
||||
|
||||
ctx.scopes.private_name_map = saved_private_names;
|
||||
ctx.scopes.private_element_kinds = saved_private_kinds;
|
||||
ctx.scopes.private_brand_id = saved_brand_id;
|
||||
Ok(ctor_reg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,13 +90,25 @@ impl Emitter {
|
||||
let saved_static = ctx.in_static_method;
|
||||
ctx.in_instance_method = !method.r#static;
|
||||
ctx.in_static_method = method.r#static;
|
||||
let mut method_module = self.compile_function_body_with_bindings(
|
||||
// 私有方法/访问器访问需对接收者做 brand 检查:方法函数捕获类 brand 对象
|
||||
// (@@class_brand upvalue,值 = 类原型)。
|
||||
let extra_uv: Vec<(&str, u8)> = ctx
|
||||
.captured_bindings
|
||||
.get("@@class_brand")
|
||||
.map(|c| ("@@class_brand", *c))
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut method_module = self.compile_function_body_with_field_hooks(
|
||||
¶m_names,
|
||||
body_stmts,
|
||||
ctx,
|
||||
false,
|
||||
self_binding,
|
||||
FunctionBodyContext::ClassElement,
|
||||
None::<fn(&Emitter, &mut CompileCtx) -> Result<(), String>>,
|
||||
false,
|
||||
&[],
|
||||
&extra_uv,
|
||||
)?;
|
||||
ctx.in_instance_method = saved_instance;
|
||||
ctx.in_static_method = saved_static;
|
||||
|
||||
@@ -190,7 +190,13 @@ impl Emitter {
|
||||
// 参数声明名遮蔽 catch 作用域。
|
||||
let mut catch_shadow = shadow.clone();
|
||||
if let Some(param) = &h.param {
|
||||
self.collect_capture_names_binding_pattern(¶m.pattern, ref_set, shadow, out, &mut catch_shadow);
|
||||
self.collect_capture_names_binding_pattern(
|
||||
¶m.pattern,
|
||||
ref_set,
|
||||
shadow,
|
||||
out,
|
||||
&mut catch_shadow,
|
||||
);
|
||||
}
|
||||
for s in &h.body.body {
|
||||
self.collect_capture_names_stmt(s, ref_set, &catch_shadow, out);
|
||||
@@ -490,7 +496,8 @@ impl Emitter {
|
||||
}
|
||||
|
||||
/// 只从嵌套函数节点进入扫描(本函数直接引用不算捕获)。
|
||||
fn collect_captured_stmt(&self, stmt: &Statement, own: &HashSet<String>, out: &mut HashSet<String>) { match stmt {
|
||||
fn collect_captured_stmt(&self, stmt: &Statement, own: &HashSet<String>, out: &mut HashSet<String>) {
|
||||
match stmt {
|
||||
Statement::FunctionDeclaration(fd) => {
|
||||
let body: &[Statement] = fd.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]);
|
||||
self.collect_fn_default_captured(&fd.params, own, out);
|
||||
|
||||
@@ -24,7 +24,6 @@ pub fn is_anonymous_function_definition(expr: &oxide_parser::Expression) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
use crate::emit_ctx::{LabelCtx, ScopeCtx};
|
||||
use crate::symbol_table::{Binding, ScopeKind, SymbolTable};
|
||||
|
||||
@@ -254,6 +253,8 @@ impl CompileCtx {
|
||||
symbols: SymbolTable::new(),
|
||||
builtin_reg_map: Vec::new(),
|
||||
private_name_map: Vec::new(),
|
||||
private_element_kinds: Vec::new(),
|
||||
private_brand_id: None,
|
||||
next_private_name_id: 1,
|
||||
},
|
||||
nested: Vec::new(),
|
||||
@@ -659,7 +660,6 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ── 闭包捕获分析(AST 级,时序无关)──
|
||||
|
||||
/// 收集函数参数的绑定名(BindingIdentifier 形态)。
|
||||
@@ -741,8 +741,8 @@ impl Emitter {
|
||||
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext,
|
||||
mut emit_fields: Option<E>, fields_after_super: bool,
|
||||
extra_capture_exprs: &[&'a Expression<'a>], extra_upvalue_names: &[(&str, u8)],
|
||||
mut emit_fields: Option<E>, fields_after_super: bool, extra_capture_exprs: &[&'a Expression<'a>],
|
||||
extra_upvalue_names: &[(&str, u8)],
|
||||
) -> Result<IRFunction, String>
|
||||
where
|
||||
E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>,
|
||||
@@ -753,6 +753,8 @@ impl Emitter {
|
||||
// 解析到父预先分配的槽位。
|
||||
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
|
||||
ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone();
|
||||
ctx.scopes.private_element_kinds = parent_ctx.scopes.private_element_kinds.clone();
|
||||
ctx.scopes.private_brand_id = parent_ctx.scopes.private_brand_id;
|
||||
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
|
||||
|
||||
// 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。
|
||||
@@ -961,7 +963,11 @@ impl Emitter {
|
||||
|
||||
for spec in param_specs {
|
||||
match spec {
|
||||
ParamSpec::Pattern { synthetic_name, pattern, initializer } => {
|
||||
ParamSpec::Pattern {
|
||||
synthetic_name,
|
||||
pattern,
|
||||
initializer,
|
||||
} => {
|
||||
let src_reg = ctx.lookup(synthetic_name)?;
|
||||
let src_reg = if let Some(init) = initializer {
|
||||
self.emit_default_if_undefined(src_reg, init, Some(synthetic_name), ctx)?
|
||||
@@ -1154,4 +1160,3 @@ impl Default for Emitter {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use crate::symbol_table::SymbolTable;
|
||||
use crate::LabelScope;
|
||||
use oxide_ir::operand::LabelId;
|
||||
use oxide_parser::MethodDefinitionKind;
|
||||
|
||||
/// 跳转目标 / 标签语句解析状态。
|
||||
pub(crate) struct LabelCtx {
|
||||
@@ -41,6 +42,11 @@ pub(crate) struct ScopeCtx {
|
||||
pub(crate) symbols: SymbolTable,
|
||||
pub(crate) builtin_reg_map: Vec<(String, u32)>,
|
||||
pub(crate) private_name_map: Vec<(String, u32)>,
|
||||
/// 私有元素类型(name, kind,static 标志)。kind=None 表示字段;
|
||||
/// instance 字段的私有访问走 PrivateFieldFind 原型链查找,不加 brand 检查。
|
||||
pub(crate) private_element_kinds: Vec<(String, Option<MethodDefinitionKind>, bool)>,
|
||||
/// 当前类的私有 brand 私有名 id:私有方法/访问器访问时对实例做 brand 检查。
|
||||
pub(crate) private_brand_id: Option<u32>,
|
||||
pub(crate) next_private_name_id: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -207,12 +207,15 @@ impl Emitter {
|
||||
}
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::SET_PRIVATE,
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
ctx.inst(Inst::set_private(
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
Ok(val_reg)
|
||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left {
|
||||
|
||||
@@ -39,13 +39,16 @@ impl Emitter {
|
||||
let (callee_reg, this_reg) = match &call.callee {
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let callee_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::GET_PRIVATE,
|
||||
ctx.inst(Inst::get_private(
|
||||
Operand::Reg(callee_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
(callee_reg, obj_reg)
|
||||
}
|
||||
|
||||
@@ -54,12 +54,7 @@ impl Emitter {
|
||||
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
|
||||
|
||||
let has_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::IN,
|
||||
Operand::Reg(has_reg),
|
||||
Operand::Reg(key_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
));
|
||||
ctx.inst(Inst::new(OpCode::IN, Operand::Reg(has_reg), Operand::Reg(key_reg), Operand::Reg(obj_reg)));
|
||||
|
||||
let fallback_label = ctx.next_label_id();
|
||||
let end_label = ctx.next_label_id();
|
||||
@@ -101,12 +96,7 @@ impl Emitter {
|
||||
));
|
||||
}
|
||||
} else if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
Operand::Reg(reg),
|
||||
Operand::None,
|
||||
));
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(reg), Operand::None));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.inst(Inst::load_const(Operand::Reg(result_reg), undef_idx));
|
||||
@@ -164,12 +154,7 @@ impl Emitter {
|
||||
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
|
||||
|
||||
let has_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::IN,
|
||||
Operand::Reg(has_reg),
|
||||
Operand::Reg(key_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
));
|
||||
ctx.inst(Inst::new(OpCode::IN, Operand::Reg(has_reg), Operand::Reg(key_reg), Operand::Reg(obj_reg)));
|
||||
|
||||
let fallback_label = ctx.next_label_id();
|
||||
let end_label = ctx.next_label_id();
|
||||
|
||||
@@ -70,9 +70,7 @@ impl Emitter {
|
||||
// 非法正则字面量须在编译期报 SyntaxError(负面测试期望编译失败)。
|
||||
// 用 regress(ECMAScript 语法)校验,避免误报 backreference/lookaround 等合法模式。
|
||||
if regress::Regex::with_flags(&pattern, flags.as_str()).is_err() {
|
||||
return Err(format!(
|
||||
"SyntaxError: Invalid regular expression: /{pattern}/{flags}"
|
||||
));
|
||||
return Err(format!("SyntaxError: Invalid regular expression: /{pattern}/{flags}"));
|
||||
}
|
||||
let pat_ci = ctx.add_constant(Constant::String(pattern));
|
||||
let pat_reg = ctx.alloc_reg();
|
||||
@@ -96,5 +94,3 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -60,13 +60,16 @@ impl Emitter {
|
||||
&self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::GET_PRIVATE,
|
||||
ctx.inst(Inst::get_private(
|
||||
Operand::Reg(r),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
@@ -343,12 +343,7 @@ impl Emitter {
|
||||
} else {
|
||||
OpCode::SUB
|
||||
};
|
||||
ctx.inst(Inst::new(
|
||||
op,
|
||||
Operand::Reg(new_reg),
|
||||
Operand::Reg(old_reg),
|
||||
Operand::Reg(one_reg),
|
||||
));
|
||||
ctx.inst(Inst::new(op, Operand::Reg(new_reg), Operand::Reg(old_reg), Operand::Reg(one_reg)));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::SET_PROP_DYNAMIC,
|
||||
Operand::Reg(obj_reg),
|
||||
|
||||
@@ -72,13 +72,16 @@ impl Emitter {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let callee_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::GET_PRIVATE,
|
||||
ctx.inst(Inst::get_private(
|
||||
Operand::Reg(callee_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
(callee_reg, obj_reg)
|
||||
}
|
||||
@@ -165,13 +168,16 @@ impl Emitter {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::GET_PRIVATE,
|
||||
ctx.inst(Inst::get_private(
|
||||
Operand::Reg(value_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
Ok(value_reg)
|
||||
}
|
||||
@@ -200,13 +206,16 @@ impl Emitter {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let name = member.field.name.as_str();
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::GET_PRIVATE,
|
||||
ctx.inst(Inst::get_private(
|
||||
Operand::Reg(value_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
Ok(value_reg)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_parser::{Expression, PropertyKey};
|
||||
use oxide_parser::{Expression, MethodDefinitionKind, PropertyKey};
|
||||
|
||||
impl Emitter {
|
||||
/// 实例字段初始化:define 语义(不触发原型 setter)。
|
||||
/// 非计算键用字符串常量;计算键从 `@@field_keys` upvalue 数组按 `key_slot` 取(类定义期求值一次)。
|
||||
pub(crate) fn emit_public_field_init(
|
||||
&self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>,
|
||||
key_slot: Option<u8>, ctx: &mut CompileCtx,
|
||||
&self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, key_slot: Option<u8>,
|
||||
ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
let key_reg = if computed {
|
||||
self.emit_instance_field_key(key_slot, ctx)?
|
||||
@@ -28,7 +28,12 @@ impl Emitter {
|
||||
fn emit_instance_field_key(&self, key_slot: Option<u8>, ctx: &mut CompileCtx) -> Result<u32, String> {
|
||||
let uv = ctx.field_keys_uv.expect("computed instance field without field keys upvalue");
|
||||
let arr_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(OpCode::LOAD_UPVALUE, Operand::Reg(arr_reg), Operand::Imm(uv as u16), Operand::None));
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_UPVALUE,
|
||||
Operand::Reg(arr_reg),
|
||||
Operand::Imm(uv as u16),
|
||||
Operand::None,
|
||||
));
|
||||
let idx_reg = ctx.alloc_reg();
|
||||
let idx = ctx.add_constant(Constant::Int(key_slot.unwrap_or(0) as i32));
|
||||
ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx));
|
||||
@@ -56,7 +61,7 @@ impl Emitter {
|
||||
) -> Result<(), String> {
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let value_reg = self.emit_field_value(value, ctx)?;
|
||||
ctx.inst(Inst::new(OpCode::INIT_PRIVATE, target, Operand::Reg(value_reg), Operand::Reg(key_reg)));
|
||||
ctx.inst(Inst::init_private(target, Operand::Reg(value_reg), Operand::Reg(key_reg), false));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -67,9 +72,30 @@ impl Emitter {
|
||||
return Err("expected private method key".into());
|
||||
};
|
||||
let name = private.name.as_str();
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let method_reg = self.emit_class_method_function(method, name, home_reg, ctx, &[])?;
|
||||
ctx.inst(Inst::new(OpCode::INIT_PRIVATE, target, Operand::Reg(method_reg), Operand::Reg(key_reg)));
|
||||
match method.kind {
|
||||
MethodDefinitionKind::Method => {
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
ctx.inst(Inst::init_private(target, Operand::Reg(method_reg), Operand::Reg(key_reg), true));
|
||||
}
|
||||
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
|
||||
let id = self.private_name_id(name, ctx)?;
|
||||
let key_idx = ctx.add_constant(Constant::Int(id as i32));
|
||||
let undef_reg = self.emit_undefined(ctx);
|
||||
let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get {
|
||||
(method_reg, undef_reg)
|
||||
} else {
|
||||
(undef_reg, method_reg)
|
||||
};
|
||||
ctx.inst(Inst::define_accessor(
|
||||
target,
|
||||
Operand::Reg(get_reg),
|
||||
Operand::Reg(set_reg),
|
||||
key_idx as u32,
|
||||
));
|
||||
}
|
||||
MethodDefinitionKind::Constructor => return Err("constructor cannot be private".into()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use oxide_ir::operand::Operand;
|
||||
use oxide_parser::PropertyKey;
|
||||
|
||||
impl Emitter {
|
||||
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
|
||||
pub(crate) fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
|
||||
ctx.scopes
|
||||
.private_name_map
|
||||
.iter()
|
||||
@@ -25,6 +25,36 @@ impl Emitter {
|
||||
Ok(reg)
|
||||
}
|
||||
|
||||
/// 私有成员访问的 brand 编码:返回 `(brand_reg, brand_id)`,供 GET_PRIVATE/SET_PRIVATE
|
||||
/// dispatch 做 brand 检查。
|
||||
///
|
||||
/// instance 字段走 PrivateFieldFind 原型链查找(允许 `Object.create` 链穿透),
|
||||
/// 不检查 brand(返回 `(0,0)`);方法/访问器/静态字段须验证接收者属于当前类
|
||||
/// (brand 对象同一性)。brand 对象由方法函数捕获的 `@@class_brand` upvalue 提供;
|
||||
/// 嵌套函数未捕获该 upvalue 时跳过检查(保持语法合法,brand 语义受限)。
|
||||
pub(crate) fn private_access_brand(
|
||||
&self, _obj_reg: u32, name: &str, ctx: &mut CompileCtx,
|
||||
) -> Result<(u32, u32), String> {
|
||||
let Some(brand_id) = ctx.scopes.private_brand_id else { return Ok((0, 0)) };
|
||||
let Some((_, kind, is_static)) = ctx.scopes.private_element_kinds.iter().find(|(n, _, _)| n == name) else {
|
||||
return Ok((0, 0));
|
||||
};
|
||||
if kind.is_none() && !is_static {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == "@@class_brand") else {
|
||||
return Ok((0, 0));
|
||||
};
|
||||
let brand_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_UPVALUE,
|
||||
Operand::Reg(brand_reg),
|
||||
Operand::Imm(uv_idx as u16),
|
||||
Operand::None,
|
||||
));
|
||||
Ok((brand_reg, brand_id))
|
||||
}
|
||||
|
||||
pub(crate) fn emit_class_key_reg(
|
||||
&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
|
||||
@@ -21,17 +21,15 @@ impl Emitter {
|
||||
let mut param_names = Vec::new();
|
||||
for (idx, param) in fd.params.items.iter().enumerate() {
|
||||
match ¶m.pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
|
||||
param_names.push(ParamSpec::Identifier {
|
||||
name: bi.name.to_string(),
|
||||
initializer: param.initializer.as_deref(),
|
||||
})
|
||||
}
|
||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier {
|
||||
name: bi.name.to_string(),
|
||||
initializer: param.initializer.as_deref(),
|
||||
}),
|
||||
pattern => param_names.push(ParamSpec::Pattern {
|
||||
synthetic_name: format!("@@param_{idx}"),
|
||||
pattern,
|
||||
initializer: param.initializer.as_deref(),
|
||||
}),
|
||||
synthetic_name: format!("@@param_{idx}"),
|
||||
pattern,
|
||||
initializer: param.initializer.as_deref(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] };
|
||||
@@ -54,4 +52,3 @@ impl Emitter {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,4 +67,3 @@ impl Emitter {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,8 @@ impl Inst {
|
||||
| OpCode::SET_PROP_DYNAMIC
|
||||
| OpCode::IC_SET_PROP
|
||||
| OpCode::SET_ELEM
|
||||
| OpCode::GET_PRIVATE
|
||||
| OpCode::SET_PRIVATE
|
||||
| OpCode::INIT_PRIVATE
|
||||
| OpCode::PRIVATE_BRAND_IN
|
||||
| OpCode::DELETE_PROP_STATIC
|
||||
| OpCode::DELETE_PROP_DYNAMIC
|
||||
| OpCode::DEFINE_ACCESSOR
|
||||
@@ -253,16 +251,37 @@ impl Inst {
|
||||
| OpCode::SET_PROP_DYNAMIC
|
||||
| OpCode::IC_SET_PROP
|
||||
| OpCode::SET_ELEM
|
||||
| OpCode::GET_PRIVATE
|
||||
| OpCode::SET_PRIVATE
|
||||
| OpCode::INIT_PRIVATE
|
||||
| OpCode::PRIVATE_BRAND_IN
|
||||
| OpCode::DEFINE_ACCESSOR
|
||||
| OpCode::DEFINE_PROP => {
|
||||
push_operand(&mut uses, &self.rd);
|
||||
push_operand(&mut uses, &self.a);
|
||||
push_operand(&mut uses, &self.b);
|
||||
}
|
||||
// 私有写:rd=对象、a=值、b=键是 use,额外读 ext[0](brand 对象寄存器)
|
||||
OpCode::SET_PRIVATE => {
|
||||
push_operand(&mut uses, &self.rd);
|
||||
push_operand(&mut uses, &self.a);
|
||||
push_operand(&mut uses, &self.b);
|
||||
let brand_reg = self.ext.first().copied().unwrap_or(0);
|
||||
if brand_reg != 0 {
|
||||
uses.push(brand_reg);
|
||||
}
|
||||
}
|
||||
// 私有读/品牌检查:a=对象、b=私有键是 use,rd 是结果 def
|
||||
// GET_PRIVATE 额外读 ext[0](brand 对象寄存器,0 表示跳过检查)
|
||||
OpCode::GET_PRIVATE => {
|
||||
push_operand(&mut uses, &self.a);
|
||||
push_operand(&mut uses, &self.b);
|
||||
let brand_reg = self.ext.first().copied().unwrap_or(0);
|
||||
if brand_reg != 0 {
|
||||
uses.push(brand_reg);
|
||||
}
|
||||
}
|
||||
OpCode::PRIVATE_BRAND_IN => {
|
||||
push_operand(&mut uses, &self.a);
|
||||
push_operand(&mut uses, &self.b);
|
||||
}
|
||||
// delete:读 obj(+dynamic 读 key)
|
||||
OpCode::DELETE_PROP_STATIC => push_operand(&mut uses, &self.rd),
|
||||
OpCode::DELETE_PROP_DYNAMIC => {
|
||||
|
||||
@@ -168,6 +168,24 @@ impl Inst {
|
||||
|
||||
// ── 其他带 ext 字 ──
|
||||
|
||||
/// 私有成员读取:result=结果,obj=接收者,key=私有名 id。
|
||||
/// ext = [brand_reg, brand_id]:brand_reg 为当前类 brand 对象寄存器(0 表示跳过
|
||||
/// brand 检查,instance 字段路径),brand_id 为实例 brand 槽的私有名 id。
|
||||
pub fn get_private(result: Operand, obj: Operand, key: Operand, brand_reg: u32, brand_id: u32) -> Self {
|
||||
Self::with_ext(OpCode::GET_PRIVATE, result, obj, key, &[brand_reg, brand_id])
|
||||
}
|
||||
|
||||
/// 私有成员写入:obj=接收者,value=新值,key=私有名 id。ext 语义同 `get_private`。
|
||||
pub fn set_private(obj: Operand, value: Operand, key: Operand, brand_reg: u32, brand_id: u32) -> Self {
|
||||
Self::with_ext(OpCode::SET_PRIVATE, obj, value, key, &[brand_reg, brand_id])
|
||||
}
|
||||
|
||||
/// 私有成员初始化:target=接收者,value=初值,key=私有名 id。
|
||||
/// ext = [is_method]:1 表示私有方法槽(不可写),0 表示字段/brand 槽。
|
||||
pub fn init_private(target: Operand, value: Operand, key: Operand, is_method: bool) -> Self {
|
||||
Self::with_ext(OpCode::INIT_PRIVATE, target, value, key, &[is_method as u32])
|
||||
}
|
||||
|
||||
/// 定义访问器属性:home 为宿主对象,get/set 为访问器函数寄存器,key_idx 为属性名常量下标。
|
||||
pub fn define_accessor(home: Operand, get: Operand, set: Operand, key_idx: u32) -> Self {
|
||||
Self::with_ext(OpCode::DEFINE_ACCESSOR, home, get, set, &[key_idx])
|
||||
|
||||
@@ -307,4 +307,3 @@ fn operand_this_is_semantic_not_physical_index() {
|
||||
assert_eq!(Operand::This, Operand::This);
|
||||
assert_eq!(Operand::NewTarget, Operand::NewTarget);
|
||||
}
|
||||
|
||||
|
||||
@@ -1104,7 +1104,14 @@ impl BuiltinWorld {
|
||||
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
|
||||
let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject;
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1), ("fromCodePoint", methods.from_code_point, 1),);
|
||||
bind_methods!(
|
||||
self,
|
||||
ctor,
|
||||
string_forge,
|
||||
shape_forge,
|
||||
("fromCharCode", methods.from_char_code, 1),
|
||||
("fromCodePoint", methods.from_code_point, 1),
|
||||
);
|
||||
|
||||
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
|
||||
@@ -85,8 +85,7 @@ pub fn init(config: &LogConfig) {
|
||||
}
|
||||
SUBSYSTEM.set(filter.clone()).ok();
|
||||
|
||||
let env_filter = EnvFilter::try_from_env("OXIDE_LOG")
|
||||
.unwrap_or_else(|_| EnvFilter::new("oxide=trace"));
|
||||
let env_filter = EnvFilter::try_from_env("OXIDE_LOG").unwrap_or_else(|_| EnvFilter::new("oxide=trace"));
|
||||
|
||||
subsystem::apply_env_levels();
|
||||
|
||||
|
||||
@@ -36,8 +36,9 @@ pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a
|
||||
|
||||
// 语法早期错误(Early Errors)检查:oxc parser 把复杂早期错误委托给
|
||||
// oxc_semantic(strict 绑定标识符、重复参数、NSPL、super 位置等)。
|
||||
let semantic_ret =
|
||||
oxc_semantic::SemanticBuilder::new().with_check_syntax_error(true).build(&ret.program);
|
||||
let semantic_ret = oxc_semantic::SemanticBuilder::new()
|
||||
.with_check_syntax_error(true)
|
||||
.build(&ret.program);
|
||||
if !semantic_ret.errors.is_empty() {
|
||||
return Err(semantic_ret.errors.into_iter().map(OxideError::from).collect());
|
||||
}
|
||||
|
||||
@@ -225,6 +225,16 @@ fn rewrite_inst(
|
||||
}
|
||||
ext
|
||||
}
|
||||
// GET_PRIVATE/SET_PRIVATE:ext[0] 是 brand 对象寄存器(0 表示跳过检查),须重映射。
|
||||
OpCode::GET_PRIVATE | OpCode::SET_PRIVATE => {
|
||||
let mut ext = inst.ext.clone();
|
||||
if let Some(brand_reg) = ext.first_mut() {
|
||||
if *brand_reg != 0 {
|
||||
*brand_reg = remap_ext_reg(*brand_reg, slot_color, map);
|
||||
}
|
||||
}
|
||||
ext
|
||||
}
|
||||
_ => inst.ext.clone(),
|
||||
};
|
||||
// 调用点首参槽改指 arg_window_base(桥接后)
|
||||
|
||||
@@ -774,6 +774,18 @@ impl JsObject {
|
||||
self.set_meta_at(position, PropMetaEntry::accessor(get, set, attributes));
|
||||
}
|
||||
|
||||
/// 标记私有方法槽为不可写:复用 `hole` 位(私有键在普通属性/枚举路径不可见,
|
||||
/// 与数组删除标记无冲突)。GET_PRIVATE 仍按数据槽取值,SET_PRIVATE 遇此标记抛错。
|
||||
pub fn set_private_method_meta(&mut self, position: impl PropIndex) {
|
||||
self.set_meta_at(
|
||||
position,
|
||||
PropMetaEntry {
|
||||
hole: true,
|
||||
..PropMetaEntry::data(PropAttributes::DEFAULT_DATA)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 判断指定下标的属性是否为访问器属性。
|
||||
pub fn is_accessor_meta(&self, position: impl PropIndex) -> bool {
|
||||
self.prop_meta_at(position).is_some_and(|entry| entry.is_accessor)
|
||||
|
||||
@@ -31,7 +31,11 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
),
|
||||
("@@search", oxide_builtins::regexp::regexp_symbol_search::<crate::vm::Vm> as *const (), 1),
|
||||
("@@split", oxide_builtins::regexp::regexp_symbol_split::<crate::vm::Vm> as *const (), 2),
|
||||
("@@matchAll", oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"@@matchAll",
|
||||
oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -171,9 +171,12 @@ impl Vm {
|
||||
|
||||
// length:实参个数,可写、不可枚举、可配置。
|
||||
let length_si = self.kernel_core.perm_interner().intern("length").0;
|
||||
if let Err(msg) =
|
||||
self.define_data_property(obj, length_si, JsValue::int(count as i32), PropAttributes::new(true, false, true))
|
||||
{
|
||||
if let Err(msg) = self.define_data_property(
|
||||
obj,
|
||||
length_si,
|
||||
JsValue::int(count as i32),
|
||||
PropAttributes::new(true, false, true),
|
||||
) {
|
||||
return self.raise_error_kind("TypeError", &msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{ic_debug, ic_trace, vm_trace};
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_runtime_api as coercion;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::private_key::make_private_name_id;
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -88,33 +89,115 @@ impl Vm {
|
||||
None
|
||||
}
|
||||
|
||||
fn dispatch_get_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("GET_PRIVATE rd={} a={} b={}", rd, a, b);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[a], "private field access on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
let Some(value) = self.resolve_private_value(obj, private_key) else {
|
||||
/// 沿实例→proto 链查找私有槽:私有方法/访问器存于类的 home(proto),
|
||||
/// 私有字段存于实例自身,两者都需要原型链遍历。
|
||||
fn find_private_slot(&self, obj: &JsObject, private_key: u32) -> Option<*mut JsObject> {
|
||||
if self.private_slot(obj, private_key).is_some() {
|
||||
return Some(obj as *const JsObject as *mut JsObject);
|
||||
}
|
||||
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 self.private_slot(proto_obj, private_key).is_some() {
|
||||
return Some(proto_obj as *const JsObject as *mut JsObject);
|
||||
}
|
||||
proto = proto_obj.proto();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 私有方法/访问器/静态字段访问的 brand 检查:接收者 own 的 brand 槽(构造时写入
|
||||
/// 的类 brand 对象)必须与当前类 brand 对象同一。类每次求值创建新 brand,同字节码
|
||||
/// 多次实例化(eval/factory)可借此区分。
|
||||
fn private_brand_check(&mut self, obj: &JsObject, brand_reg: usize, brand_key: u32) -> Result<(), String> {
|
||||
let Some(pos) = self.private_slot(obj, brand_key) else {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
};
|
||||
self.regs[rd] = value;
|
||||
let inst_brand = obj.get_prop_at(pos);
|
||||
if !coercion::strict_equality(inst_brand, self.regs[brand_reg]) {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_get_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("GET_PRIVATE rd={} a={} b={}", rd, a, b);
|
||||
let obj_val = self.regs[a];
|
||||
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field access on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
let brand_reg = self.bytecode[self.pc] as usize;
|
||||
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
|
||||
self.pc += 2;
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let Some(home_ptr) = self.find_private_slot(obj, private_key) else {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
};
|
||||
if brand_reg != 0 {
|
||||
self.private_brand_check(obj, brand_reg, brand_key)?;
|
||||
}
|
||||
let home = unsafe { &*home_ptr };
|
||||
let pos = self.private_slot(home, private_key).expect("slot just found");
|
||||
if let Some(meta) = home.prop_meta_at(pos) {
|
||||
if meta.is_accessor {
|
||||
if meta.get.is_undefined() {
|
||||
return self.raise_type_error("private field has no getter");
|
||||
}
|
||||
let getter = meta.get;
|
||||
let pushed = self.push_bytecode_getter_frame(getter, obj_val, rd as u8)?;
|
||||
// 与 dispatch_get_prop 一致:getter 帧入栈后消费 accessor_frame_target_reg,
|
||||
// 避免残留状态污染后续属性访问(getter 返回经 continuation 写 rd)。
|
||||
self.accessor_frame_target_reg.take();
|
||||
if pushed {
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.regs[rd] = home.get_prop_at(pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_set_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("SET_PRIVATE rd={} a={} b={}", rd, a, b);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "private field assignment on non-object")? else {
|
||||
let obj_val = self.regs[rd];
|
||||
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field assignment on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let Some(pos) = self.private_slot(obj, private_key) else {
|
||||
let brand_reg = self.bytecode[self.pc] as usize;
|
||||
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
|
||||
self.pc += 2;
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let Some(home_ptr) = self.find_private_slot(obj, private_key) else {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
};
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
obj.set_prop_shape(pos, value);
|
||||
if brand_reg != 0 {
|
||||
self.private_brand_check(obj, brand_reg, brand_key)?;
|
||||
}
|
||||
let home = unsafe { &*home_ptr };
|
||||
let pos = self.private_slot(home, private_key).expect("slot just found");
|
||||
if let Some(meta) = home.prop_meta_at(pos) {
|
||||
if meta.is_accessor {
|
||||
if meta.set.is_undefined() {
|
||||
return self.raise_type_error("private field has no setter");
|
||||
}
|
||||
let setter = meta.set;
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
return self.call_or_push_setter(setter, obj_val, value, true);
|
||||
}
|
||||
// 私有方法槽不可写(init 时打标记)。
|
||||
if meta.is_hole() {
|
||||
return self
|
||||
.raise_type_error("Cannot write private member to an object whose class did not declare it");
|
||||
}
|
||||
}
|
||||
let value = self.promote_if_needed_for_write_ptr(home_ptr, self.regs[a]);
|
||||
let home = unsafe { &mut *home_ptr };
|
||||
home.set_prop_shape(pos, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -125,12 +208,28 @@ impl Vm {
|
||||
return Ok(());
|
||||
};
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
let is_method = self.bytecode[self.pc] != 0;
|
||||
self.pc += 1;
|
||||
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key) {
|
||||
obj.set_prop_shape(pos, value);
|
||||
} else {
|
||||
self.set_or_create_prop_value(obj, private_key, value);
|
||||
if self
|
||||
.kernel_core
|
||||
.shape_forge()
|
||||
.lookup_position(obj.shape_id(), private_key)
|
||||
.is_some()
|
||||
{
|
||||
// 私有元素重复初始化(构造器返回外部对象、同一对象再次初始化):
|
||||
// 规范 PrivateFieldAdd 对已存在的私有名抛 TypeError。
|
||||
return self.raise_type_error("Cannot initialize private element twice");
|
||||
}
|
||||
self.set_or_create_prop_value(obj, private_key, value);
|
||||
if is_method {
|
||||
let pos = self
|
||||
.kernel_core
|
||||
.shape_forge()
|
||||
.lookup_position(obj.shape_id(), private_key)
|
||||
.expect("private slot just created");
|
||||
obj.set_private_method_meta(pos);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -139,8 +238,8 @@ impl Vm {
|
||||
vm_trace!("PRIVATE_BRAND_IN rd={} a={} b={}", rd, a, b);
|
||||
let obj_val = self.regs[a];
|
||||
if !obj_val.is_object() {
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
return Ok(());
|
||||
// `#x in rval`:rval 非对象抛 TypeError(规范 PrivateFieldIn 步骤 4)。
|
||||
return self.raise_type_error("private field `in` requires an object on the right-hand side");
|
||||
}
|
||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
|
||||
@@ -86,7 +86,10 @@ pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_d
|
||||
if proto_depth == 0 {
|
||||
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
let v = obj.get_prop_shape(slot_index);
|
||||
eprintln!("[DBG] ic_get_hit shape={} slot={} apc={} v={:?}", shape_id, slot_index, obj.array_prop_count, v);
|
||||
eprintln!(
|
||||
"[DBG] ic_get_hit shape={} slot={} apc={} v={:?}",
|
||||
shape_id, slot_index, obj.array_prop_count, v
|
||||
);
|
||||
return Some(v);
|
||||
}
|
||||
return None;
|
||||
@@ -127,4 +130,3 @@ pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, pro
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -154,18 +154,9 @@ pub struct TryHandler {
|
||||
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Completion {
|
||||
Break {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Continue {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Return {
|
||||
value: JsValue,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Break { target_pc: usize, remaining_finally: usize },
|
||||
Continue { target_pc: usize, remaining_finally: usize },
|
||||
Return { value: JsValue, remaining_finally: usize },
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
@@ -181,9 +172,18 @@ impl Completion {
|
||||
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
||||
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
||||
match *self {
|
||||
Completion::Break { target_pc, .. } => Completion::Break { target_pc, remaining_finally: remaining },
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue { target_pc, remaining_finally: remaining },
|
||||
Completion::Return { value, .. } => Completion::Return { value, remaining_finally: remaining },
|
||||
Completion::Break { target_pc, .. } => Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
Completion::Return { value, .. } => Completion::Return {
|
||||
value,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::vm::{Completion, FrameContinuation, TryHandler, Vm};
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_types::object::PropAttributes;
|
||||
use oxide_types::private_key::make_private_name_id;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
@@ -15,7 +16,8 @@ impl Vm {
|
||||
let obj_ptr = callee.as_js_object_ptr();
|
||||
if !obj_ptr.is_null() {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
if obj.is_function() { if obj.is_class_constructor() {
|
||||
if obj.is_function() {
|
||||
if obj.is_class_constructor() {
|
||||
return self
|
||||
.raise_type_error("class constructor cannot be invoked without 'new'")
|
||||
.map(|_| true);
|
||||
@@ -106,7 +108,13 @@ impl Vm {
|
||||
return self.raise_type_error("DEFINE_ACCESSOR target is not object");
|
||||
}
|
||||
let key_val = self.immutables()[prop_idx];
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
// 私有访问器:key 常量编码为 Int(私有名局部 id),映射到私有键高半区;
|
||||
// 普通访问器 key 是字符串常量,走 interner 键。
|
||||
let prop_name_si = if key_val.is_int() {
|
||||
make_private_name_id(key_val.as_int().max(0) as u32)
|
||||
} else {
|
||||
self.property_key_si(key_val)
|
||||
};
|
||||
let getter = self.regs[a];
|
||||
let setter = self.regs[b];
|
||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||
@@ -153,7 +161,10 @@ impl Vm {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break { target_pc, remaining_finally: crossed }) {
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
@@ -165,7 +176,10 @@ impl Vm {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue { target_pc, remaining_finally: crossed }) {
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
@@ -242,9 +256,10 @@ impl Vm {
|
||||
.iter()
|
||||
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
||||
.count();
|
||||
if let Some(finally_pc) =
|
||||
self.record_completion(Completion::Return { value: result, remaining_finally: crossed })
|
||||
{
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Return {
|
||||
value: result,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -283,11 +298,7 @@ impl Vm {
|
||||
let is_derived_constructor = frame.is_derived_constructor;
|
||||
let continuation = frame.continuation;
|
||||
let callee_this = self.regs[254];
|
||||
vm_trace!(
|
||||
"RETURN frame: continuation={:?}, derived={}",
|
||||
continuation,
|
||||
is_derived_constructor
|
||||
);
|
||||
vm_trace!("RETURN frame: continuation={:?}, derived={}", continuation, is_derived_constructor);
|
||||
self.restore_frame(frame);
|
||||
if let (Some(target_reg), Some(constructed_this)) = (construct_result_reg, constructed_this) {
|
||||
if is_derived_constructor && result.is_undefined() && callee_this.is_undefined() {
|
||||
|
||||
@@ -91,7 +91,7 @@ impl Vm {
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn push_bytecode_getter_frame(
|
||||
pub(crate) fn push_bytecode_getter_frame(
|
||||
&mut self, getter: JsValue, receiver: JsValue, target_reg: u8,
|
||||
) -> Result<bool, String> {
|
||||
vm_trace!(
|
||||
@@ -230,7 +230,7 @@ impl Vm {
|
||||
self.ordinary_set_inner(receiver_obj, prop_name_si, promoted, receiver, use_frame_push)
|
||||
}
|
||||
|
||||
fn call_or_push_setter(
|
||||
pub(crate) fn call_or_push_setter(
|
||||
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
|
||||
) -> Result<(), String> {
|
||||
vm_trace!("call_or_push_setter: frame_push={}", use_frame_push);
|
||||
|
||||
@@ -138,11 +138,7 @@ fn typed_array_to_string_is_identifiable() {
|
||||
#[test]
|
||||
fn typed_array_element_write_roundtrips() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int32Array(2); ta[0] = 42; ta[1] = ta[0] * 2; ta[1] === 84",
|
||||
)
|
||||
.unwrap();
|
||||
let result = eval(&mut vm, "var ta = new Int32Array(2); ta[0] = 42; ta[1] = ta[0] * 2; ta[1] === 84").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
@@ -156,11 +152,7 @@ fn typed_array_element_write_clamps_int8() {
|
||||
#[test]
|
||||
fn typed_array_out_of_bounds_write_is_ignored() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int8Array(1); ta[5] = 1; ta.length === 1 && ta[5] === undefined",
|
||||
)
|
||||
.unwrap();
|
||||
let result = eval(&mut vm, "var ta = new Int8Array(1); ta[5] = 1; ta.length === 1 && ta[5] === undefined").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
|
||||
@@ -363,9 +363,11 @@ fn computed_field_key_evaluated_once_at_class_definition() {
|
||||
#[test]
|
||||
fn computed_field_key_once_but_value_per_instance() {
|
||||
let mut vm = Vm::new();
|
||||
let result =
|
||||
eval(&mut vm, "var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]")
|
||||
.unwrap();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]",
|
||||
)
|
||||
.unwrap();
|
||||
assert_num(result, 323.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ fn delete_non_configurable_returns_false_in_sloppy_mode() {
|
||||
"delete obj.x",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(r.is_bool() && !r.as_bool(), "delete non-configurable in sloppy should return false, got {:?}", r);
|
||||
assert!(
|
||||
r.is_bool() && !r.as_bool(),
|
||||
"delete non-configurable in sloppy should return false, got {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 删除不存在的属性返回 true ──
|
||||
|
||||
@@ -27,17 +27,26 @@ fn eval(source: &str) -> String {
|
||||
|
||||
#[test]
|
||||
fn break_runs_finally_and_exits_loop() {
|
||||
assert_eq!(eval("var fin = 0; var i = 0; for (; i < 5; ) { try { break; } finally { fin = 1; } i = i + 1; } fin * 10 + i"), "10");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; var i = 0; for (; i < 5; ) { try { break; } finally { fin = 1; } i = i + 1; } fin * 10 + i"),
|
||||
"10"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_runs_finally_every_iteration() {
|
||||
assert_eq!(eval("var fin = 0; for (var i = 0; i < 5; i++) { try { continue; } finally { fin = fin + 1; } } fin"), "5");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; for (var i = 0; i < 5; i++) { try { continue; } finally { fin = fin + 1; } } fin"),
|
||||
"5"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; function f() { try { return 1; } finally { fin = 1; } } var r = f(); r * 10 + fin"), "11");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; function f() { try { return 1; } finally { fin = 1; } } var r = f(); r * 10 + fin"),
|
||||
"11"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -47,7 +56,10 @@ fn break_inside_finally_breaks_loop() {
|
||||
|
||||
#[test]
|
||||
fn throw_still_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; function g() { try { throw 1; } finally { fin = 1; } } try { g(); } catch (e) {} fin"), "1");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; function g() { try { throw 1; } finally { fin = 1; } } try { g(); } catch (e) {} fin"),
|
||||
"1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -101,12 +113,20 @@ fn break_in_finally_overrides_exception() {
|
||||
|
||||
#[test]
|
||||
fn continue_in_finally_overrides_break() {
|
||||
assert_eq!(eval("var log = 0; for (var i = 0; i < 3; i++) { try { break; } finally { log = log * 10 + i; continue; } } log"), "12");
|
||||
assert_eq!(
|
||||
eval(
|
||||
"var log = 0; for (var i = 0; i < 3; i++) { try { break; } finally { log = log * 10 + i; continue; } } log"
|
||||
),
|
||||
"12"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_in_finally_overrides_previous_exception() {
|
||||
assert_eq!(eval("var fin = 0; try { try { throw 1; } finally { throw 2; } } catch (e) { fin = e; } fin"), "2");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; try { try { throw 1; } finally { throw 2; } } catch (e) { fin = e; } fin"),
|
||||
"2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -143,7 +163,10 @@ fn continue_with_finally_conditionally() {
|
||||
|
||||
#[test]
|
||||
fn normal_finally_path_still_works() {
|
||||
assert_eq!(eval("var s = 0; for (var i = 0; i < 3; i++) { try { s = s + i; } finally { s = s + 10; } } s"), "33");
|
||||
assert_eq!(
|
||||
eval("var s = 0; for (var i = 0; i < 3; i++) { try { s = s + i; } finally { s = s + 10; } } s"),
|
||||
"33"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -202,7 +225,10 @@ fn return_in_try_catch_value_path() {
|
||||
|
||||
#[test]
|
||||
fn return_in_try_finally_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; function f(){ try{ return 1 } finally{ fin = 1 } } f() * 10 + fin"), "11");
|
||||
assert_eq!(
|
||||
eval("var fin = 0; function f(){ try{ return 1 } finally{ fin = 1 } } f() * 10 + fin"),
|
||||
"11"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user