From ca6cc9b460b22321d7f7c5cae2d2dcc9eecd19ad Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Wed, 26 Apr 2023 07:44:03 +0200 Subject: [PATCH] Make `RuntimeLimit` error non-catchable from JavaScript --- boa_engine/src/error.rs | 98 +++++++++++++++++++ boa_engine/src/vm/call_frame/env_stack.rs | 2 +- boa_engine/src/vm/mod.rs | 20 +++- .../src/vm/opcode/iteration/loop_ops.rs | 7 +- 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 280449d076a..dca9da6be31 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -458,10 +458,17 @@ impl JsNativeError { /// )); /// ``` #[must_use] + #[inline] pub fn aggregate(errors: Vec) -> 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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")] @@ -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 @@ -590,6 +668,7 @@ impl JsNativeError { /// assert_eq!(error.message(), "number too large"); /// ``` #[must_use] + #[inline] pub fn with_message(mut self, message: S) -> Self where S: Into>, @@ -610,6 +689,7 @@ impl JsNativeError { /// assert!(error.cause().unwrap().as_native().is_some()); /// ``` #[must_use] + #[inline] pub fn with_cause(mut self, cause: V) -> Self where V: Into, @@ -634,6 +714,7 @@ impl JsNativeError { /// assert_eq!(error.message(), "number too large"); /// ``` #[must_use] + #[inline] pub const fn message(&self) -> &str { &self.message } @@ -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() } @@ -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, @@ -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( @@ -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 for JsNativeErrorKind { @@ -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) } diff --git a/boa_engine/src/vm/call_frame/env_stack.rs b/boa_engine/src/vm/call_frame/env_stack.rs index fc71aaac64c..eabd16b0c9f 100644 --- a/boa_engine/src/vm/call_frame/env_stack.rs +++ b/boa_engine/src/vm/call_frame/env_stack.rs @@ -134,7 +134,7 @@ impl EnvStackEntry { /// Returns true if an `EnvStackEntry` is a loop #[cfg(not(feature = "runtime-limits"))] pub(crate) fn is_loop_env(&self) -> bool { - self.kind == EnvEntryKind::Loop + self.kind == EnvEntryKind::Loop {} } /// Returns true if an `EnvStackEntry` is a try block diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index e7260981170..a41380777b0 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -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}; @@ -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; } diff --git a/boa_engine/src/vm/opcode/iteration/loop_ops.rs b/boa_engine/src/vm/opcode/iteration/loop_ops.rs index 5559154a189..649f3c1fe1f 100644 --- a/boa_engine/src/vm/opcode/iteration/loop_ops.rs +++ b/boa_engine/src/vm/opcode/iteration/loop_ops.rs @@ -1,8 +1,11 @@ use crate::{ vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType}, - Context, JsNativeError, JsResult, + Context, JsResult, }; +#[cfg(feature = "runtime-limits")] +use crate::JsNativeError; + /// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart` /// /// Operation: @@ -79,7 +82,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()); }