feat: class 私有成员语义(字段/方法/访问器 + brand 检查 + 契约修复)
This commit is contained in:
@@ -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