feat: 支持 class extends 与 super 调用

This commit is contained in:
爱特每羊队
2026-06-09 09:51:34 +08:00
parent 85e78bd248
commit 5638638909
12 changed files with 478 additions and 32 deletions
+10
View File
@@ -70,6 +70,8 @@ pub(crate) struct CompileCtx {
/// Used by arrow functions to capture lexical `this` (D-01).
/// Initialized to 254 (conventional this register) at the top level.
pub(crate) enclosing_this_reg: u8,
pub(crate) in_derived_constructor: bool,
pub(crate) in_instance_method: bool,
}
impl CompileCtx {
@@ -89,6 +91,8 @@ impl CompileCtx {
builtin_reg_map: Vec::new(),
sub_modules: Vec::new(),
enclosing_this_reg: 254, // conventional this register at top level
in_derived_constructor: false,
in_instance_method: false,
}
}
@@ -297,6 +301,8 @@ impl Compiler {
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
ctx.in_derived_constructor = parent_ctx.in_derived_constructor;
ctx.in_instance_method = parent_ctx.in_instance_method;
// Inherit parent's global scope entries so previously-declared function names
// are visible from within the body.
@@ -402,6 +408,8 @@ impl Compiler {
captured_this_const_idx: 0,
function_name: None,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
})
}
@@ -460,6 +468,8 @@ impl Compiler {
captured_this_const_idx: 0,
function_name: None,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
})
}
}
+44 -6
View File
@@ -9,8 +9,18 @@ impl Compiler {
fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) {
ctx.alloc_reg(); // ctor_reg
ctx.alloc_reg(); // proto_reg
if let Some(super_class) = &class.super_class {
self.count_expression(super_class, ctx);
}
ctx.projected_pc += 2; // LOAD_CONST ctor + NEW_OBJECT proto
if class.super_class.is_some() {
ctx.alloc_reg(); // parent prototype key
ctx.alloc_reg(); // parent prototype value
ctx.alloc_reg(); // __proto__ key
ctx.projected_pc += 5; // LOAD_CONST + GET_PROP + LOAD_CONST + two SET_PROP
}
for element in &class.body.body {
let ClassElement::MethodDefinition(method) = element else {
continue;
@@ -21,7 +31,7 @@ impl Compiler {
MethodDefinitionKind::Method => {
ctx.alloc_reg(); // method_reg
ctx.alloc_reg(); // key_reg
ctx.projected_pc += 3; // LOAD_CONST method + LOAD_CONST key + SET_PROP
ctx.projected_pc += 4; // LOAD_CONST method + SET_HOME_OBJECT + LOAD_CONST key + SET_PROP
}
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {}
}
@@ -335,13 +345,32 @@ impl Compiler {
}
}
Expression::CallExpression(call) => {
if matches!(&call.callee, Expression::Super(_)) {
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
self.count_expression(expr, ctx);
}
}
ctx.alloc_reg();
ctx.projected_pc += 2; // SUPER_CALL + arg_count ext word
return;
}
match &call.callee {
Expression::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
ctx.alloc_reg();
ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext words
if matches!(&member.object, Expression::Super(_)) {
ctx.alloc_reg(); // this register
ctx.projected_pc += 1; // LOAD_VAR this
ctx.alloc_reg(); // key
ctx.projected_pc += 1; // LOAD_CONST key
ctx.alloc_reg(); // callee
ctx.projected_pc += 1; // SUPER_GET_PROP
} else {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
ctx.alloc_reg();
ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext words
}
}
_ => {
self.count_expression(&call.callee, ctx);
@@ -547,6 +576,15 @@ impl Compiler {
}
}
Expression::StaticMemberExpression(member) => {
if matches!(&member.object, Expression::Super(_)) {
ctx.alloc_reg(); // key
ctx.projected_pc += 1; // LOAD_CONST key
ctx.alloc_reg(); // this
ctx.projected_pc += 1; // LOAD_VAR this
ctx.alloc_reg(); // result
ctx.projected_pc += 1; // SUPER_GET_PROP
return;
}
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST key
+90 -11
View File
@@ -19,12 +19,9 @@ impl Compiler {
}
fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
if class.super_class.is_some() {
return Err("class extends not yet supported".into());
}
let mut constructor_method = None;
let mut instance_methods = Vec::new();
let is_derived = class.super_class.is_some();
for element in &class.body.body {
let ClassElement::MethodDefinition(method) = element else {
@@ -59,14 +56,33 @@ impl Compiler {
let ctor_reg = ctx.alloc_reg();
let proto_reg = ctx.alloc_reg();
let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default();
let super_reg = if let Some(super_expr) = &class.super_class {
Some(self.emit_expression(super_expr, ctx)?)
} else {
None
};
let saved_derived = ctx.in_derived_constructor;
ctx.in_derived_constructor = is_derived;
let mut ctor_module = if let Some(method) = constructor_method {
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
self.compile_function_body_with_bindings(&param_names, body_stmts, ctx, false, &self_binding)?
} else {
self.compile_function_body_with_bindings(&[], &[], ctx, false, &self_binding)?
let mut module = self.compile_function_body_with_bindings(&[], &[], ctx, false, &self_binding)?;
if is_derived {
module.bytecode.clear();
module.constants.clear();
module.n_registers = 1;
module.bytecode.push(opcode::encode(OpCode::SUPER_CALL, 0, 0, 0));
module.bytecode.push(0);
module.bytecode.push(opcode::encode(OpCode::RETURN, 0, 0, 0));
}
module
};
ctx.in_derived_constructor = saved_derived;
ctor_module.is_class_constructor = true;
ctor_module.is_derived_constructor = is_derived;
ctor_module.function_name = ctor_name.clone();
ctx.sub_modules.push(ctor_module);
@@ -74,16 +90,35 @@ impl Compiler {
ctx.emit_load_const(ctor_reg, ctor_idx);
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0));
if let Some(super_reg) = super_reg {
let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string()));
let parent_proto_key_reg = ctx.alloc_reg();
ctx.emit_load_const(parent_proto_key_reg, proto_key_idx);
let parent_proto_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::GET_PROP, super_reg, parent_proto_reg, parent_proto_key_reg));
let proto_link_idx = ctx.add_constant(Constant::String("__proto__".to_string()));
let proto_link_key_reg = ctx.alloc_reg();
ctx.emit_load_const(proto_link_key_reg, proto_link_idx);
ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, parent_proto_reg, proto_link_key_reg));
ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, super_reg, proto_link_key_reg));
}
for (method, method_name) in instance_methods {
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
let saved_method = ctx.in_instance_method;
ctx.in_instance_method = true;
let mut method_module =
self.compile_function_body_with_bindings(&param_names, body_stmts, ctx, false, &self_binding)?;
ctx.in_instance_method = saved_method;
method_module.function_name = Some(method_name.clone());
method_module.needs_home_object = true;
ctx.sub_modules.push(method_module);
let method_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32));
let method_reg = ctx.alloc_reg();
ctx.emit_load_const(method_reg, method_idx);
ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, proto_reg, 0));
let key_idx = ctx.add_constant(Constant::String(method_name));
let key_reg = ctx.alloc_reg();
@@ -753,6 +788,20 @@ impl Compiler {
}
}
Expression::StaticMemberExpression(member) => {
if matches!(&member.object, Expression::Super(_)) {
if !ctx.in_instance_method && !ctx.in_derived_constructor {
return Err("super property only supported in class methods".into());
}
let prop_name = member.property.name.as_str();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.emit_load_const(key_reg, idx);
let this_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0));
let result_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::SUPER_GET_PROP, result_reg, this_reg, key_reg));
return Ok(result_reg);
}
let obj_reg = self.emit_expression(&member.object, ctx)?;
let prop_name = member.property.name.as_str();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
@@ -1139,19 +1188,49 @@ impl Compiler {
}
Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx),
Expression::CallExpression(call) => {
if matches!(&call.callee, Expression::Super(_)) {
if !ctx.in_derived_constructor {
return Err("super() only supported in derived constructors".into());
}
let mut arg_regs = Vec::new();
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
arg_regs.push(self.emit_expression(expr, ctx)?);
}
}
let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] };
let result_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::SUPER_CALL, result_reg, first_arg_reg, 0));
ctx.emit(arg_regs.len() as u32);
return Ok(result_reg);
}
let (callee_reg, this_reg) = match &call.callee {
Expression::StaticMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let is_super_member = matches!(&member.object, Expression::Super(_));
let obj_reg = if is_super_member {
let this_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0));
this_reg
} else {
self.emit_expression(&member.object, ctx)?
};
let prop_name = member.property.name.as_str();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.emit_load_const(key_reg, idx);
let callee_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0));
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg));
ctx.emit(0);
ctx.emit(0);
ctx.emit(0);
if is_super_member {
if !ctx.in_instance_method && !ctx.in_derived_constructor {
return Err("super property only supported in class methods".into());
}
ctx.emit(opcode::encode(OpCode::SUPER_GET_PROP, callee_reg, obj_reg, key_reg));
} else {
ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0));
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg));
ctx.emit(0);
ctx.emit(0);
ctx.emit(0);
}
(callee_reg, obj_reg)
}
_ => {
+9
View File
@@ -123,6 +123,9 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
id.name.as_str().hash(h);
}
class.super_class.is_some().hash(h);
if let Some(super_class) = &class.super_class {
hash_expression(super_class, h);
}
for element in &class.body.body {
hash_class_element(element, h);
}
@@ -319,6 +322,9 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
id.name.as_str().hash(h);
}
class.super_class.is_some().hash(h);
if let Some(super_class) = &class.super_class {
hash_expression(super_class, h);
}
for element in &class.body.body {
hash_class_element(element, h);
}
@@ -333,6 +339,9 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
}
}
}
Expression::Super(_) => {
19u8.hash(h);
}
Expression::RegExpLiteral(lit) => {
if let Some(raw) = &lit.raw {
raw.to_string().hash(h);
+9
View File
@@ -34,6 +34,11 @@ pub struct CompiledModule {
/// True when this bytecode function is a class constructor.
/// Ordinary CALL must reject it, while NEW_EXPRESSION may construct through it.
pub is_class_constructor: bool,
/// True when this class constructor has an `extends` clause.
/// `this` stays uninitialized until SUPER_CALL completes.
pub is_derived_constructor: bool,
/// True for prototype methods whose function object needs a runtime home_object.
pub needs_home_object: bool,
}
impl CompiledModule {
@@ -50,6 +55,8 @@ impl CompiledModule {
captured_this_const_idx: 0,
function_name: None,
is_class_constructor: false,
is_derived_constructor: false,
needs_home_object: false,
}
}
}
@@ -74,6 +81,8 @@ impl Clone for CompiledModule {
captured_this_const_idx: self.captured_this_const_idx,
function_name: self.function_name.clone(),
is_class_constructor: self.is_class_constructor,
is_derived_constructor: self.is_derived_constructor,
needs_home_object: self.needs_home_object,
}
}
}
+12
View File
@@ -84,6 +84,10 @@ pub enum OpCode {
RETURN = 0x41,
CALL_NATIVE = 0x42,
NEW_EXPRESSION = 0x43,
SUPER_CALL = 0x44,
SUPER_GET_PROP = 0x45,
SUPER_STATIC_GET_PROP = 0x46,
SET_HOME_OBJECT = 0x47,
// ── Object Property (0x50-0x5F) ──
IC_GET_PROP = 0x50,
@@ -217,6 +221,10 @@ impl TryFrom<u8> for OpCode {
0x41 => Ok(OpCode::RETURN),
0x42 => Ok(OpCode::CALL_NATIVE),
0x43 => Ok(OpCode::NEW_EXPRESSION),
0x44 => Ok(OpCode::SUPER_CALL),
0x45 => Ok(OpCode::SUPER_GET_PROP),
0x46 => Ok(OpCode::SUPER_STATIC_GET_PROP),
0x47 => Ok(OpCode::SET_HOME_OBJECT),
0x50 => Ok(OpCode::IC_GET_PROP),
0x51 => Ok(OpCode::IC_SET_PROP),
0x52 => Ok(OpCode::GET_PROP),
@@ -311,6 +319,10 @@ impl fmt::Display for OpCode {
OpCode::RETURN => "RETURN",
OpCode::CALL_NATIVE => "CALL_NATIVE",
OpCode::NEW_EXPRESSION => "NEW_EXPRESSION",
OpCode::SUPER_CALL => "SUPER_CALL",
OpCode::SUPER_GET_PROP => "SUPER_GET_PROP",
OpCode::SUPER_STATIC_GET_PROP => "SUPER_STATIC_GET_PROP",
OpCode::SET_HOME_OBJECT => "SET_HOME_OBJECT",
OpCode::IC_GET_PROP => "IC_GET_PROP",
OpCode::IC_SET_PROP => "IC_SET_PROP",
OpCode::GET_PROP => "GET_PROP",
+27 -2
View File
@@ -37,9 +37,10 @@ impl PropIndex for i32 {
}
}
/// Layout (56B):
/// Layout:
/// header: u32 bits
/// [0:23] shape_id
/// [26] is_derived_constructor
/// [27] is_class_constructor
/// [28] is_arrow
/// [29] is_array
@@ -52,6 +53,7 @@ impl PropIndex for i32 {
/// native_fn: u64 (8 bytes, 0 = None sentinel)
/// sub_module_index: u32 (4 bytes + 4 pad, index into CompiledModule.sub_modules)
/// captured_this: JsValue (8 bytes, lexical this for arrow functions)
/// home_object: JsValue (8 bytes, [[HomeObject]] for super lookup)
#[repr(C)]
pub struct JsObject {
header: u32,
@@ -65,6 +67,7 @@ pub struct JsObject {
sub_module_index: u32,
_pad3: [u8; 4],
captured_this: JsValue,
home_object: JsValue,
}
impl JsObject {
@@ -81,6 +84,7 @@ impl JsObject {
sub_module_index: 0,
_pad3: [0; 4],
captured_this: JsValue::undefined(),
home_object: JsValue::undefined(),
}
}
@@ -97,6 +101,7 @@ impl JsObject {
sub_module_index: 0,
_pad3: [0; 4],
captured_this: JsValue::undefined(),
home_object: JsValue::undefined(),
};
let vec = Box::new(vec![Box::new(JsValue::undefined()); n_elements]);
obj.hash_props = Box::into_raw(vec) as *mut u8;
@@ -333,6 +338,26 @@ impl JsObject {
self.header &= !(1 << 27);
}
}
pub fn is_derived_constructor(&self) -> bool {
(self.header >> 26) & 1 != 0
}
pub fn set_derived_constructor(&mut self, v: bool) {
if v {
self.header |= 1 << 26;
} else {
self.header &= !(1 << 26);
}
}
pub fn home_object(&self) -> JsValue {
self.home_object
}
pub fn set_home_object(&mut self, v: JsValue) {
self.home_object = v;
}
}
#[cfg(test)]
@@ -343,7 +368,7 @@ mod tests {
#[test]
fn object_size_bounds() {
let sz = std::mem::size_of::<JsObject>();
assert!(sz >= 40 && sz <= 56, "JsObject layout mismatch - expected 40-56B, got {sz}B");
assert!(sz >= 40 && sz <= 72, "JsObject layout mismatch - expected 40-72B, got {sz}B");
}
#[test]
+2
View File
@@ -170,8 +170,10 @@ mod tests {
saved_regs: vec![JsValue::undefined()].into_boxed_slice(),
saved_this: JsValue::undefined(),
saved_new_target: JsValue::undefined(),
callee: JsValue::undefined(),
construct_result_reg: None,
constructed_this: None,
is_derived_constructor: false,
});
let stack = error_stack_getter(&mut vm, &[0]).unwrap();
+16 -1
View File
@@ -90,8 +90,15 @@ impl Vm {
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
self.pc += 3;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
let ext0 = self.bytecode[self.pc];
let ext1 = self.bytecode[self.pc + 1];
let ext2 = self.bytecode[self.pc + 2];
@@ -150,6 +157,10 @@ impl Vm {
}
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
return Ok(());
}
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, self.regs[a]);
} else {
@@ -181,6 +192,10 @@ impl Vm {
}
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
if self.kernel.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
return Ok(());
}
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, self.regs[b]);
} else {
+205 -12
View File
@@ -50,8 +50,10 @@ pub struct CallFrame {
pub saved_regs: Box<[JsValue]>,
pub saved_this: JsValue,
pub saved_new_target: JsValue,
pub callee: JsValue,
pub construct_result_reg: Option<u8>,
pub constructed_this: Option<JsValue>,
pub is_derived_constructor: bool,
}
pub struct ForInIter<'bump> {
@@ -246,13 +248,18 @@ impl Vm {
/// Create a function JsObject for a BytecodeFunc constant.
/// When `is_arrow` is true, captures the current `this` (regs[254])
/// for lexical this binding at call time (D-01).
fn create_function_object(&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool) -> JsValue {
fn create_function_object(
&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
needs_home_object: bool,
) -> JsValue {
let func_proto_ptr = self.kernel.builtin_world().function_proto.as_ptr() as *mut JsObject;
let proto_val = JsValue::from_js_object(func_proto_ptr);
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val);
obj.set_function(true);
obj.set_sub_module_index(sub_idx);
obj.set_class_constructor(is_class_constructor);
obj.set_derived_constructor(is_derived_constructor);
let _ = needs_home_object;
if is_arrow {
obj.set_arrow(true);
// Capture lexical `this` from the enclosing scope (regs[254]).
@@ -293,13 +300,25 @@ impl Vm {
Constant::Undefined => Ok(JsValue::undefined()),
Constant::BytecodeFunc(idx) => {
let sub_idx = *idx as usize;
let (is_arrow, is_class_constructor) = if sub_idx > 0 && sub_idx <= self.sub_modules.len() {
let sub_module = &self.sub_modules[sub_idx - 1];
(sub_module.is_arrow, sub_module.is_class_constructor)
} else {
(false, false)
};
Ok(self.create_function_object(*idx, is_arrow, is_class_constructor))
let (is_arrow, is_class_constructor, is_derived_constructor, needs_home_object) =
if sub_idx > 0 && sub_idx <= self.sub_modules.len() {
let sub_module = &self.sub_modules[sub_idx - 1];
(
sub_module.is_arrow,
sub_module.is_class_constructor,
sub_module.is_derived_constructor,
sub_module.needs_home_object,
)
} else {
(false, false, false, false)
};
Ok(self.create_function_object(
*idx,
is_arrow,
is_class_constructor,
is_derived_constructor,
needs_home_object,
))
}
Constant::RegExp(pattern, flags) => {
let pat_si = self.kernel.string_forge().intern(pattern).0;
@@ -697,6 +716,12 @@ impl Vm {
}
OpCode::LOAD_VAR => {
if a == 254
&& self.frames.last().map(|frame| frame.is_derived_constructor).unwrap_or(false)
&& self.regs[a].is_undefined()
{
throw_err!(self, ReferenceError, "must call super constructor before using 'this'");
}
self.regs[rd] = self.regs[a];
}
@@ -790,8 +815,10 @@ impl Vm {
saved_regs,
saved_this,
saved_new_target,
callee,
construct_result_reg: None,
constructed_this: None,
is_derived_constructor: false,
});
self.bytecode = sub_bytecode;
@@ -948,7 +975,11 @@ impl Vm {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.regs[sub_param_base + i] = self.regs[src_reg];
}
self.regs[254] = new_obj_val;
self.regs[254] = if ctor_obj.is_derived_constructor() {
JsValue::undefined()
} else {
new_obj_val
};
self.regs[255] = constructor;
let converted_sub_constants = self.convert_constants(&sub_constants)?;
@@ -967,8 +998,10 @@ impl Vm {
saved_regs,
saved_this,
saved_new_target,
callee: constructor,
construct_result_reg: Some(rd as u8),
constructed_this: Some(new_obj_val),
is_derived_constructor: ctor_obj.is_derived_constructor(),
});
self.bytecode = sub_bytecode;
@@ -999,15 +1032,173 @@ impl Vm {
}
}
OpCode::SUPER_CALL => {
let first_arg_reg = a as u8;
let ext = self.bytecode[self.pc];
self.pc += 1;
let arg_count = (ext & 0xFF) as usize;
let Some(frame) = self.frames.last() else {
throw_err!(self, ReferenceError, "super() used outside class constructor");
};
if !frame.is_derived_constructor {
throw_err!(self, ReferenceError, "super() used outside derived constructor");
}
if !self.regs[254].is_undefined() {
throw_err!(self, ReferenceError, "super() called more than once");
}
let Some(derived_this) = frame.constructed_this else {
throw_err!(self, ReferenceError, "super() without derived this");
};
let new_target = self.regs[255];
if !new_target.is_object() {
throw_err!(self, TypeError, "super() new.target is not an object");
}
let new_target_obj = unsafe { &*new_target.as_js_object_ptr() };
let super_ctor = new_target_obj.proto();
if !super_ctor.is_object() {
throw_err!(self, TypeError, "super constructor is not an object");
}
let super_obj = unsafe { &*super_ctor.as_js_object_ptr() };
if !super_obj.is_function() {
throw_err!(self, TypeError, "super constructor is not a function");
}
if super_obj.native_fn().is_some() {
self.regs[253] = derived_this;
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253);
let func: NativeFn = unsafe { std::mem::transmute(super_obj.native_fn().unwrap()) };
match func(self, &args_buf[..len]) {
Ok(val) => {
self.regs[254] = if val.is_object() { val } else { derived_this };
self.regs[rd] = self.regs[254];
}
Err(err_val) => {
self.exception_value = Some(err_val);
self.pending_error_kind = Some("Error");
match self.unwind() {
Ok(()) => continue,
Err(e) => return Err(e),
}
}
}
} else if super_obj.sub_module_index() > 0 {
let sub_idx = super_obj.sub_module_index() as usize - 1;
if sub_idx >= self.sub_modules.len() {
return Err(format!(
"SUPER_CALL: sub_module_index {} out of bounds (max {})",
sub_idx,
self.sub_modules.len()
));
}
if self.frames.len() >= self.kernel.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
}
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
let sub_constants = self.sub_modules[sub_idx].constants.clone();
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
let caller_reg_limit = self.active_reg_limit.max(1);
let saved_regs = self.regs[..caller_reg_limit as usize].to_vec().into_boxed_slice();
let saved_this = self.regs[254];
let saved_new_target = self.regs[255];
for i in 0..sub_n_args {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.regs[sub_param_base + i] = self.regs[src_reg];
}
self.regs[254] = derived_this;
self.regs[255] = new_target;
let converted_sub_constants = self.convert_constants(&sub_constants)?;
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
self.saved_constants_stack.push(std::mem::take(&mut self.constants));
self.frames.push(CallFrame {
return_addr: self.pc,
function_name: self.sub_modules[sub_idx]
.function_name
.as_deref()
.map(|name| self.kernel.string_forge().intern(name).0)
.unwrap_or(0),
caller_reg_limit,
saved_regs,
saved_this,
saved_new_target,
callee: super_ctor,
construct_result_reg: Some(254),
constructed_this: Some(derived_this),
is_derived_constructor: super_obj.is_derived_constructor(),
});
self.bytecode = sub_bytecode;
self.constants = converted_sub_constants;
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
let si = self.kernel.string_forge().intern(name.as_str()).0;
let global = self.kernel.global_object();
if let Some(pos) = self.kernel.shape_forge().lookup_position(global.shape_id(), si) {
self.regs[*reg as usize] = global.get_prop_at(pos);
}
}
self.active_reg_limit = sub_n_registers.max(1);
self.pc = 0;
continue;
} else {
throw_err!(self, TypeError, "super constructor is not callable");
}
}
OpCode::SUPER_GET_PROP | OpCode::SUPER_STATIC_GET_PROP => {
let key_val = self.regs[b];
let prop_name_si = self.property_key_si(key_val);
let Some(frame) = self.frames.last() else {
throw_err!(self, ReferenceError, "super property used outside function");
};
if !frame.callee.is_object() {
throw_err!(self, ReferenceError, "super property has no home object");
}
let callee_obj = unsafe { &*frame.callee.as_js_object_ptr() };
let home_object = callee_obj.home_object();
if !home_object.is_object() {
throw_err!(self, ReferenceError, "super property has no home object");
}
let home_obj = unsafe { &*home_object.as_js_object_ptr() };
let super_base = home_obj.proto();
if !super_base.is_object() {
self.regs[rd] = JsValue::undefined();
} else {
let super_obj = unsafe { &*super_base.as_js_object_ptr() };
self.regs[rd] = self.resolve_property(super_obj, prop_name_si).unwrap_or(JsValue::undefined());
}
}
OpCode::SET_HOME_OBJECT => {
let func_val = self.regs[rd];
let home_val = self.regs[a];
if !func_val.is_object() || !home_val.is_object() {
throw_err!(self, TypeError, "SET_HOME_OBJECT expects function and object");
}
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
if !func_obj.is_function() {
throw_err!(self, TypeError, "SET_HOME_OBJECT target is not a function");
}
func_obj.set_home_object(home_val);
}
OpCode::RETURN => {
let result = self.regs[rd];
if let Some(frame) = self.frames.pop() {
let construct_result_reg = frame.construct_result_reg;
let constructed_this = frame.constructed_this;
let is_derived_constructor = frame.is_derived_constructor;
let callee_this = self.regs[254];
self.restore_frame(frame);
if let (Some(target_reg), Some(constructed_this)) =
(construct_result_reg, constructed_this)
{
if let (Some(target_reg), Some(constructed_this)) = (construct_result_reg, constructed_this) {
if is_derived_constructor && result.is_undefined() && callee_this.is_undefined() {
throw_err!(self, ReferenceError, "derived constructor must call super()");
}
self.regs[target_reg as usize] = if result.is_object() { result } else { constructed_this };
self.regs[0] = self.regs[target_reg as usize];
} else {
@@ -1419,8 +1610,10 @@ mod tests {
saved_regs: vec![JsValue::undefined()].into_boxed_slice(),
saved_this: JsValue::undefined(),
saved_new_target: JsValue::undefined(),
callee: JsValue::undefined(),
construct_result_reg: None,
constructed_this: None,
is_derived_constructor: false,
});
vm.for_in_iters.push(std::ptr::dangling_mut::<u8>());
vm.for_of_iters.push(std::ptr::dangling_mut::<u8>());
+45
View File
@@ -9,6 +9,11 @@ fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
vm.run(&module)
}
fn assert_num(value: JsValue, expected: f64) {
let actual = if value.is_int() { value.as_int() as f64 } else { value.as_double() };
assert!((actual - expected).abs() < 0.0001, "expected {expected}, got {actual}");
}
#[test]
fn class_declaration_is_function_value() {
let mut vm = Vm::new();
@@ -68,3 +73,43 @@ fn class_constructor_primitive_return_preserves_instance() {
let result = eval(&mut vm, "class A { constructor() { this.x = 1; return 5; } } new A().x").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn derived_constructor_super_initializes_parent_state() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"class A { constructor(){ this.x = 1 } } class B extends A { constructor(){ super(); this.y = 2 } } let b = new B(); b.x + b.y",
)
.unwrap();
assert_num(result, 3.0);
}
#[test]
fn derived_default_constructor_delegates_to_parent() {
let mut vm = Vm::new();
let result = eval(&mut vm, "class A { constructor(){ this.x = 1 } } class B extends A {} new B().x").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn derived_constructor_this_before_super_throws_reference_error() {
let mut vm = Vm::new();
let err = eval(
&mut vm,
"class A { constructor(){ this.x = 1 } } class B extends A { constructor(){ this.x = 2; super(); } } new B()",
)
.unwrap_err();
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
}
#[test]
fn derived_method_super_call_uses_current_receiver() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"class A { m(){ return this.x } } class B extends A { constructor(){ super(); this.x = 2 } m(){ return super.m() + 1 } } new B().m()",
)
.unwrap();
assert_num(result, 3.0);
}
@@ -161,6 +161,15 @@ fn object_instanceof_object() {
assert_bool!(eval("({}) instanceof Object"), true, "({}) instanceof Object");
}
#[test]
fn derived_class_instanceof_parent() {
assert_bool!(
eval("class A {} class B extends A {} let b = new B(); b instanceof A"),
true,
"derived class instanceof parent"
);
}
// -- instanceof for native types should NOT respond to their own false positives --
#[test]
fn number_not_instanceof_array() {