Skip to content

Commit

Permalink
Make RuntimeLimit error non-catchable from JavaScript
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Apr 26, 2023
1 parent 5c85c62 commit b108f29
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 5 deletions.
98 changes: 98 additions & 0 deletions boa_engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,10 +458,17 @@ impl JsNativeError {
/// ));
/// ```
#[must_use]
#[inline]
pub fn aggregate(errors: Vec<JsError>) -> Self {
Self::new(JsNativeErrorKind::Aggregate(errors), Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Aggregate`].
#[inline]
pub const fn is_aggregate(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Aggregate(_))
}

/// Creates a new `JsNativeError` of kind `Error`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -473,10 +480,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Error));
/// ```
#[must_use]
#[inline]
pub fn error() -> Self {
Self::new(JsNativeErrorKind::Error, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Error`].
#[inline]
pub const fn is_error(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Error)
}

/// Creates a new `JsNativeError` of kind `EvalError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -488,10 +502,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Eval));
/// ```
#[must_use]
#[inline]
pub fn eval() -> Self {
Self::new(JsNativeErrorKind::Eval, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Eval`].
#[inline]
pub const fn is_eval(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Eval)
}

/// Creates a new `JsNativeError` of kind `RangeError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -503,10 +524,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Range));
/// ```
#[must_use]
#[inline]
pub fn range() -> Self {
Self::new(JsNativeErrorKind::Range, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Range`].
#[inline]
pub const fn is_range(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Range)
}

/// Creates a new `JsNativeError` of kind `ReferenceError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -518,10 +546,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Reference));
/// ```
#[must_use]
#[inline]
pub fn reference() -> Self {
Self::new(JsNativeErrorKind::Reference, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Reference`].
#[inline]
pub const fn is_reference(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Reference)
}

/// Creates a new `JsNativeError` of kind `SyntaxError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -533,10 +568,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Syntax));
/// ```
#[must_use]
#[inline]
pub fn syntax() -> Self {
Self::new(JsNativeErrorKind::Syntax, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Syntax`].
#[inline]
pub const fn is_syntax(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Syntax)
}

/// Creates a new `JsNativeError` of kind `TypeError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -548,10 +590,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Type));
/// ```
#[must_use]
#[inline]
pub fn typ() -> Self {
Self::new(JsNativeErrorKind::Type, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Type`].
#[inline]
pub const fn is_type(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Type)
}

/// Creates a new `JsNativeError` of kind `UriError`, with empty `message` and undefined `cause`.
///
/// # Examples
Expand All @@ -563,10 +612,17 @@ impl JsNativeError {
/// assert!(matches!(error.kind, JsNativeErrorKind::Uri));
/// ```
#[must_use]
#[inline]
pub fn uri() -> Self {
Self::new(JsNativeErrorKind::Uri, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::Uri`].
#[inline]
pub const fn is_uri(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::Uri)
}

/// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This
/// is only used in a fuzzing context.
#[cfg(feature = "fuzz")]
Expand All @@ -579,6 +635,28 @@ impl JsNativeError {
)
}

/// Check if it's a [`JsNativeErrorKind::NoInstructionsRemain`].
#[inline]
#[cfg(feature = "fuzz")]
pub const fn is_no_instructions_remain(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::NoInstructionsRemain)
}

/// Creates a new `JsNativeError` that indicates that the context exceeded the runtime limits.
#[must_use]
#[inline]
#[cfg(feature = "runtime-limits")]
pub fn runtime_limit() -> Self {
Self::new(JsNativeErrorKind::RuntimeLimit, Box::default(), None)
}

/// Check if it's a [`JsNativeErrorKind::RuntimeLimit`].
#[inline]
#[cfg(feature = "runtime-limits")]
pub const fn is_runtime_limit(&self) -> bool {
matches!(self.kind, JsNativeErrorKind::RuntimeLimit)
}

/// Sets the message of this error.
///
/// # Examples
Expand All @@ -590,6 +668,7 @@ impl JsNativeError {
/// assert_eq!(error.message(), "number too large");
/// ```
#[must_use]
#[inline]
pub fn with_message<S>(mut self, message: S) -> Self
where
S: Into<Box<str>>,
Expand All @@ -610,6 +689,7 @@ impl JsNativeError {
/// assert!(error.cause().unwrap().as_native().is_some());
/// ```
#[must_use]
#[inline]
pub fn with_cause<V>(mut self, cause: V) -> Self
where
V: Into<JsError>,
Expand All @@ -634,6 +714,7 @@ impl JsNativeError {
/// assert_eq!(error.message(), "number too large");
/// ```
#[must_use]
#[inline]
pub const fn message(&self) -> &str {
&self.message
}
Expand All @@ -655,6 +736,7 @@ impl JsNativeError {
/// assert!(error.cause().unwrap().as_native().is_some());
/// ```
#[must_use]
#[inline]
pub fn cause(&self) -> Option<&JsError> {
self.cause.as_deref()
}
Expand All @@ -673,6 +755,11 @@ impl JsNativeError {
/// assert!(error_obj.borrow().is_error());
/// assert_eq!(error_obj.get("message", context).unwrap(), "error!".into())
/// ```
///
/// # Panics
///
/// If the converting a [`JsNativeErrorKind::RuntimeLimit`] to an opaque object.
#[inline]
pub fn to_opaque(&self, context: &mut Context<'_>) -> JsObject {
let Self {
kind,
Expand Down Expand Up @@ -707,6 +794,10 @@ impl JsNativeError {
"The NoInstructionsRemain native error cannot be converted to an opaque type."
)
}
#[cfg(feature = "runtime-limits")]
JsNativeErrorKind::RuntimeLimit => {
panic!("The RuntimeLimit native error cannot be converted to an opaque type.")
}
};

let o = JsObject::from_proto_and_data_with_shared_shape(
Expand Down Expand Up @@ -845,10 +936,15 @@ pub enum JsNativeErrorKind {
/// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
/// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
Uri,

/// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS
/// error variant.
#[cfg(feature = "fuzz")]
NoInstructionsRemain,

/// Error thrown when a runtime limit is exceeded. It's not a valid JS error variant.
#[cfg(feature = "runtime-limits")]
RuntimeLimit,
}

impl PartialEq<ErrorKind> for JsNativeErrorKind {
Expand Down Expand Up @@ -880,6 +976,8 @@ impl std::fmt::Display for JsNativeErrorKind {
Self::Uri => "UriError",
#[cfg(feature = "fuzz")]
Self::NoInstructionsRemain => "NoInstructionsRemain",
#[cfg(feature = "runtime-limits")]
Self::RuntimeLimit => "RuntimeLimit",
}
.fmt(f)
}
Expand Down
20 changes: 16 additions & 4 deletions boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
//! This module will provide an instruction set for the AST to use, various traits,
//! plus an interpreter to execute those instructions
#[cfg(feature = "fuzz")]
use crate::JsNativeError;
use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
environments::{DeclarativeEnvironment, DeclarativeEnvironmentStack},
vm::code_block::Readable,
Context, JsError, JsObject, JsResult, JsValue,
};
#[cfg(feature = "fuzz")]
use crate::{JsNativeError, JsNativeErrorKind};

use boa_gc::Gc;
use boa_profiler::Profiler;
use std::{convert::TryInto, mem::size_of};
Expand Down Expand Up @@ -273,8 +274,19 @@ impl Context<'_> {
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if matches!(native_error.kind, JsNativeErrorKind::NoInstructionsRemain)
{
if native_error.is_no_instructions_remain() {
self.vm.err = Some(err);
break CompletionType::Throw;
}
}
}

#[cfg(feature = "runtime-limits")]
{
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if native_error.is_runtime_limit() {
self.vm.err = Some(err);
break CompletionType::Throw;
}
Expand Down
2 changes: 1 addition & 1 deletion boa_engine/src/vm/opcode/iteration/loop_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl Operation for LoopContinue {
{
let max = context.vm.runtime_limits.loop_iteration_limit();
if iteration_count >= max {
return Err(JsNativeError::range()
return Err(JsNativeError::runtime_limit()
.with_message(format!("max loop iteration limit {max} exceeded"))
.into());
}
Expand Down

0 comments on commit b108f29

Please sign in to comment.