Skip to content

Commit

Permalink
Fix panics on empty return values
Browse files Browse the repository at this point in the history
  • Loading branch information
raskad committed Jun 8, 2023
1 parent 0fda681 commit 4b749dc
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 69 deletions.
58 changes: 58 additions & 0 deletions boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2098,3 +2098,61 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> {
self.visit(node.statement())
}
}

/// Returns `true` if the given statement returns a value.
#[must_use]
pub fn returns_value<'a, N>(node: &'a N) -> bool
where
&'a N: Into<NodeRef<'a>>,
{
ReturnsValueVisitor.visit(node.into()).is_break()
}

/// The [`Visitor`] used for [`returns_value`].
#[derive(Debug)]
struct ReturnsValueVisitor;

impl<'ast> Visitor<'ast> for ReturnsValueVisitor {
type BreakTy = ();

fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow<Self::BreakTy> {
for statement in node.statement_list().statements() {
match statement {
StatementListItem::Declaration(_) => {}
StatementListItem::Statement(node) => try_break!(self.visit(node)),
}
}
ControlFlow::Continue(())
}

fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node {
Statement::Empty | Statement::Var(_) => {}
Statement::Block(node) => try_break!(self.visit(node)),
Statement::Labelled(node) => try_break!(self.visit(node)),
_ => return ControlFlow::Break(()),
}
ControlFlow::Continue(())
}

fn visit_case(&mut self, node: &'ast crate::statement::Case) -> ControlFlow<Self::BreakTy> {
for statement in node.body().statements() {
match statement {
StatementListItem::Declaration(_) => {}
StatementListItem::Statement(node) => try_break!(self.visit(node)),
}
}
ControlFlow::Continue(())
}

fn visit_labelled(
&mut self,
node: &'ast crate::statement::Labelled,
) -> ControlFlow<Self::BreakTy> {
match node.item() {
LabelledItem::Statement(node) => try_break!(self.visit(node)),
LabelledItem::Function(_) => {}
}
ControlFlow::Continue(())
}
}
11 changes: 0 additions & 11 deletions boa_ast/src/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,6 @@ impl Statement {
_ => false,
}
}

/// Returns `true` if the statement returns a value.
#[inline]
#[must_use]
pub const fn returns_value(&self) -> bool {
match self {
Self::Block(block) if block.statement_list().statements().is_empty() => false,
Self::Empty | Self::Var(_) | Self::Break(_) | Self::Continue(_) => false,
_ => true,
}
}
}

impl ToIndentedString for Statement {
Expand Down
49 changes: 25 additions & 24 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use boa_ast::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class,
FormalParameterList, Function, FunctionBody, Generator, PrivateName,
},
operations::returns_value,
pattern::Pattern,
Declaration, Expression, Statement, StatementList, StatementListItem,
};
Expand Down Expand Up @@ -734,34 +735,34 @@ 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 {
let list_until_loop_exit: Vec<_> = list
.statements()
.iter()
.take_while(|item| {
!matches!(
item,
StatementListItem::Statement(Statement::Break(_) | Statement::Continue(_))
)
})
.collect();
let expr_index = list_until_loop_exit
.iter()
.rev()
.skip_while(|item| {
matches!(
item,
&&StatementListItem::Statement(Statement::Empty | Statement::Var(_))
| &&StatementListItem::Declaration(_)
)
})
.count();

if expr_index == 0 && !list.statements().is_empty() {
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(_))
| StatementListItem::Declaration(_) => {}
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);
}

for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i + 1 == expr_index, block);
self.compile_stmt_list_item(item, i == use_expr_index, block);
}
} else {
for item in list.statements() {
Expand Down
38 changes: 21 additions & 17 deletions boa_engine/src/bytecompiler/statement/break.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
bytecompiler::{ByteCompiler, Label},
bytecompiler::{ByteCompiler, JumpControlInfo},
vm::Opcode,
};
use boa_ast::statement::Break;
Expand Down Expand Up @@ -29,10 +29,11 @@ impl ByteCompiler<'_, '_> {
let (break_label, target_jump_label) = self.emit_opcode_with_two_operands(opcode);

if let Some(node_label) = node.label() {
self.search_jump_info_label(target_jump_label, node_label);
let info = self.jump_info_label(node_label);
info.push_break_label(target_jump_label);

if !has_finally_or_is_finally {
self.search_jump_info_label(break_label, node_label);
info.push_break_label(break_label);
return;
}
} else {
Expand All @@ -55,29 +56,32 @@ impl ByteCompiler<'_, '_> {
// Emit the break opcode -> (Label, Label)
let (break_label, target_label) = self.emit_opcode_with_two_operands(opcode);
if let Some(label) = node.label() {
self.search_jump_info_label(break_label, label);
self.search_jump_info_label(target_label, label);
let info = self.jump_info_label(label);
info.push_break_label(break_label);
info.push_break_label(target_label);
return;
}

let info = self
.jump_info
.last_mut()
.expect("unlabeled break must be inside loop or switch");

let info = self.jump_info_non_labelled();
info.push_break_label(break_label);
info.push_break_label(target_label);
}

fn search_jump_info_label(&mut self, address: Label, node_label: Sym) {
let mut found = false;
fn jump_info_non_labelled(&mut self) -> &mut JumpControlInfo {
for info in self.jump_info.iter_mut().rev() {
if !info.is_labelled() {
return info;
}
}
panic!("Jump info without label must exist");
}

fn jump_info_label(&mut self, label: Sym) -> &mut JumpControlInfo {
for info in self.jump_info.iter_mut().rev() {
if info.label() == Some(node_label) {
info.push_break_label(address);
found = true;
break;
if info.label() == Some(label) {
return info;
}
}
assert!(found, "Cannot use the undeclared label");
panic!("Jump info with label must exist");
}
}
6 changes: 3 additions & 3 deletions boa_engine/src/bytecompiler/statement/if.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::statement::If;
use boa_ast::{operations::returns_value, statement::If};

impl ByteCompiler<'_, '_> {
pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool) {
self.compile_expr(node.cond(), true);
let jelse = self.jump_if_false();

if !node.body().returns_value() {
if !returns_value(node.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(node.body(), true);
Expand All @@ -18,7 +18,7 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PushUndefined);
}
Some(else_body) => {
if !else_body.returns_value() {
if !returns_value(else_body) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(else_body, true);
Expand Down
12 changes: 6 additions & 6 deletions boa_engine/src/bytecompiler/statement/loop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use boa_ast::{
declaration::Binding,
operations::bound_names,
operations::{bound_names, returns_value},
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop,
Expand Down Expand Up @@ -95,7 +95,7 @@ impl ByteCompiler<'_, '_> {
}
let exit = self.jump_if_false();

if !for_loop.body().returns_value() {
if !returns_value(for_loop.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_loop.body(), true);
Expand Down Expand Up @@ -229,7 +229,7 @@ impl ByteCompiler<'_, '_> {
}
}

if !for_in_loop.body().returns_value() {
if !returns_value(for_in_loop.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_in_loop.body(), true);
Expand Down Expand Up @@ -375,7 +375,7 @@ impl ByteCompiler<'_, '_> {
}
}

if !for_of_loop.body().returns_value() {
if !returns_value(for_of_loop.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(for_of_loop.body(), true);
Expand Down Expand Up @@ -416,7 +416,7 @@ impl ByteCompiler<'_, '_> {
self.compile_expr(while_loop.condition(), true);
let exit = self.jump_if_false();

if !while_loop.body().returns_value() {
if !returns_value(while_loop.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(while_loop.body(), true);
Expand Down Expand Up @@ -454,7 +454,7 @@ impl ByteCompiler<'_, '_> {

self.patch_jump(initial_label);

if !do_while_loop.body().returns_value() {
if !returns_value(do_while_loop.body()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(do_while_loop.body(), true);
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/bytecompiler/statement/switch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::statement::Switch;
use boa_ast::{operations::returns_value, statement::Switch};

impl ByteCompiler<'_, '_> {
/// Compile a [`Switch`] `boa_ast` node
Expand Down Expand Up @@ -44,7 +44,7 @@ impl ByteCompiler<'_, '_> {
};
self.patch_jump(label);
self.compile_statement_list(case.body(), true, true);
if !case.body().statements().is_empty() {
if returns_value(case) {
self.emit_opcode(Opcode::LoopUpdateReturnValue);
}
}
Expand Down
8 changes: 4 additions & 4 deletions boa_engine/src/bytecompiler/statement/try.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use boa_ast::{
declaration::Binding,
operations::bound_names,
operations::{bound_names, returns_value},
statement::{Catch, Finally, Try},
};

Expand All @@ -21,7 +21,7 @@ impl ByteCompiler<'_, '_> {
self.push_try_control_info(t.finally().is_some(), try_start);

self.compile_block(t.block(), true);
if t.block().statement_list().statements().is_empty() {
if !returns_value(t.block()) {
self.emit_opcode(Opcode::PushUndefined);
}
if !use_expr {
Expand Down Expand Up @@ -80,7 +80,7 @@ impl ByteCompiler<'_, '_> {
}

self.compile_block(catch.block(), true);
if catch.block().statement_list().statements().is_empty() {
if !returns_value(catch.block()) {
self.emit_opcode(Opcode::PushUndefined);
}
if !use_expr {
Expand All @@ -103,7 +103,7 @@ impl ByteCompiler<'_, '_> {

pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, finally_end_label: Label) {
self.compile_block(finally.block(), true);
if !finally.block().statement_list().statements().is_empty() {
if returns_value(finally.block()) {
self.emit_opcode(Opcode::Pop);
}

Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/bytecompiler/statement/with.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::statement::With;
use boa_ast::{operations::returns_value, statement::With};

impl ByteCompiler<'_, '_> {
/// Compile a [`With`] `boa_ast` node
Expand All @@ -8,7 +8,7 @@ impl ByteCompiler<'_, '_> {
self.push_compile_environment(false);
self.emit_opcode(Opcode::PushObjectEnvironment);

if !with.statement().returns_value() {
if !returns_value(with.statement()) {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_stmt(with.statement(), true);
Expand Down
33 changes: 33 additions & 0 deletions boa_engine/src/vm/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,36 @@ fn arguments_object_constructor_valid_index() {
"object",
)]);
}

#[test]
fn empty_return_values() {
run_test_actions([
TestAction::run(indoc! {r#"do {{}} while (false);"#}),
TestAction::run(indoc! {r#"do try {{}} catch {} while (false);"#}),
TestAction::run(indoc! {r#"do {} while (false);"#}),
TestAction::run(indoc! {r#"do try {{}{}} catch {} while (false);"#}),
TestAction::run(indoc! {r#"do {{}{}} while (false);"#}),
TestAction::run(indoc! {r#"do {;{}} while (false);"#}),
TestAction::run(indoc! {r#"do {e: {}} while (false);"#}),
TestAction::run(indoc! {r#"do {e: ;} while (false);"#}),
TestAction::run(indoc! {r#"do { break } while (false);"#}),
TestAction::run(indoc! {r#"while (true) a: break"#}),
TestAction::run(indoc! {r#"while (true) a: {"a"; break};"#}),
TestAction::run(indoc! {r#"do {"a";{}} while (false);"#}),
TestAction::run(indoc! {r#"
switch (false) {
default: {}
}
"#}),
TestAction::run(indoc! {r#"
switch (false) {
default: {}{}
}
"#}),
TestAction::run(indoc! {r#"
switch (false) {
default: ;{}{}
}
"#}),
]);
}

0 comments on commit 4b749dc

Please sign in to comment.