mirror of https://github.com/boa-dev/boa.git
Browse Source
<!--- Thank you for contributing to Boa! Please fill out the template below, and remove or add any information as you feel necessary. ---> This Pull Request is meant to address #1900. While working on it, there was a decent amount of refactoring/restructuring. Initially, I had kept with the current approach of just keeping track of a kind and counter on the environment stack, especially because that kept the size of each stack entry to a minimum. I did, however, make a switch to having the opcode create the `EnvStackEntry` with a start address and exit address for a bit more precision. It changes the following: - Consolidates `loop_env_stack` and `try_env_stack` into one `EnvStackEntry` struct. - Changes `Continue`, `Break`, `LoopStart`, `LoopContinue`, `FinallyStart`, `FinallyEnd` and various others. Most of this primarily revolves around the creating of `EnvStackEntry` and interacting with the `env_stack`. - Changes/updates the try-catch-finally, break and continue statement compilations as necessary - Adds an `AbruptCompletionRecord` in place of the `finally_jump` vector. - Adds some tests for try-catch-finally blocks with breaks.pull/2588/head
Kevin
2 years ago
34 changed files with 1728 additions and 832 deletions
@ -0,0 +1,75 @@
|
||||
//! Implements an `AbruptCompletionRecord` struct for `CallFrame` tracking.
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)] |
||||
pub(crate) enum AbruptKind { |
||||
Continue, |
||||
Break, |
||||
Throw, |
||||
} |
||||
|
||||
/// The `AbruptCompletionRecord` tracks the current `AbruptCompletion` and target address of completion.
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub(crate) struct AbruptCompletionRecord { |
||||
kind: AbruptKind, |
||||
target: u32, |
||||
} |
||||
|
||||
/// ---- `AbruptCompletionRecord` initialization methods ----
|
||||
impl AbruptCompletionRecord { |
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Break`.
|
||||
pub(crate) const fn new_break() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Break, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Continue`.
|
||||
pub(crate) const fn new_continue() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Continue, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Throw`.
|
||||
pub(crate) const fn new_throw() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Throw, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Set the target field of the `AbruptCompletionRecord`.
|
||||
pub(crate) const fn with_initial_target(mut self, target: u32) -> Self { |
||||
self.target = target; |
||||
self |
||||
} |
||||
} |
||||
|
||||
/// ---- `AbruptCompletionRecord` interaction methods ----
|
||||
impl AbruptCompletionRecord { |
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a break.
|
||||
pub(crate) fn is_break(self) -> bool { |
||||
self.kind == AbruptKind::Break |
||||
} |
||||
|
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a continue.
|
||||
pub(crate) fn is_continue(self) -> bool { |
||||
self.kind == AbruptKind::Continue |
||||
} |
||||
|
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a throw.
|
||||
pub(crate) fn is_throw(self) -> bool { |
||||
self.kind == AbruptKind::Throw |
||||
} |
||||
|
||||
pub(crate) fn is_throw_with_target(self) -> bool { |
||||
self.is_throw() && self.target < u32::MAX |
||||
} |
||||
|
||||
/// Returns the value of `AbruptCompletionRecord`'s `target` field.
|
||||
pub(crate) const fn target(self) -> u32 { |
||||
self.target |
||||
} |
||||
} |
@ -0,0 +1,143 @@
|
||||
//! Module for implementing a `CallFrame`'s environment stacks
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] |
||||
pub(crate) enum EnvEntryKind { |
||||
Global, |
||||
Loop, |
||||
Try, |
||||
Catch, |
||||
Finally, |
||||
Labelled, |
||||
} |
||||
|
||||
/// The `EnvStackEntry` tracks the environment count and relavant information for the current environment.
|
||||
#[derive(Copy, Clone, Debug)] |
||||
pub(crate) struct EnvStackEntry { |
||||
start: u32, |
||||
exit: u32, |
||||
kind: EnvEntryKind, |
||||
env_num: usize, |
||||
} |
||||
|
||||
impl Default for EnvStackEntry { |
||||
fn default() -> Self { |
||||
Self { |
||||
start: 0, |
||||
exit: u32::MAX, |
||||
kind: EnvEntryKind::Global, |
||||
env_num: 0, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// ---- `EnvStackEntry` creation methods ----
|
||||
impl EnvStackEntry { |
||||
/// Creates a new `EnvStackEntry` with the supplied start addresses.
|
||||
pub(crate) const fn new(start_address: u32, exit_address: u32) -> Self { |
||||
Self { |
||||
start: start_address, |
||||
exit: exit_address, |
||||
kind: EnvEntryKind::Global, |
||||
env_num: 0, |
||||
} |
||||
} |
||||
|
||||
/// Returns calling `EnvStackEntry` with `kind` field of `Try`.
|
||||
pub(crate) const fn with_try_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Try; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling `EnvStackEntry` with `kind` field of `Loop`.
|
||||
pub(crate) const fn with_loop_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Loop; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling `EnvStackEntry` with `kind` field of `Catch`.
|
||||
pub(crate) const fn with_catch_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Catch; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling `EnvStackEntry` with `kind` field of `Finally`.
|
||||
pub(crate) const fn with_finally_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Finally; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling `EnvStackEntry` with `kind` field of `Labelled`.
|
||||
pub(crate) const fn with_labelled_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Labelled; |
||||
self |
||||
} |
||||
|
||||
pub(crate) const fn with_start_address(mut self, start_address: u32) -> Self { |
||||
self.start = start_address; |
||||
self |
||||
} |
||||
} |
||||
|
||||
/// ---- `EnvStackEntry` interaction methods ----
|
||||
impl EnvStackEntry { |
||||
/// Returns the `start` field of this `EnvStackEntry`.
|
||||
pub(crate) const fn start_address(&self) -> u32 { |
||||
self.start |
||||
} |
||||
|
||||
/// Returns the `exit` field of this `EnvStackEntry`.
|
||||
pub(crate) const fn exit_address(&self) -> u32 { |
||||
self.exit |
||||
} |
||||
|
||||
pub(crate) fn is_global_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Global |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a loop
|
||||
pub(crate) fn is_loop_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Loop |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a try block
|
||||
pub(crate) fn is_try_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Try |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a labelled block
|
||||
pub(crate) fn is_labelled_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Labelled |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a catch block
|
||||
pub(crate) fn is_catch_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Catch |
||||
} |
||||
|
||||
pub(crate) fn is_finally_env(&self) -> bool { |
||||
self.kind == EnvEntryKind::Finally |
||||
} |
||||
|
||||
/// Returns the current environment number for this entry.
|
||||
pub(crate) const fn env_num(&self) -> usize { |
||||
self.env_num |
||||
} |
||||
|
||||
pub(crate) fn set_exit_address(&mut self, exit_address: u32) { |
||||
self.exit = exit_address; |
||||
} |
||||
|
||||
pub(crate) fn clear_env_num(&mut self) { |
||||
self.env_num = 0; |
||||
} |
||||
|
||||
/// Increments the `env_num` field for current `EnvEntryStack`.
|
||||
pub(crate) fn inc_env_num(&mut self) { |
||||
self.env_num += 1; |
||||
} |
||||
|
||||
/// Decrements the `env_num` field for current `EnvEntryStack`.
|
||||
pub(crate) fn dec_env_num(&mut self) { |
||||
self.env_num -= 1; |
||||
} |
||||
} |
@ -0,0 +1,102 @@
|
||||
use crate::{ |
||||
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, FinallyReturn, ShouldExit}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `Break` implements the Opcode Operation for `Opcode::Break`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Pop required environments and jump to address.
|
||||
pub(crate) struct Break; |
||||
|
||||
impl Operation for Break { |
||||
const NAME: &'static str = "Break"; |
||||
const INSTRUCTION: &'static str = "INST - Break"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let jump_address = context.vm.read::<u32>(); |
||||
let target_address = context.vm.read::<u32>(); |
||||
|
||||
// 1. Iterate through Env stack looking for exit address.
|
||||
let mut envs_to_pop = 0; |
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if (jump_address == env_entry.exit_address()) |
||||
|| (env_entry.is_finally_env() && jump_address == env_entry.start_address()) |
||||
{ |
||||
break; |
||||
} |
||||
|
||||
// Checks for the break if we have jumped from inside of a finally block
|
||||
if jump_address == env_entry.exit_address() { |
||||
break; |
||||
} |
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
// 2. Register target address in AbruptCompletionRecord.
|
||||
let new_record = AbruptCompletionRecord::new_break().with_initial_target(target_address); |
||||
context.vm.frame_mut().abrupt_completion = Some(new_record); |
||||
|
||||
// 3. Set program counter and finally return fields.
|
||||
context.vm.frame_mut().pc = jump_address as usize; |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `Continue` implements the Opcode Operation for `Opcode::Continue`
|
||||
///
|
||||
/// Operands:
|
||||
/// - Target address
|
||||
/// - Initial environments to reconcile on continue (will be tracked along with changes to environment stack)
|
||||
///
|
||||
/// Operation:
|
||||
/// - Initializes the `AbruptCompletionRecord` for a delayed continued in a `Opcode::FinallyEnd`
|
||||
pub(crate) struct Continue; |
||||
|
||||
impl Operation for Continue { |
||||
const NAME: &'static str = "Continue"; |
||||
const INSTRUCTION: &'static str = "INST - Continue"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let jump_address = context.vm.read::<u32>(); |
||||
let target_address = context.vm.read::<u32>(); |
||||
|
||||
// 1. Iterate through Env stack looking for exit address.
|
||||
let mut envs_to_pop = 0; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.last() { |
||||
// We check two conditions here where continue actually jumps to a higher address.
|
||||
// 1. When we have reached a finally env that matches the jump address we are moving to.
|
||||
// 2. When there is no finally, and we have reached the continue location.
|
||||
if (env_entry.is_finally_env() && jump_address == env_entry.start_address()) |
||||
|| (jump_address == target_address && jump_address == env_entry.start_address()) |
||||
{ |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
// The below check determines whether we have continued from inside of a finally block.
|
||||
if jump_address > target_address && jump_address == env_entry.exit_address() { |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
break; |
||||
} |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
// 2. Register target address in AbruptCompletionRecord.
|
||||
let new_record = AbruptCompletionRecord::new_continue().with_initial_target(target_address); |
||||
context.vm.frame_mut().abrupt_completion = Some(new_record); |
||||
|
||||
// 3. Set program counter and finally return fields.
|
||||
context.vm.frame_mut().pc = jump_address as usize; |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -0,0 +1,97 @@
|
||||
use crate::{ |
||||
vm::{call_frame::EnvStackEntry, opcode::Operation, FinallyReturn, ShouldExit}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `CatchStart` implements the Opcode Operation for `Opcode::CatchStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a catch block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchStart; |
||||
|
||||
impl Operation for CatchStart { |
||||
const NAME: &'static str = "CatchStart"; |
||||
const INSTRUCTION: &'static str = "INST - CatchStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let start = context.vm.frame().pc as u32 - 1; |
||||
let finally = context.vm.read::<u32>(); |
||||
|
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.push(EnvStackEntry::new(start, finally - 1).with_catch_flag()); |
||||
|
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `CatchEnd` implements the Opcode Operation for `Opcode::CatchEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a catch block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchEnd; |
||||
|
||||
impl Operation for CatchEnd { |
||||
const NAME: &'static str = "CatchEnd"; |
||||
const INSTRUCTION: &'static str = "INST - CatchEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
context.vm.frame_mut().try_catch.pop(); |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_catch_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `CatchEnd2` implements the Opcode Operation for `Opcode::CatchEnd2`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a catch block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchEnd2; |
||||
|
||||
impl Operation for CatchEnd2 { |
||||
const NAME: &'static str = "CatchEnd2"; |
||||
const INSTRUCTION: &'static str = "INST - CatchEnd2"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
if let Some(catch_entry) = context |
||||
.vm |
||||
.frame() |
||||
.env_stack |
||||
.last() |
||||
.filter(|entry| entry.is_catch_env()) |
||||
{ |
||||
let env_truncation_len = context |
||||
.realm |
||||
.environments |
||||
.len() |
||||
.saturating_sub(catch_entry.env_num()); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
if context.vm.frame_mut().finally_return == FinallyReturn::Err { |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
} |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -0,0 +1,178 @@
|
||||
use crate::{ |
||||
vm::{opcode::Operation, FinallyReturn, ShouldExit}, |
||||
Context, JsError, JsResult, |
||||
}; |
||||
|
||||
/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyStart; |
||||
|
||||
impl Operation for FinallyStart { |
||||
const NAME: &'static str = "FinallyStart"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let exit = context.vm.read::<u32>(); |
||||
context.vm.frame_mut().try_catch.pop(); |
||||
|
||||
let finally_env = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.last_mut() |
||||
.expect("EnvStackEntries must exist"); |
||||
|
||||
finally_env.set_exit_address(exit); |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `FinallyEnd` implements the Opcode Operation for `Opcode::FinallyEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyEnd; |
||||
|
||||
impl Operation for FinallyEnd { |
||||
const NAME: &'static str = "FinallyEnd"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let finally_candidates = context.vm.frame().env_stack.iter().filter(|env| { |
||||
env.is_finally_env() && context.vm.frame().pc < (env.start_address() as usize) |
||||
}); |
||||
|
||||
let next_finally = match finally_candidates.last() { |
||||
Some(env) => env.start_address(), |
||||
_ => u32::MAX, |
||||
}; |
||||
|
||||
let abrupt_record = context.vm.frame_mut().abrupt_completion; |
||||
match context.vm.frame_mut().finally_return { |
||||
FinallyReturn::None => { |
||||
// Check if there is an `AbruptCompletionRecord`.
|
||||
if let Some(record) = abrupt_record { |
||||
let mut envs_to_pop = 0; |
||||
if next_finally < record.target() { |
||||
context.vm.frame_mut().pc = next_finally as usize; |
||||
|
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if next_finally <= env_entry.exit_address() { |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
} else if record.is_break() && context.vm.frame().pc < record.target() as usize |
||||
{ |
||||
// handle the continuation of an abrupt break.
|
||||
context.vm.frame_mut().pc = record.target() as usize; |
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if record.target() == env_entry.exit_address() { |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
} else if record.is_continue() |
||||
&& context.vm.frame().pc > record.target() as usize |
||||
{ |
||||
// Handle the continuation of an abrupt continue
|
||||
context.vm.frame_mut().pc = record.target() as usize; |
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if env_entry.start_address() == record.target() { |
||||
break; |
||||
} |
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
} |
||||
|
||||
let env_truncation_len = |
||||
context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
} else { |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
Ok(ShouldExit::False) |
||||
} |
||||
FinallyReturn::Ok => Ok(ShouldExit::True), |
||||
FinallyReturn::Err => { |
||||
if let Some(record) = abrupt_record { |
||||
let mut envs_to_pop = 0; |
||||
if next_finally < record.target() { |
||||
context.vm.frame_mut().pc = next_finally as usize; |
||||
|
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if next_finally <= env_entry.exit_address() { |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
} else if record.is_throw_with_target() |
||||
&& context.vm.frame().pc < record.target() as usize |
||||
{ |
||||
context.vm.frame_mut().pc = record.target() as usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
if env_entry.start_address() == record.target() { |
||||
break; |
||||
} |
||||
} |
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
} else if !record.is_throw_with_target() { |
||||
let current_stack = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.pop() |
||||
.expect("Popping current finally stack."); |
||||
|
||||
let env_truncation_len = context |
||||
.realm |
||||
.environments |
||||
.len() |
||||
.saturating_sub(current_stack.env_num()); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
return Err(JsError::from_opaque(context.vm.pop())); |
||||
} |
||||
|
||||
let env_truncation_len = |
||||
context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
return Ok(ShouldExit::False); |
||||
} |
||||
let current_stack = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.pop() |
||||
.expect("Popping current finally stack."); |
||||
|
||||
let env_truncation_len = context |
||||
.realm |
||||
.environments |
||||
.len() |
||||
.saturating_sub(current_stack.env_num()); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
Err(JsError::from_opaque(context.vm.pop())) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
use crate::{ |
||||
vm::{call_frame::EnvStackEntry, opcode::Operation, ShouldExit}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `LabelledStart` implements the Opcode Operation for `Opcode::LabelledStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a labelled block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LabelledStart; |
||||
|
||||
impl Operation for LabelledStart { |
||||
const NAME: &'static str = "LabelledStart"; |
||||
const INSTRUCTION: &'static str = "INST - LabelledStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let start = context.vm.frame().pc as u32 - 1; |
||||
let end = context.vm.read::<u32>(); |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.push(EnvStackEntry::new(start, end).with_labelled_flag()); |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `LabelledEnd` implements the Opcode Operation for `Opcode::LabelledEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Clean up environments at the end of labelled block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LabelledEnd; |
||||
|
||||
impl Operation for LabelledEnd { |
||||
const NAME: &'static str = "LabelledEnd"; |
||||
const INSTRUCTION: &'static str = "INST - LabelledEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_labelled_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -0,0 +1,11 @@
|
||||
pub(crate) mod r#break; |
||||
pub(crate) mod catch; |
||||
pub(crate) mod finally; |
||||
pub(crate) mod labelled; |
||||
pub(crate) mod r#try; |
||||
|
||||
pub(crate) use catch::*; |
||||
pub(crate) use finally::*; |
||||
pub(crate) use labelled::*; |
||||
pub(crate) use r#break::*; |
||||
pub(crate) use r#try::*; |
@ -0,0 +1,83 @@
|
||||
use crate::{ |
||||
vm::{ |
||||
call_frame::{EnvStackEntry, FinallyAddresses}, |
||||
opcode::Operation, |
||||
FinallyReturn, ShouldExit, |
||||
}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `TryStart` implements the Opcode Operation for `Opcode::TryStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a try block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryStart; |
||||
|
||||
impl Operation for TryStart { |
||||
const NAME: &'static str = "TryStart"; |
||||
const INSTRUCTION: &'static str = "INST - TryStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let catch = context.vm.read::<u32>(); |
||||
let finally = context.vm.read::<u32>(); |
||||
|
||||
// If a finally exists, push the env to the stack before the try.
|
||||
if finally != u32::MAX { |
||||
context.vm.frame_mut().env_stack.push( |
||||
EnvStackEntry::default() |
||||
.with_finally_flag() |
||||
.with_start_address(finally), |
||||
); |
||||
} |
||||
|
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.push(EnvStackEntry::new(catch, finally).with_try_flag()); |
||||
|
||||
let finally = if finally == u32::MAX { |
||||
None |
||||
} else { |
||||
Some(finally) |
||||
}; |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.try_catch |
||||
.push(FinallyAddresses::new(finally)); |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `TryEnd` implements the Opcode Operation for `Opcode::TryEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a try block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryEnd; |
||||
|
||||
impl Operation for TryEnd { |
||||
const NAME: &'static str = "TryEnd"; |
||||
const INSTRUCTION: &'static str = "INST - TryEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
context.vm.frame_mut().try_catch.pop(); |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_try_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop); |
||||
context.realm.environments.truncate(env_truncation_len); |
||||
|
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -1,44 +0,0 @@
|
||||
use crate::{ |
||||
vm::{opcode::Operation, ShouldExit}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `Break` implements the Opcode Operation for `Opcode::Break`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Pop required environments and jump to address.
|
||||
pub(crate) struct Break; |
||||
|
||||
impl Operation for Break { |
||||
const NAME: &'static str = "Break"; |
||||
const INSTRUCTION: &'static str = "INST - Break"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let address = context.vm.read::<u32>(); |
||||
let pop_envs = context.vm.read::<u32>(); |
||||
|
||||
for _ in 0..pop_envs { |
||||
context.realm.environments.pop(); |
||||
|
||||
let loop_envs = *context |
||||
.vm |
||||
.frame() |
||||
.loop_env_stack |
||||
.last() |
||||
.expect("loop env stack must exist"); |
||||
if loop_envs == 0 { |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.loop_env_stack |
||||
.pop() |
||||
.expect("loop env stack must exist"); |
||||
} |
||||
|
||||
context.vm.frame_mut().loop_env_stack_dec(); |
||||
context.vm.frame_mut().try_env_stack_dec(); |
||||
} |
||||
context.vm.frame_mut().pc = address as usize; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -1,80 +0,0 @@
|
||||
use crate::{ |
||||
vm::{opcode::Operation, FinallyReturn, ShouldExit}, |
||||
Context, JsError, JsResult, |
||||
}; |
||||
|
||||
/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyStart; |
||||
|
||||
impl Operation for FinallyStart { |
||||
const NAME: &'static str = "FinallyStart"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
*context |
||||
.vm |
||||
.frame_mut() |
||||
.finally_jump |
||||
.last_mut() |
||||
.expect("finally jump must exist here") = None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `FinallyEnd` implements the Opcode Operation for `Opcode::FinallyEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyEnd; |
||||
|
||||
impl Operation for FinallyEnd { |
||||
const NAME: &'static str = "FinallyEnd"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let address = context |
||||
.vm |
||||
.frame_mut() |
||||
.finally_jump |
||||
.pop() |
||||
.expect("finally jump must exist here"); |
||||
match context.vm.frame_mut().finally_return { |
||||
FinallyReturn::None => { |
||||
if let Some(address) = address { |
||||
context.vm.frame_mut().pc = address as usize; |
||||
} |
||||
Ok(ShouldExit::False) |
||||
} |
||||
FinallyReturn::Ok => Ok(ShouldExit::True), |
||||
FinallyReturn::Err => Err(JsError::from_opaque(context.vm.pop())), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// `FinallySetJump` implements the Opcode Operation for `Opcode::FinallySetJump`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Set the address for a finally jump.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallySetJump; |
||||
|
||||
impl Operation for FinallySetJump { |
||||
const NAME: &'static str = "FinallySetJump"; |
||||
const INSTRUCTION: &'static str = "INST - FinallySetJump"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let address = context.vm.read::<u32>(); |
||||
*context |
||||
.vm |
||||
.frame_mut() |
||||
.finally_jump |
||||
.last_mut() |
||||
.expect("finally jump must exist here") = Some(address); |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
@ -1,164 +0,0 @@
|
||||
use crate::{ |
||||
vm::{opcode::Operation, CatchAddresses, FinallyReturn, ShouldExit, TryStackEntry}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `TryStart` implements the Opcode Operation for `Opcode::TryStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a try block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryStart; |
||||
|
||||
impl Operation for TryStart { |
||||
const NAME: &'static str = "TryStart"; |
||||
const INSTRUCTION: &'static str = "INST - TryStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let next = context.vm.read::<u32>(); |
||||
let finally = context.vm.read::<u32>(); |
||||
let finally = if finally == 0 { None } else { Some(finally) }; |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.catch |
||||
.push(CatchAddresses { next, finally }); |
||||
context.vm.frame_mut().finally_jump.push(None); |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
context.vm.frame_mut().try_env_stack.push(TryStackEntry { |
||||
num_env: 0, |
||||
num_loop_stack_entries: 0, |
||||
}); |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `TryEnd` implements the Opcode Operation for `Opcode::TryEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a try block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryEnd; |
||||
|
||||
impl Operation for TryEnd { |
||||
const NAME: &'static str = "TryEnd"; |
||||
const INSTRUCTION: &'static str = "INST - TryEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
context.vm.frame_mut().catch.pop(); |
||||
let try_stack_entry = context |
||||
.vm |
||||
.frame_mut() |
||||
.try_env_stack |
||||
.pop() |
||||
.expect("must exist"); |
||||
for _ in 0..try_stack_entry.num_env { |
||||
context.realm.environments.pop(); |
||||
} |
||||
let mut num_env = try_stack_entry.num_env; |
||||
for _ in 0..try_stack_entry.num_loop_stack_entries { |
||||
num_env -= context |
||||
.vm |
||||
.frame_mut() |
||||
.loop_env_stack |
||||
.pop() |
||||
.expect("must exist"); |
||||
} |
||||
*context |
||||
.vm |
||||
.frame_mut() |
||||
.loop_env_stack |
||||
.last_mut() |
||||
.expect("must exist") -= num_env; |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `CatchStart` implements the Opcode Operation for `Opcode::CatchStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a catch block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchStart; |
||||
|
||||
impl Operation for CatchStart { |
||||
const NAME: &'static str = "CatchStart"; |
||||
const INSTRUCTION: &'static str = "INST - CatchStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let finally = context.vm.read::<u32>(); |
||||
context.vm.frame_mut().catch.push(CatchAddresses { |
||||
next: finally, |
||||
finally: Some(finally), |
||||
}); |
||||
context.vm.frame_mut().try_env_stack.push(TryStackEntry { |
||||
num_env: 0, |
||||
num_loop_stack_entries: 0, |
||||
}); |
||||
context.vm.frame_mut().thrown = false; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `CatchEnd` implements the Opcode Operation for `Opcode::CatchEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a catch block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchEnd; |
||||
|
||||
impl Operation for CatchEnd { |
||||
const NAME: &'static str = "CatchEnd"; |
||||
const INSTRUCTION: &'static str = "INST - CatchEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
context.vm.frame_mut().catch.pop(); |
||||
let try_stack_entry = context |
||||
.vm |
||||
.frame_mut() |
||||
.try_env_stack |
||||
.pop() |
||||
.expect("must exist"); |
||||
for _ in 0..try_stack_entry.num_env { |
||||
context.realm.environments.pop(); |
||||
} |
||||
let mut num_env = try_stack_entry.num_env; |
||||
for _ in 0..try_stack_entry.num_loop_stack_entries { |
||||
num_env -= context |
||||
.vm |
||||
.frame_mut() |
||||
.loop_env_stack |
||||
.pop() |
||||
.expect("must exist"); |
||||
} |
||||
*context |
||||
.vm |
||||
.frame_mut() |
||||
.loop_env_stack |
||||
.last_mut() |
||||
.expect("must exist") -= num_env; |
||||
context.vm.frame_mut().finally_return = FinallyReturn::None; |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
||||
|
||||
/// `CatchEnd2` implements the Opcode Operation for `Opcode::CatchEnd2`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a catch block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct CatchEnd2; |
||||
|
||||
impl Operation for CatchEnd2 { |
||||
const NAME: &'static str = "CatchEnd2"; |
||||
const INSTRUCTION: &'static str = "INST - CatchEnd2"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { |
||||
let frame = context.vm.frame_mut(); |
||||
if frame.finally_return == FinallyReturn::Err { |
||||
frame.finally_return = FinallyReturn::None; |
||||
} |
||||
Ok(ShouldExit::False) |
||||
} |
||||
} |
Loading…
Reference in new issue