Skip to content

Commit

Permalink
Add err-register and preserve JsNativeErrors
Browse files Browse the repository at this point in the history
  • Loading branch information
nekevss committed Mar 4, 2023
1 parent a4a3dac commit 59ddcaf
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 34 deletions.
14 changes: 1 addition & 13 deletions boa_engine/src/vm/completion_record.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{vm::CompletionType, JsError, JsResult, JsValue};
use crate::{JsError, JsResult, JsValue};

#[derive(Debug, Clone)]
pub(crate) enum CompletionRecord {
Expand All @@ -7,18 +7,6 @@ pub(crate) enum CompletionRecord {
Throw(JsError),
}

// ---- CompletionRecord creation and destruction methods ----
impl CompletionRecord {
/// Create a new `CompletionRecord` with a provided `CompletionType` and `JsValue`.
pub(crate) const fn new(completion_type: CompletionType, value: JsValue) -> Self {
match completion_type {
CompletionType::Normal => Self::Normal(value),
CompletionType::Return => Self::Return(value),
CompletionType::Throw => Self::Throw(JsError::from_opaque(value)),
}
}
}

// ---- `CompletionRecord` methods ----
impl CompletionRecord {
pub(crate) const fn is_throw_completion(&self) -> bool {
Expand Down
44 changes: 29 additions & 15 deletions boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod tests;
pub struct Vm {
pub(crate) frames: Vec<CallFrame>,
pub(crate) stack: Vec<JsValue>,
pub(crate) err: Option<JsError>,
pub(crate) trace: bool,
pub(crate) stack_size_limit: usize,
}
Expand All @@ -48,6 +49,7 @@ impl Default for Vm {
Self {
frames: Vec::with_capacity(16),
stack: Vec::with_capacity(1024),
err: None,
trace: false,
stack_size_limit: 1024,
}
Expand Down Expand Up @@ -193,9 +195,8 @@ impl Context<'_> {
#[cfg(feature = "fuzz")]
{
if self.instructions_remaining == 0 {
let err = JsError::from_native(JsNativeError::no_instructions_remain())
.to_opaque(self);
self.vm.push(err);
let err = JsError::from_native(JsNativeError::no_instructions_remain());
self.vm.err = Some(err);
break CompletionType::Throw;
}
self.instructions_remaining -= 1;
Expand Down Expand Up @@ -255,15 +256,13 @@ impl Context<'_> {
// (Rust) caller instead of trying to handle as an exception.
if matches!(native_error.kind, JsNativeErrorKind::NoInstructionsRemain)
{
let error = native_error.to_opaque(self);
self.vm.push(error);
self.vm.err = Some(err);
break CompletionType::Throw;
}
}
}

let err_value = err.to_opaque(self);
self.vm.push(err_value);
self.vm.err = Some(err);

// If this frame has not evaluated the throw as an AbruptCompletion, then evaluate it
let evaluation = Opcode::Throw
Expand All @@ -286,12 +285,12 @@ impl Context<'_> {
let result = self.vm.pop();
self.vm.stack.truncate(self.vm.frame().fp);
self.vm.frame_mut().early_return = None;
return CompletionRecord::new(CompletionType::Normal, result);
return CompletionRecord::Normal(result);
}
EarlyReturnType::Yield => {
let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined);
self.vm.frame_mut().early_return = None;
return CompletionRecord::new(CompletionType::Return, result);
return CompletionRecord::Return(result);
}
}
}
Expand Down Expand Up @@ -320,7 +319,11 @@ impl Context<'_> {
}

// Determine the execution result
let execution_result = if execution_completion != CompletionType::Normal {
let execution_result = if execution_completion == CompletionType::Throw {
self.vm.frame_mut().abrupt_completion = None;
self.vm.stack.truncate(self.vm.frame().fp);
JsValue::undefined()
} else if execution_completion == CompletionType::Return {
self.vm.frame_mut().abrupt_completion = None;
let result = self.vm.pop();
self.vm.stack.truncate(self.vm.frame().fp);
Expand Down Expand Up @@ -349,10 +352,12 @@ impl Context<'_> {
.expect("cannot fail per spec");
}
CompletionType::Throw => {
let err = self.vm.err.take().expect("Take must exist on a Throw");
promise
.reject()
.call(&JsValue::undefined(), &[execution_result.clone()], self)
.call(&JsValue::undefined(), &[err.to_opaque(self)], self)
.expect("cannot fail per spec");
self.vm.err = Some(err);
}
}
} else if let Some(generator_object) = self.vm.frame().async_generator.clone() {
Expand All @@ -372,7 +377,11 @@ impl Context<'_> {
if execution_completion == CompletionType::Throw {
AsyncGenerator::complete_step(
&next,
Err(JsError::from_opaque(execution_result)),
Err(self
.vm
.err
.take()
.expect("err must exist on a Completion::Throw")),
true,
self,
);
Expand All @@ -385,9 +394,14 @@ impl Context<'_> {
}

// Any valid return statement is re-evaluated as a normal completion vs. return (yield).
if execution_completion == CompletionType::Return {
return CompletionRecord::new(CompletionType::Normal, execution_result);
if execution_completion == CompletionType::Throw {
return CompletionRecord::Throw(
self.vm
.err
.take()
.expect("Err must exist for a CompletionType::Throw"),
);
}
CompletionRecord::new(execution_completion, execution_result)
CompletionRecord::Normal(execution_result)
}
}
4 changes: 3 additions & 1 deletion boa_engine/src/vm/opcode/control_flow/finally.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsResult,
Context, JsError, JsResult,
};

/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart`
Expand Down Expand Up @@ -140,6 +140,8 @@ impl Operation for FinallyEnd {
.saturating_sub(current_stack.env_num());
context.realm.environments.truncate(env_truncation_len);

let err = JsError::from_opaque(context.vm.pop());
context.vm.err = Some(err);
return Ok(CompletionType::Throw);
}
_ => {
Expand Down
16 changes: 11 additions & 5 deletions boa_engine/src/vm/opcode/control_flow/throw.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
Context, JsResult,
Context, JsError, JsResult,
};

/// `Throw` implements the Opcode Operation for `Opcode::Throw`
Expand All @@ -15,7 +15,11 @@ impl Operation for Throw {
const INSTRUCTION: &'static str = "INST - Throw";

fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let error = context.vm.pop();
let error = if let Some(err) = context.vm.err.take() {
err
} else {
JsError::from_opaque(context.vm.pop())
};
// 1. Find the viable catch and finally blocks
let current_address = context.vm.frame().pc;
let viable_catch_candidates = context
Expand Down Expand Up @@ -71,7 +75,8 @@ impl Operation for Throw {
context.vm.frame_mut().pop_on_return = 0;
let record = AbruptCompletionRecord::new_throw().with_initial_target(catch_target);
context.vm.frame_mut().abrupt_completion = Some(record);
context.vm.push(error);
let err = error.to_opaque(context);
context.vm.push(err);
return Ok(CompletionType::Normal);
}

Expand Down Expand Up @@ -117,11 +122,12 @@ impl Operation for Throw {
context.vm.frame_mut().pop_on_return = 0;

context.vm.frame_mut().pc = address;
context.vm.push(error);
let err = error.to_opaque(context);
context.vm.push(err);
return Ok(CompletionType::Normal);
}

context.vm.push(error);
context.vm.err = Some(error);
Ok(CompletionType::Throw)
}
}

0 comments on commit 59ddcaf

Please sign in to comment.