Browse Source

Fix order dependent execution in assignment. (#2378)

Fixes #1917 (fixes the code example given with  the hashes)

Fixes the order dependent execution of assignment, so the following code works correctly:
```javascript
function f(x) { console.log(x) } // used to check the order of execution
let a = [[]]

(f(1), a)[(f(2), 0)][(f(3), 0)] = (f(4), 123) // 🤮
```
Prints out:
```bash
1
2
3
4
123
```
As expected

This introduces some opcodes:
 - ~~`Swap3`:  currently used only to keep some previous code working that needs refactoring.~~
 - ~~`RotateRight n`: Rotates the `n` top values from the top of the stack to the right by `1`.~~ Already added by #2390 

~~Besides the new opcodes,~~ Some opcodes pop and push order of values on the stack have been changed. To eliminate many swaps and to make this change easier.

~~This PR is still a WIP and needs more refactoring~~

This is now ready for review/merge :)
pull/2396/head
Halid Odat 2 years ago
parent
commit
bc2dd9c4bd
  1. 238
      boa_engine/src/bytecompiler/mod.rs
  2. 2
      boa_engine/src/vm/opcode/define/class/getter.rs
  3. 2
      boa_engine/src/vm/opcode/define/class/method.rs
  4. 2
      boa_engine/src/vm/opcode/define/class/setter.rs
  5. 2
      boa_engine/src/vm/opcode/define/own_property.rs
  6. 2
      boa_engine/src/vm/opcode/delete/mod.rs
  7. 4
      boa_engine/src/vm/opcode/get/property.rs
  8. 24
      boa_engine/src/vm/opcode/mod.rs
  9. 6
      boa_engine/src/vm/opcode/set/private.rs
  10. 16
      boa_engine/src/vm/opcode/set/property.rs
  11. 6
      boa_engine/src/vm/opcode/unary_ops/decrement.rs
  12. 6
      boa_engine/src/vm/opcode/unary_ops/increment.rs
  13. 37
      boa_engine/src/vm/tests.rs

238
boa_engine/src/bytecompiler/mod.rs

@ -678,8 +678,8 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::GetPropertyByName, &[index]); self.emit(Opcode::GetPropertyByName, &[index]);
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.compile_expr(access.target(), true)?; self.compile_expr(access.target(), true)?;
self.compile_expr(expr, true)?;
self.emit(Opcode::GetPropertyByValue, &[]); self.emit(Opcode::GetPropertyByValue, &[]);
} }
}, },
@ -695,8 +695,8 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::GetPropertyByName, &[index]); self.emit(Opcode::GetPropertyByName, &[index]);
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(&**expr, true)?;
self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::Super);
self.compile_expr(&**expr, true)?;
self.emit_opcode(Opcode::GetPropertyByValue); self.emit_opcode(Opcode::GetPropertyByValue);
} }
}, },
@ -712,62 +712,95 @@ impl<'b> ByteCompiler<'b> {
Ok(()) Ok(())
} }
#[inline] // The wrap is needed so it can match the function signature.
fn access_set( #[allow(clippy::unnecessary_wraps)]
&mut self, fn access_set_top_of_stack_expr_fn(compiler: &mut ByteCompiler<'_>, level: u8) -> JsResult<()> {
access: Access<'_>, match level {
expr: Option<&Expression>, 0 => {}
use_expr: bool, 1 => compiler.emit_opcode(Opcode::Swap),
) -> JsResult<()> { _ => {
if let Some(expr) = expr { compiler.emit_opcode(Opcode::RotateLeft);
self.compile_expr(expr, true)?; compiler.emit_u8(level + 1);
} }
if use_expr {
self.emit(Opcode::Dup, &[]);
} }
Ok(())
}
#[inline]
fn access_set<F, R>(&mut self, access: Access<'_>, use_expr: bool, expr_fn: F) -> JsResult<R>
where
F: FnOnce(&mut ByteCompiler<'_>, u8) -> JsResult<R>,
{
match access { match access {
Access::Variable { name } => { Access::Variable { name } => {
let result = expr_fn(self, 0);
if use_expr {
self.emit(Opcode::Dup, &[]);
}
let binding = self.context.set_mutable_binding(name); let binding = self.context.set_mutable_binding(name);
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::SetName, &[index]);
result
} }
Access::Property { access } => match access { Access::Property { access } => match access {
PropertyAccess::Simple(access) => match access.field() { PropertyAccess::Simple(access) => match access.field() {
PropertyAccessField::Const(name) => { PropertyAccessField::Const(name) => {
self.compile_expr(access.target(), true)?; self.compile_expr(access.target(), true)?;
let result = expr_fn(self, 1);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::SetPropertyByName, &[index]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
result
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.compile_expr(access.target(), true)?; self.compile_expr(access.target(), true)?;
self.compile_expr(expr, true)?;
let result = expr_fn(self, 2);
self.emit(Opcode::SetPropertyByValue, &[]); self.emit(Opcode::SetPropertyByValue, &[]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
result
} }
}, },
PropertyAccess::Private(access) => { PropertyAccess::Private(access) => {
self.compile_expr(access.target(), true)?; self.compile_expr(access.target(), true)?;
self.emit_opcode(Opcode::Swap); let result = expr_fn(self, 1);
let index = self.get_or_insert_name(access.field().into()); let index = self.get_or_insert_name(access.field().into());
self.emit(Opcode::AssignPrivateField, &[index]); self.emit(Opcode::AssignPrivateField, &[index]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
result
} }
PropertyAccess::Super(access) => match access.field() { PropertyAccess::Super(access) => match access.field() {
PropertyAccessField::Const(name) => { PropertyAccessField::Const(name) => {
self.emit(Opcode::Super, &[]); self.emit(Opcode::Super, &[]);
let result = expr_fn(self, 1);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::SetPropertyByName, &[index]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
result
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.emit(Opcode::Super, &[]); self.emit(Opcode::Super, &[]);
self.compile_expr(expr, true)?;
let result = expr_fn(self, 0);
self.emit(Opcode::SetPropertyByValue, &[]); self.emit(Opcode::SetPropertyByValue, &[]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
result
} }
}, },
}, },
Access::This => todo!("access_set `this`"), Access::This => todo!("access_set `this`"),
} }
Ok(())
} }
#[inline] #[inline]
@ -781,8 +814,8 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::DeletePropertyByName, &[index]); self.emit(Opcode::DeletePropertyByName, &[index]);
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?;
self.compile_expr(access.target(), true)?; self.compile_expr(access.target(), true)?;
self.compile_expr(expr, true)?;
self.emit(Opcode::DeletePropertyByValue, &[]); self.emit(Opcode::DeletePropertyByValue, &[]);
} }
}, },
@ -869,47 +902,61 @@ impl<'b> ByteCompiler<'b> {
Expression::Unary(unary) => { Expression::Unary(unary) => {
let opcode = match unary.op() { let opcode = match unary.op() {
UnaryOp::IncrementPre => { UnaryOp::IncrementPre => {
self.compile_expr(unary.target(), true)?; // TODO: promote to an early error.
self.emit(Opcode::Inc, &[]);
let access = Access::from_expression(unary.target()).ok_or_else(|| { let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid increment operand") JsNativeError::syntax().with_message("Invalid increment operand")
})?; })?;
self.access_set(access, None, true)?;
self.access_set(access, true, |compiler, _| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::Inc, &[]);
Ok(())
})?;
None None
} }
UnaryOp::DecrementPre => { UnaryOp::DecrementPre => {
self.compile_expr(unary.target(), true)?;
self.emit(Opcode::Dec, &[]);
// TODO: promote to an early error. // TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| { let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid decrement operand") JsNativeError::syntax().with_message("Invalid decrement operand")
})?; })?;
self.access_set(access, None, true)?;
self.access_set(access, true, |compiler, _| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::Dec, &[]);
Ok(())
})?;
None None
} }
UnaryOp::IncrementPost => { UnaryOp::IncrementPost => {
self.compile_expr(unary.target(), true)?;
self.emit(Opcode::IncPost, &[]);
// TODO: promote to an early error. // TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| { let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid increment operand") JsNativeError::syntax().with_message("Invalid increment operand")
})?; })?;
self.access_set(access, None, false)?;
self.access_set(access, false, |compiler, level| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::IncPost, &[]);
compiler.emit_opcode(Opcode::RotateRight);
compiler.emit_u8(level + 2);
Ok(())
})?;
None None
} }
UnaryOp::DecrementPost => { UnaryOp::DecrementPost => {
self.compile_expr(unary.target(), true)?;
self.emit(Opcode::DecPost, &[]);
// TODO: promote to an early error. // TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| { let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid decrement operand") JsNativeError::syntax().with_message("Invalid decrement operand")
})?; })?;
self.access_set(access, None, false)?;
self.access_set(access, false, |compiler, level| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::DecPost, &[]);
compiler.emit_opcode(Opcode::RotateRight);
compiler.emit_u8(level + 2);
Ok(())
})?;
None None
} }
@ -1038,7 +1085,10 @@ impl<'b> ByteCompiler<'b> {
} }
Expression::Assign(assign) if assign.op() == AssignOp::Assign => { Expression::Assign(assign) if assign.op() == AssignOp::Assign => {
match Access::from_assign_target(assign.lhs()) { match Access::from_assign_target(assign.lhs()) {
Ok(access) => self.access_set(access, Some(assign.rhs()), use_expr)?, Ok(access) => self.access_set(access, use_expr, |compiler, _| {
compiler.compile_expr(assign.rhs(), true)?;
Ok(())
})?,
Err(pattern) => { Err(pattern) => {
self.compile_expr(assign.rhs(), true)?; self.compile_expr(assign.rhs(), true)?;
if use_expr { if use_expr {
@ -1051,7 +1101,30 @@ impl<'b> ByteCompiler<'b> {
Expression::Assign(assign) => { Expression::Assign(assign) => {
let access = Access::from_assign_target(assign.lhs()) let access = Access::from_assign_target(assign.lhs())
.expect("patterns should throw early errors on complex assignment operators"); .expect("patterns should throw early errors on complex assignment operators");
self.access_get(access, true)?;
let shortcircuit_operator_compilation =
|compiler: &mut ByteCompiler<'_>, opcode: Opcode| -> JsResult<()> {
let (early_exit, pop_count) =
compiler.access_set(access, use_expr, |compiler, level| {
compiler.access_get(access, true)?;
let early_exit = compiler.emit_opcode_with_operand(opcode);
compiler.compile_expr(assign.rhs(), true)?;
Ok((early_exit, level))
})?;
if pop_count == 0 {
compiler.patch_jump(early_exit);
} else {
let exit = compiler.emit_opcode_with_operand(Opcode::Jump);
compiler.patch_jump(early_exit);
for _ in 0..pop_count {
compiler.emit_opcode(Opcode::Swap);
compiler.emit_opcode(Opcode::Pop);
}
compiler.patch_jump(exit);
}
Ok(())
};
let opcode = match assign.op() { let opcode = match assign.op() {
AssignOp::Assign => unreachable!(), AssignOp::Assign => unreachable!(),
AssignOp::Add => Opcode::Add, AssignOp::Add => Opcode::Add,
@ -1067,31 +1140,25 @@ impl<'b> ByteCompiler<'b> {
AssignOp::Shr => Opcode::ShiftRight, AssignOp::Shr => Opcode::ShiftRight,
AssignOp::Ushr => Opcode::UnsignedShiftRight, AssignOp::Ushr => Opcode::UnsignedShiftRight,
AssignOp::BoolAnd => { AssignOp::BoolAnd => {
let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd); shortcircuit_operator_compilation(self, Opcode::LogicalAnd)?;
self.compile_expr(assign.rhs(), true)?;
self.access_set(access, None, use_expr)?;
self.patch_jump(exit);
return Ok(()); return Ok(());
} }
AssignOp::BoolOr => { AssignOp::BoolOr => {
let exit = self.emit_opcode_with_operand(Opcode::LogicalOr); shortcircuit_operator_compilation(self, Opcode::LogicalOr)?;
self.compile_expr(assign.rhs(), true)?;
self.access_set(access, None, use_expr)?;
self.patch_jump(exit);
return Ok(()); return Ok(());
} }
AssignOp::Coalesce => { AssignOp::Coalesce => {
let exit = self.emit_opcode_with_operand(Opcode::Coalesce); shortcircuit_operator_compilation(self, Opcode::Coalesce)?;
self.compile_expr(assign.rhs(), true)?;
self.access_set(access, None, use_expr)?;
self.patch_jump(exit);
return Ok(()); return Ok(());
} }
}; };
self.compile_expr(assign.rhs(), true)?; self.access_set(access, use_expr, |compiler, _| {
self.emit(opcode, &[]); compiler.access_get(access, true)?;
self.access_set(access, None, use_expr)?; compiler.compile_expr(assign.rhs(), true)?;
compiler.emit(opcode, &[]);
Ok(())
})?;
} }
Expression::ObjectLiteral(object) => { Expression::ObjectLiteral(object) => {
self.emit_opcode(Opcode::PushEmptyObject); self.emit_opcode(Opcode::PushEmptyObject);
@ -1105,7 +1172,6 @@ impl<'b> ByteCompiler<'b> {
PropertyDefinition::Property(name, expr) => match name { PropertyDefinition::Property(name, expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -1121,7 +1187,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Get(expr) => match name { MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertyGetterByName, &[index]); self.emit(Opcode::SetPropertyGetterByName, &[index]);
} }
@ -1136,7 +1201,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Set(expr) => match name { MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertySetterByName, &[index]); self.emit(Opcode::SetPropertySetterByName, &[index]);
} }
@ -1150,7 +1214,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Ordinary(expr) => match name { MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -1164,7 +1227,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Async(expr) => match name { MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -1178,7 +1240,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Generator(expr) => match name { MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -1192,7 +1253,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::AsyncGenerator(expr) => match name { MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -1365,7 +1425,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccessField::Expr(field) => { PropertyAccessField::Expr(field) => {
self.compile_expr(field, true)?; self.compile_expr(field, true)?;
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]); self.emit(Opcode::GetPropertyByValue, &[]);
} }
} }
@ -1404,9 +1463,9 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);
} }
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(Sym::RAW.into()); let index = self.get_or_insert_name(Sym::RAW.into());
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::Pop, &[]);
for expr in template.exprs() { for expr in template.exprs() {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
@ -1491,7 +1550,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccessField::Expr(field) => { PropertyAccessField::Expr(field) => {
self.compile_expr(field, true)?; self.compile_expr(field, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue); self.emit_opcode(Opcode::GetPropertyByValue);
} }
} }
@ -1512,7 +1570,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue); self.emit_opcode(Opcode::GetPropertyByValue);
} }
} }
@ -1602,7 +1659,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccessField::Expr(expr) => { PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]); self.emit(Opcode::GetPropertyByValue, &[]);
} }
} }
@ -1858,7 +1914,11 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::DefInitVar, &[index]); self.emit(Opcode::DefInitVar, &[index]);
} }
IterableLoopInitializer::Access(access) => { IterableLoopInitializer::Access(access) => {
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
} }
IterableLoopInitializer::Var(declaration) => match declaration { IterableLoopInitializer::Var(declaration) => match declaration {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
@ -1979,7 +2039,11 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::DefInitVar, &[index]); self.emit(Opcode::DefInitVar, &[index]);
} }
IterableLoopInitializer::Access(access) => { IterableLoopInitializer::Access(access) => {
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
} }
IterableLoopInitializer::Var(declaration) => match declaration { IterableLoopInitializer::Var(declaration) => match declaration {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
@ -2649,7 +2713,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyName::Computed(node) => { PropertyName::Computed(node) => {
self.compile_expr(node, true)?; self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
if rest_exits { if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush); self.emit_opcode(Opcode::GetPropertyByValuePush);
} else { } else {
@ -2702,7 +2765,11 @@ impl<'b> ByteCompiler<'b> {
)); ));
} }
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32, 0]); self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32, 0]);
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
} }
AssignmentPropertyAccess { AssignmentPropertyAccess {
name, name,
@ -2717,7 +2784,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyName::Computed(node) => { PropertyName::Computed(node) => {
self.compile_expr(node, true)?; self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
if rest_exits { if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush); self.emit_opcode(Opcode::GetPropertyByValuePush);
} else { } else {
@ -2733,7 +2799,11 @@ impl<'b> ByteCompiler<'b> {
self.patch_jump(skip); self.patch_jump(skip);
} }
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
if rest_exits && name.computed().is_some() { if rest_exits && name.computed().is_some() {
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
@ -2753,7 +2823,6 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyName::Computed(node) => { PropertyName::Computed(node) => {
self.compile_expr(node, true)?; self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue); self.emit_opcode(Opcode::GetPropertyByValue);
} }
} }
@ -2806,7 +2875,11 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccess { access } => { PropertyAccess { access } => {
self.emit_opcode(Opcode::IteratorNext); self.emit_opcode(Opcode::IteratorNext);
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
} }
// BindingElement : BindingPattern Initializer[opt] // BindingElement : BindingPattern Initializer[opt]
Pattern { Pattern {
@ -2831,7 +2904,11 @@ impl<'b> ByteCompiler<'b> {
} }
PropertyAccessRest { access } => { PropertyAccessRest { access } => {
self.emit_opcode(Opcode::IteratorToArray); self.emit_opcode(Opcode::IteratorToArray);
self.access_set(Access::Property { access }, None, false)?; self.access_set(
Access::Property { access },
false,
Self::access_set_top_of_stack_expr_fn,
)?;
} }
// BindingRestElement : ... BindingPattern // BindingRestElement : ... BindingPattern
PatternRest { pattern } => { PatternRest { pattern } => {
@ -3128,7 +3205,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Get(expr) => match name { MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassGetterByName, &[index]); self.emit(Opcode::DefineClassGetterByName, &[index]);
} }
@ -3142,7 +3218,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Set(expr) => match name { MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassSetterByName, &[index]); self.emit(Opcode::DefineClassSetterByName, &[index]);
} }
@ -3156,7 +3231,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Ordinary(expr) => match name { MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3170,7 +3244,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Async(expr) => match name { MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3184,7 +3257,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Generator(expr) => match name { MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3198,7 +3270,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::AsyncGenerator(expr) => match name { MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3333,7 +3404,6 @@ impl<'b> ByteCompiler<'b> {
} else { } else {
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
} }
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
@ -3432,7 +3502,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Get(expr) => match name { MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassGetterByName, &[index]); self.emit(Opcode::DefineClassGetterByName, &[index]);
} }
@ -3446,7 +3515,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Set(expr) => match name { MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassSetterByName, &[index]); self.emit(Opcode::DefineClassSetterByName, &[index]);
} }
@ -3460,7 +3528,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Ordinary(expr) => match name { MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3474,7 +3541,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Async(expr) => match name { MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3488,7 +3554,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Generator(expr) => match name { MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }
@ -3502,7 +3567,6 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::AsyncGenerator(expr) => match name { MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?; self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name((*name).into()); let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]); self.emit(Opcode::DefineClassMethodByName, &[index]);
} }

2
boa_engine/src/vm/opcode/define/class/getter.rs

@ -17,8 +17,8 @@ impl Operation for DefineClassGetterByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = object.to_object(context)?; let object = object.to_object(context)?;
value value
.as_object() .as_object()

2
boa_engine/src/vm/opcode/define/class/method.rs

@ -17,8 +17,8 @@ impl Operation for DefineClassMethodByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {

2
boa_engine/src/vm/opcode/define/class/setter.rs

@ -17,8 +17,8 @@ impl Operation for DefineClassSetterByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = object.to_object(context)?; let object = object.to_object(context)?;
value value
.as_object() .as_object()

2
boa_engine/src/vm/opcode/define/own_property.rs

@ -17,8 +17,8 @@ impl Operation for DefineOwnPropertyByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {

2
boa_engine/src/vm/opcode/delete/mod.rs

@ -47,8 +47,8 @@ impl Operation for DeletePropertyByValue {
const INSTRUCTION: &'static str = "INST - DeletePropertyByValue"; const INSTRUCTION: &'static str = "INST - DeletePropertyByValue";
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let object = context.vm.pop();
let key = context.vm.pop(); let key = context.vm.pop();
let object = context.vm.pop();
let result = object let result = object
.to_object(context)? .to_object(context)?
.__delete__(&key.to_property_key(context)?, context)?; .__delete__(&key.to_property_key(context)?, context)?;

4
boa_engine/src/vm/opcode/get/property.rs

@ -50,8 +50,8 @@ impl Operation for GetPropertyByValue {
const INSTRUCTION: &'static str = "INST - GetPropertyByValue"; const INSTRUCTION: &'static str = "INST - GetPropertyByValue";
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let object = context.vm.pop();
let key = context.vm.pop(); let key = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {
@ -78,8 +78,8 @@ impl Operation for GetPropertyByValuePush {
const INSTRUCTION: &'static str = "INST - GetPropertyByValuePush"; const INSTRUCTION: &'static str = "INST - GetPropertyByValuePush";
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let object = context.vm.pop();
let key = context.vm.pop(); let key = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {

24
boa_engine/src/vm/opcode/mod.rs

@ -702,7 +702,7 @@ generate_impl! {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: key, object **=>** value /// Stack: object, key **=>** value
GetPropertyByValue, GetPropertyByValue,
/// Get a property by value from an object an push the key and value on the stack. /// Get a property by value from an object an push the key and value on the stack.
@ -711,7 +711,7 @@ generate_impl! {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: key, object **=>** key, value /// Stack: object, key **=>** key, value
GetPropertyByValuePush, GetPropertyByValuePush,
/// Sets a property by name of an object. /// Sets a property by name of an object.
@ -720,21 +720,21 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>** value
SetPropertyByName, SetPropertyByName,
/// Defines a own property of an object by name. /// Defines a own property of an object by name.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
DefineOwnPropertyByName, DefineOwnPropertyByName,
/// Defines a class method by name. /// Defines a class method by name.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
DefineClassMethodByName, DefineClassMethodByName,
/// Sets a property by value of an object. /// Sets a property by value of an object.
@ -743,7 +743,7 @@ generate_impl! {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: value, key, object **=>** /// Stack: object, key, value **=>** value
SetPropertyByValue, SetPropertyByValue,
/// Defines a own property of an object by value. /// Defines a own property of an object by value.
@ -766,7 +766,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
SetPropertyGetterByName, SetPropertyGetterByName,
/// Defines a getter class method by name. /// Defines a getter class method by name.
@ -775,7 +775,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
DefineClassGetterByName, DefineClassGetterByName,
/// Sets a getter property by value of an object. /// Sets a getter property by value of an object.
@ -802,7 +802,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
SetPropertySetterByName, SetPropertySetterByName,
/// Defines a setter class method by name. /// Defines a setter class method by name.
@ -811,7 +811,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, object **=>** /// Stack: object, value **=>**
DefineClassSetterByName, DefineClassSetterByName,
/// Sets a setter property by value of an object. /// Sets a setter property by value of an object.
@ -838,7 +838,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: object, value **=>** /// Stack: object, value **=>** value
AssignPrivateField, AssignPrivateField,
/// Set a private property of a class constructor by it's name. /// Set a private property of a class constructor by it's name.
@ -936,7 +936,7 @@ generate_impl! {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: key, object **=>** /// Stack: object, key **=>**
DeletePropertyByValue, DeletePropertyByValue,
/// Copy all properties of one object to another object. /// Copy all properties of one object to another object.

6
boa_engine/src/vm/opcode/set/private.rs

@ -25,7 +25,8 @@ impl Operation for AssignPrivateField {
let mut object_borrow_mut = object.borrow_mut(); let mut object_borrow_mut = object.borrow_mut();
match object_borrow_mut.get_private_element(name.sym()) { match object_borrow_mut.get_private_element(name.sym()) {
Some(PrivateElement::Field(_)) => { Some(PrivateElement::Field(_)) => {
object_borrow_mut.set_private_element(name.sym(), PrivateElement::Field(value)); object_borrow_mut
.set_private_element(name.sym(), PrivateElement::Field(value.clone()));
} }
Some(PrivateElement::Method(_)) => { Some(PrivateElement::Method(_)) => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
@ -38,7 +39,7 @@ impl Operation for AssignPrivateField {
}) => { }) => {
let setter = setter.clone(); let setter = setter.clone();
drop(object_borrow_mut); drop(object_borrow_mut);
setter.call(&object.clone().into(), &[value], context)?; setter.call(&object.clone().into(), &[value.clone()], context)?;
} }
None => { None => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
@ -56,6 +57,7 @@ impl Operation for AssignPrivateField {
.with_message("cannot set private property on non-object") .with_message("cannot set private property on non-object")
.into()); .into());
} }
context.vm.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

16
boa_engine/src/vm/opcode/set/property.rs

@ -18,8 +18,8 @@ impl Operation for SetPropertyByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {
@ -33,7 +33,8 @@ impl Operation for SetPropertyByName {
.into_common::<JsString>(false) .into_common::<JsString>(false)
.into(); .into();
object.set(name, value, context.vm.frame().code.strict, context)?; object.set(name, value.clone(), context.vm.frame().code.strict, context)?;
context.vm.stack.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }
@ -50,9 +51,9 @@ impl Operation for SetPropertyByValue {
const INSTRUCTION: &'static str = "INST - SetPropertyByValue"; const INSTRUCTION: &'static str = "INST - SetPropertyByValue";
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let object = context.vm.pop();
let key = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let key = context.vm.pop();
let object = context.vm.pop();
let object = if let Some(object) = object.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {
@ -60,7 +61,8 @@ impl Operation for SetPropertyByValue {
}; };
let key = key.to_property_key(context)?; let key = key.to_property_key(context)?;
object.set(key, value, context.vm.frame().code.strict, context)?; object.set(key, value.clone(), context.vm.frame().code.strict, context)?;
context.vm.stack.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }
@ -78,8 +80,8 @@ impl Operation for SetPropertyGetterByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = object.to_object(context)?; let object = object.to_object(context)?;
let name = context.vm.frame().code.names[index as usize]; let name = context.vm.frame().code.names[index as usize];
let name = context let name = context
@ -155,8 +157,8 @@ impl Operation for SetPropertySetterByName {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let object = context.vm.pop();
let value = context.vm.pop(); let value = context.vm.pop();
let object = context.vm.pop();
let object = object.to_object(context)?; let object = object.to_object(context)?;
let name = context.vm.frame().code.names[index as usize]; let name = context.vm.frame().code.names[index as usize];
let name = context let name = context

6
boa_engine/src/vm/opcode/unary_ops/decrement.rs

@ -41,13 +41,13 @@ impl Operation for DecPost {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let value = context.vm.pop(); let value = context.vm.pop();
let value = value.to_numeric(context)?; let value = value.to_numeric(context)?;
context.vm.push(value.clone());
match value { match value {
Numeric::Number(number) => context.vm.push(number - 1f64), Numeric::Number(number) => context.vm.push(number - 1f64),
Numeric::BigInt(bigint) => { Numeric::BigInt(ref bigint) => {
context.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())); context.vm.push(JsBigInt::sub(bigint, &JsBigInt::one()));
} }
} }
context.vm.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

6
boa_engine/src/vm/opcode/unary_ops/increment.rs

@ -41,13 +41,13 @@ impl Operation for IncPost {
fn execute(context: &mut Context) -> JsResult<ShouldExit> { fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let value = context.vm.pop(); let value = context.vm.pop();
let value = value.to_numeric(context)?; let value = value.to_numeric(context)?;
context.vm.push(value.clone());
match value { match value {
Numeric::Number(number) => context.vm.push(number + 1f64), Numeric::Number(number) => context.vm.push(number + 1f64),
Numeric::BigInt(bigint) => { Numeric::BigInt(ref bigint) => {
context.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())); context.vm.push(JsBigInt::add(bigint, &JsBigInt::one()));
} }
} }
context.vm.push(value);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

37
boa_engine/src/vm/tests.rs

@ -1,4 +1,4 @@
use crate::{exec, Context, JsValue}; use crate::{check_output, exec, Context, JsValue, TestAction};
#[test] #[test]
fn typeof_string() { fn typeof_string() {
@ -189,3 +189,38 @@ fn get_reference_by_super() {
JsValue::from("ab") JsValue::from("ab")
); );
} }
#[test]
fn order_of_execution_in_assigment() {
let scenario = r#"
let i = 0;
let array = [[]];
array[i++][i++] = i++;
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("i", "3"),
TestAction::TestEq("array.length", "1"),
TestAction::TestEq("array[0].length", "2"),
]);
}
#[test]
fn order_of_execution_in_assigment_with_comma_expressions() {
let scenario = r#"
let result = "";
function f(i) {
result += i;
}
let a = [[]];
(f(1), a)[(f(2), 0)][(f(3), 0)] = (f(4), 123);
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "\"1234\""),
]);
}

Loading…
Cancel
Save