fix: 对齐 class 体 counter/emitter 指令条数,修私有方法、计算键、派生构造器字段注入位置的跳转漂移
This commit is contained in:
@@ -160,6 +160,10 @@ pub(crate) struct CompileCtx {
|
||||
pub(crate) static_block_this_reg: Option<u8>,
|
||||
pub(crate) after_super_insert: Option<Vec<opcode::Instr>>,
|
||||
pub(crate) after_super_inserted: bool,
|
||||
/// Count-pass mirror of `after_super_insert`: the instruction count of a derived
|
||||
/// constructor's instance-field code, added at the super() call site during body
|
||||
/// counting so projected_pc matches where the emit pass splices the field bytecode.
|
||||
pub(crate) after_super_count_words: Option<usize>,
|
||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
||||
/// Checked after each emit phase to produce a compile error rather than silent corruption.
|
||||
pub(crate) reg_overflow: bool,
|
||||
@@ -233,6 +237,7 @@ impl CompileCtx {
|
||||
static_block_this_reg: None,
|
||||
after_super_insert: None,
|
||||
after_super_inserted: false,
|
||||
after_super_count_words: None,
|
||||
reg_overflow: false,
|
||||
const_overflow: false,
|
||||
jump_overflow: false,
|
||||
@@ -731,20 +736,25 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
// Count pass
|
||||
if !fields_after_super {
|
||||
if let Some(count) = count_fields.as_mut() {
|
||||
// Count pass. For derived constructors the emit pass allocates/counts the instance
|
||||
// fields up front but splices the field bytecode into the body right after SUPER_CALL.
|
||||
// Mirror that: count the fields (allocating the same registers/constants), capture
|
||||
// their instruction count, undo the position here, and re-add it at the super() call
|
||||
// site during body counting so labels after super() line up with the emit pass.
|
||||
if let Some(count) = count_fields.as_mut() {
|
||||
if fields_after_super {
|
||||
let before = ctx.projected_pc;
|
||||
count(self, &mut ctx);
|
||||
let field_words = ctx.projected_pc - before;
|
||||
ctx.projected_pc = before;
|
||||
ctx.after_super_count_words = Some(field_words);
|
||||
} else {
|
||||
count(self, &mut ctx);
|
||||
}
|
||||
}
|
||||
for stmt in body_stmts {
|
||||
self.count_statement(stmt, &mut ctx);
|
||||
}
|
||||
if fields_after_super {
|
||||
if let Some(count) = count_fields.as_mut() {
|
||||
count(self, &mut ctx);
|
||||
}
|
||||
}
|
||||
ctx.max_regs = ctx.max_regs.max(1);
|
||||
ctx.reg_overflow = false;
|
||||
ctx.reset_regs();
|
||||
|
||||
@@ -13,6 +13,10 @@ impl Compiler {
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.count_call_instr_with_arg_ext(); // SUPER_CALL + arg_count ext word
|
||||
// Mirror emit: derived-ctor instance fields are injected right after super().
|
||||
if let Some(words) = ctx.after_super_count_words.take() {
|
||||
ctx.projected_pc += words;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,15 +35,19 @@ impl Compiler {
|
||||
continue;
|
||||
}
|
||||
if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) {
|
||||
if method.r#static {
|
||||
ctx.count_load_const(); // private id
|
||||
ctx.count_load_const(); // method
|
||||
ctx.count_instr(); // INIT_PRIVATE
|
||||
}
|
||||
// Mirror emit_private_method_init for static AND non-static private
|
||||
// methods: private-id LOAD_CONST + CREATE_CLOSURE + SET_HOME_OBJECT +
|
||||
// INIT_PRIVATE.
|
||||
ctx.count_load_const(); // private id reg
|
||||
ctx.count_load_const(); // method closure (CREATE_CLOSURE)
|
||||
ctx.count_instr(); // SET_HOME_OBJECT
|
||||
ctx.count_instr(); // INIT_PRIVATE
|
||||
continue;
|
||||
}
|
||||
ctx.count_load_const(); // key_reg
|
||||
ctx.count_load_const(); // method_reg
|
||||
// Computed method key emits the full key expression; a plain key is one
|
||||
// LOAD_CONST. Mirror emit_class_key_reg via the shared count_class_key.
|
||||
self.count_class_key(&method.key, method.computed, ctx);
|
||||
ctx.count_load_const(); // method closure (CREATE_CLOSURE)
|
||||
ctx.count_instr(); // SET_HOME_OBJECT
|
||||
match method.kind {
|
||||
MethodDefinitionKind::Method => {
|
||||
@@ -59,7 +63,9 @@ impl Compiler {
|
||||
ClassElement::PropertyDefinition(prop) => {
|
||||
let prop = prop.as_ref();
|
||||
if prop.r#static {
|
||||
ctx.count_load_const(); // key_reg
|
||||
// Computed static field key emits the full key expression; mirror
|
||||
// emit_public_field_init via the shared count_class_key.
|
||||
self.count_class_key(&prop.key, prop.computed, ctx);
|
||||
if let Some(value) = &prop.value {
|
||||
self.count_expression(value, ctx);
|
||||
} else {
|
||||
|
||||
@@ -247,7 +247,7 @@ impl Compiler {
|
||||
self.emit_expression(key.to_expression(), ctx)
|
||||
}
|
||||
|
||||
fn count_class_key(&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx) {
|
||||
pub(crate) fn count_class_key(&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx) {
|
||||
if computed {
|
||||
self.count_expression(key.to_expression(), ctx);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// A class body must not drift the instruction count of code that follows it. Each repro
|
||||
// places a bounded loop after the class so a wrong count corrupts the loop's jump target.
|
||||
|
||||
#[test]
|
||||
fn non_static_private_method_then_loop() {
|
||||
assert_eq!(eval("class C{#m(){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_private_method_then_loop() {
|
||||
assert_eq!(eval("class C{static #m(){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Multi-word computed key — a single-identifier key happens to be one word and would not
|
||||
// expose the drift.
|
||||
#[test]
|
||||
fn computed_method_key_then_loop() {
|
||||
assert_eq!(eval("class C{[\"x\"+\"y\"](){return 1;}} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computed_static_field_key_then_loop() {
|
||||
assert_eq!(eval("class C{static [\"a\"+\"b\"]=1;} var c=0; while(c<3){c=c+1;} c"), "3");
|
||||
}
|
||||
|
||||
// Derived constructor with an instance field and a loop after super(): the field code is
|
||||
// injected after SUPER_CALL, so the loop's jump target must account for it.
|
||||
#[test]
|
||||
fn derived_ctor_field_then_post_super_loop() {
|
||||
assert_eq!(
|
||||
eval("class A{} class B extends A{ x=2; constructor(){super(); this.r=0; while(this.r<3){this.r=this.r+1;}}} new B().r"),
|
||||
"3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_ctor_field_value_initialized() {
|
||||
assert_eq!(eval("class A{} class B extends A{ x=5; constructor(){super();} } new B().x"), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_ctor_statement_before_super() {
|
||||
assert_eq!(
|
||||
eval("class A{constructor(){this.a=1;}} class B extends A{ y=2; constructor(){var z=0; super(); while(z<3){z=z+1;} this.z=z;} } new B().z"),
|
||||
"3"
|
||||
);
|
||||
}
|
||||
|
||||
// Non-derived class with an instance field and a ctor loop stays correct (unchanged path).
|
||||
#[test]
|
||||
fn non_derived_ctor_field_then_loop() {
|
||||
assert_eq!(
|
||||
eval("class C{ x=1; constructor(){this.r=0; while(this.r<3){this.r=this.r+1;}} } new C().r"),
|
||||
"3"
|
||||
);
|
||||
}
|
||||
|
||||
// Behavioral regressions: methods/getters/private calls still work.
|
||||
#[test]
|
||||
fn plain_method_call_regression() {
|
||||
assert_eq!(eval("class C{m(){return 9;}} new C().m()"), "9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_method_call_regression() {
|
||||
assert_eq!(eval("class C{#m(){return 7;} run(){return this.#m();}} new C().run()"), "7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn getter_regression() {
|
||||
assert_eq!(eval("class C{get x(){return 5;}} new C().x"), "5");
|
||||
}
|
||||
Reference in New Issue
Block a user