Browse Source

Fix switch statement `break` and `continue` return values (#3205)

* Fix switch statement early return values

* Remove unused control flow
pull/3209/head
raskad 1 year ago committed by GitHub
parent
commit
cd232b18f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      boa_engine/src/bytecompiler/mod.rs
  2. 38
      boa_engine/src/bytecompiler/statement/try.rs

11
boa_engine/src/bytecompiler/mod.rs

@ -843,15 +843,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile a [`StatementList`].
pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool, block: bool) {
if use_expr || self.jump_control_info_has_use_expr() {
let mut has_returns_value = false;
let mut use_expr_index = 0;
let mut first_return_is_abrupt = false;
for (i, statement) in list.statements().iter().enumerate() {
match statement {
StatementListItem::Statement(Statement::Break(_) | Statement::Continue(_)) => {
if !has_returns_value {
first_return_is_abrupt = true;
}
break;
}
StatementListItem::Statement(Statement::Empty | Statement::Var(_))
@ -859,17 +854,11 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
StatementListItem::Statement(Statement::Block(block))
if !returns_value(block) => {}
StatementListItem::Statement(_) => {
has_returns_value = true;
use_expr_index = i;
}
}
}
if first_return_is_abrupt {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetReturnValue);
}
for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i == use_expr_index, block);
}

38
boa_engine/src/bytecompiler/statement/try.rs

@ -5,7 +5,8 @@ use crate::{
use boa_ast::{
declaration::Binding,
operations::bound_names,
statement::{Catch, Finally, Try},
statement::{Block, Catch, Finally, Try},
Statement, StatementListItem,
};
impl ByteCompiler<'_, '_> {
@ -130,7 +131,7 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Pop);
}
self.compile_block(catch.block(), use_expr);
self.compile_catch_finally_block(catch.block(), use_expr);
let env_index = self.pop_compile_environment();
self.patch_jump_with_target(push_env, env_index);
@ -141,7 +142,7 @@ impl ByteCompiler<'_, '_> {
// TODO: We could probably remove the Get/SetReturnValue if we check that there is no break/continues statements.
self.current_stack_value_count += 1;
self.emit_opcode(Opcode::GetReturnValue);
self.compile_block(finally.block(), true);
self.compile_catch_finally_block(finally.block(), true);
self.emit_opcode(Opcode::SetReturnValue);
self.current_stack_value_count -= 1;
@ -162,4 +163,35 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(do_not_throw_exit);
}
/// Compile a catch or finally block.
///
/// If the block contains a break or continue as the first statement,
/// the return value is set to undefined.
/// See the [ECMAScript reference][spec] for more information.
///
/// [spec]: https://tc39.es/ecma262/#sec-try-statement-runtime-semantics-evaluation
fn compile_catch_finally_block(&mut self, block: &Block, use_expr: bool) {
let mut b = block;
loop {
match b.statement_list().first() {
Some(StatementListItem::Statement(
Statement::Break(_) | Statement::Continue(_),
)) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetReturnValue);
break;
}
Some(StatementListItem::Statement(Statement::Block(block))) => {
b = block;
}
_ => {
break;
}
}
}
self.compile_block(block, use_expr);
}
}

Loading…
Cancel
Save