From 5061ad7a4445850eb87714111f2c40ad4130ce63 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 14 Aug 2026 11:41:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=AE=A1=E7=AE=97=E6=88=90=E5=91=98?= =?UTF-8?q?=E5=A4=8D=E5=90=88=E8=B5=8B=E5=80=BC=E4=BF=9D=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E6=B1=82=E5=80=BC=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/compile_expr_tests.rs | 6 ++- crates/oxide_emit/src/expr/assignment.rs | 49 ++++++++++++------- crates/oxide_vm/tests/object_tests.rs | 48 ++++++++++++++++++ 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 806666c7..1ccdf21b 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -737,9 +737,13 @@ fn compile_computed_member_numeric_and_index_keys_stay_dynamic() { #[test] fn compile_computed_member_compound_and_update_fold_to_ic() { + // 复合赋值折叠为 ic_get → op → ic_set 三指令序列(保规范求值序),不再用单指令 RMW。 let module = compile_source("let obj = { a: 1 }; obj[\"a\"] += 1; obj[\"b\"]++;"); let ops = scan_opcodes(&module); - assert!(ops.contains(&OpCode::COMPOUND_MEMBER_ADD), "obj[\"a\"] += 应折叠为 COMPOUND_MEMBER_ADD"); + assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"a\"] += 应先 ic_get 读属性"); + assert!(ops.contains(&OpCode::IC_SET_PROP), "obj[\"a\"] += 应 ic_set 写回"); + assert!(ops.contains(&OpCode::ADD), "obj[\"a\"] += 应发 ADD 运算"); + assert!(!ops.contains(&OpCode::COMPOUND_MEMBER_ADD), "复合赋值不应发 COMPOUND_MEMBER_ADD 单指令"); assert!(ops.contains(&OpCode::MEMBER_INC), "obj[\"b\"]++ 应折叠为 MEMBER_INC"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键复合赋值不应发 GET_PROP_DYNAMIC"); } diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index ede6734c..bf9c2fb1 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -167,31 +167,46 @@ impl Emitter { return Ok(result_reg); } let obj_reg = self.emit_expression(&member.object, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; let idx = ctx.add_constant(Constant::String(key)); let key_reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx)); if assign.operator != AssignmentOperator::Assign { - let obj = Operand::Reg(obj_reg); - let val = Operand::Reg(val_reg); - let key = Operand::Reg(key_reg); - match assign.operator { - AssignmentOperator::Addition => ctx.inst(Inst::compound_member_add(obj, val, key)), - AssignmentOperator::Subtraction => ctx.inst(Inst::compound_member_sub(obj, val, key)), - AssignmentOperator::Multiplication => ctx.inst(Inst::compound_member_mul(obj, val, key)), - AssignmentOperator::Division => ctx.inst(Inst::compound_member_div(obj, val, key)), - AssignmentOperator::Remainder => ctx.inst(Inst::compound_member_mod(obj, val, key)), - AssignmentOperator::Exponential => ctx.inst(Inst::compound_member_exp(obj, val, key)), - AssignmentOperator::BitwiseAnd => ctx.inst(Inst::compound_member_bit_and(obj, val, key)), - AssignmentOperator::BitwiseOR => ctx.inst(Inst::compound_member_bit_or(obj, val, key)), - AssignmentOperator::BitwiseXOR => ctx.inst(Inst::compound_member_bit_xor(obj, val, key)), - AssignmentOperator::ShiftLeft => ctx.inst(Inst::compound_member_shl(obj, val, key)), - AssignmentOperator::ShiftRight => ctx.inst(Inst::compound_member_shr(obj, val, key)), - AssignmentOperator::ShiftRightZeroFill => ctx.inst(Inst::compound_member_ushr(obj, val, key)), + // 复合赋值保规范求值序:先读属性(ic_get)再求值 RHS——RHS 副作用 + // 可能改写同一属性,后求值才能读到旧值。 + let val_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + Operand::Reg(val_reg), + Operand::Reg(obj_reg), + Operand::None, + )); + ctx.inst(Inst::ic_get(Operand::Reg(val_reg), Operand::Reg(key_reg))); + let rhs = self.emit_expression(&assign.right, ctx)?; + let op = match assign.operator { + AssignmentOperator::Addition => OpCode::ADD, + AssignmentOperator::Subtraction => OpCode::SUB, + AssignmentOperator::Multiplication => OpCode::MUL, + AssignmentOperator::Division => OpCode::DIV, + AssignmentOperator::Remainder => OpCode::MOD, + AssignmentOperator::Exponential => OpCode::COMPOUND_EXP, + AssignmentOperator::BitwiseAnd => OpCode::BIT_AND, + AssignmentOperator::BitwiseOR => OpCode::BIT_OR, + AssignmentOperator::BitwiseXOR => OpCode::BIT_XOR, + AssignmentOperator::ShiftLeft => OpCode::SHL, + AssignmentOperator::ShiftRight => OpCode::SHR, + AssignmentOperator::ShiftRightZeroFill => OpCode::USHR, _ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)), + }; + if assign.operator == AssignmentOperator::Exponential { + // 指数无独立二元指令,COMPOUND_EXP 语义 rd=rd^a:rhs 放 a 槽。 + ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None)); + } else { + ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(rhs))); } + ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg))); Ok(val_reg) } else { + let val_reg = self.emit_expression(&assign.right, ctx)?; ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg))); Ok(val_reg) } diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs index 90f74796..56b2780d 100644 --- a/crates/oxide_vm/tests/object_tests.rs +++ b/crates/oxide_vm/tests/object_tests.rs @@ -65,6 +65,54 @@ fn eval_computed_const_key_dynamic_keys_stay_dynamic() { assert_eq!(eval("var arr=[1,2]; arr[0]=7, arr[0]"), "7"); } +#[test] +fn eval_computed_compound_assign_preserves_read_before_rhs() { + // 复合赋值保规范求值序:先读属性再求值 RHS,RHS 副作用改写属性不影响被加的旧值。 + assert_eq!(eval("var o={a:1}; function f(){o.a=100; return 1;} o[\"a\"]+=f()"), "2"); + assert_eq!(eval("var o={a:5}; o[\"a\"]-=1, o.a"), "4"); + assert_eq!(eval("var o={a:5}; o[\"a\"]*=2, o.a"), "10"); + assert_eq!(eval("var o={a:5}; o[\"a\"]/=2, o.a"), "2.5"); + assert_eq!(eval("var o={a:5}; o[\"a\"]%=3, o.a"), "2"); + assert_eq!(eval("var o={a:5}; o[\"a\"]**=2, o.a"), "25"); + assert_eq!(eval("var o={a:5}; o[\"a\"]&=3, o.a"), "1"); + assert_eq!(eval("var o={a:5}; o[\"a\"]|=8, o.a"), "13"); + assert_eq!(eval("var o={a:5}; o[\"a\"]^=1, o.a"), "4"); + assert_eq!(eval("var o={a:5}; o[\"a\"]<<=1, o.a"), "10"); + assert_eq!(eval("var o={a:5}; o[\"a\"]>>=1, o.a"), "2"); + assert_eq!(eval("var o={a:5}; o[\"a\"]>>>=1, o.a"), "2"); + // 逻辑赋值同为读→短路→RHS→写:truthy 时写 RHS 值,falsy 短路 RHS 不求值。 + assert_eq!(eval("var o={a:5}; function g(){o.a=7; return 2;} o[\"a\"]&&=g(), o.a"), "2"); + assert_eq!(eval("var o={a:0}; o[\"a\"]&&=99, o.a"), "0"); + assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]||=(hit=1), hit"), "0"); + assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]??=(hit=1), hit"), "0"); + assert_eq!(eval("var p={a:null}; p[\"a\"]??=5, p.a"), "5"); + assert_eq!(eval("var q={a:0}; q[\"a\"]||=9, q.a"), "9"); + // 静态成员复合赋值行为不变(pre-existing 求值序偏差不在本次修复范围)。 + assert_eq!(eval("var o={a:5}; o.a += 1, o.a"), "6"); +} + +#[test] +fn eval_computed_compound_assign_getter_order() { + // getter 场景:先触发 getter 读取,再求值 RHS。 + assert_eq!(eval("var g={get a(){return 2}, set a(v){}}; g[\"a\"] += 1"), "3"); + // 求值序严格验证:RHS 观察到 getter 已先行(seen=1),否则 RHS 取 100。 + // 结果 v*10+seen:规范序 101,RHS 先行 1001。 + assert_eq!( + eval("var seen=0, v=0; var g={get a(){seen=1; return v}, set a(x){v=x}}; g[\"a\"] += (seen===0?100:10); v*10+seen"), + "101" + ); +} + +#[test] +fn eval_computed_compound_assign_read_error_skips_rhs() { + // 读取抛错时 RHS 不求值,其副作用不泄漏。 + assert_eq!(eval("var h={get a(){throw 1}}; try{h[\"a\"]+=1}catch(e){e}"), "1"); + assert_eq!( + eval("var side=0; var h={get a(){throw 1}}; try{h[\"a\"]+=(side=99)}catch(e){} side"), + "0" + ); +} + #[test] fn eval_computed_const_key_method_call_keeps_this() { // 方法调用接收者折叠:this 绑定保留。