Browse Source

Fix detection of runtime limits for accessors (#3335)

* Fix detection of runtime limits for function calls

* Fix tests

* Add additional checks and extract function

* cargo fmt

* Fix build
pull/3347/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
acdc5f4500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      boa_engine/src/object/internal_methods/function.rs
  2. 2
      boa_engine/src/vm/code_block.rs
  3. 22
      boa_engine/src/vm/mod.rs
  4. 54
      boa_engine/src/vm/opcode/call/mod.rs
  5. 26
      boa_engine/src/vm/opcode/new/mod.rs
  6. 4
      boa_engine/src/vm/tests.rs

6
boa_engine/src/object/internal_methods/function.rs

@ -89,6 +89,9 @@ pub(crate) fn native_function_call(
args: &[JsValue], args: &[JsValue],
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone(); let this_function_object = obj.clone();
let object = obj.borrow(); let object = obj.borrow();
@ -135,6 +138,9 @@ fn native_function_construct(
new_target: &JsObject, new_target: &JsObject,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone(); let this_function_object = obj.clone();
let object = obj.borrow(); let object = obj.borrow();

2
boa_engine/src/vm/code_block.rs

@ -1014,6 +1014,7 @@ impl JsObject {
args: &[JsValue], args: &[JsValue],
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
context.check_runtime_limits()?;
let old_realm = context.realm().clone(); let old_realm = context.realm().clone();
let context = &mut context.guard(move |ctx| { let context = &mut context.guard(move |ctx| {
@ -1152,6 +1153,7 @@ impl JsObject {
this_target: &JsValue, this_target: &JsValue,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Self> { ) -> JsResult<Self> {
context.check_runtime_limits()?;
let old_realm = context.realm().clone(); let old_realm = context.realm().clone();
let context = &mut context.guard(move |ctx| { let context = &mut context.guard(move |ctx| {
ctx.enter_realm(old_realm); ctx.enter_realm(old_realm);

22
boa_engine/src/vm/mod.rs

@ -4,13 +4,11 @@
//! This module will provide an instruction set for the AST to use, various traits, //! This module will provide an instruction set for the AST to use, various traits,
//! plus an interpreter to execute those instructions //! plus an interpreter to execute those instructions
#[cfg(feature = "fuzz")]
use crate::JsNativeError;
use crate::{ use crate::{
environments::{DeclarativeEnvironment, EnvironmentStack}, environments::{DeclarativeEnvironment, EnvironmentStack},
script::Script, script::Script,
vm::code_block::Readable, vm::code_block::Readable,
Context, JsError, JsNativeErrorKind, JsObject, JsResult, JsValue, Module, Context, JsError, JsNativeError, JsNativeErrorKind, JsObject, JsResult, JsValue, Module,
}; };
use boa_gc::{custom_trace, Finalize, Gc, Trace}; use boa_gc::{custom_trace, Finalize, Gc, Trace};
@ -412,4 +410,22 @@ impl Context<'_> {
} }
} }
} }
/// Checks if we haven't exceeded the defined runtime limits.
pub(crate) fn check_runtime_limits(&self) -> JsResult<()> {
// Must throw if the number of recursive calls exceeds the defined limit.
if self.vm.runtime_limits.recursion_limit() <= self.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message("exceeded maximum number of recursive calls")
.into());
}
// Must throw if the stack size exceeds the defined maximum length.
if self.vm.runtime_limits.stack_size_limit() <= self.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("exceeded maximum call stack length")
.into());
}
Ok(())
}
} }

54
boa_engine/src/vm/opcode/call/mod.rs

@ -16,19 +16,6 @@ pub(crate) struct CallEval;
impl CallEval { impl CallEval {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
let mut arguments = Vec::with_capacity(argument_count); let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count { for _ in 0..argument_count {
arguments.push(context.vm.pop()); arguments.push(context.vm.pop());
@ -95,20 +82,6 @@ impl Operation for CallEvalSpread {
const INSTRUCTION: &'static str = "INST - CallEvalSpread"; const INSTRUCTION: &'static str = "INST - CallEvalSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
// Get the arguments that are stored as an array object on the stack. // Get the arguments that are stored as an array object on the stack.
let arguments_array = context.vm.pop(); let arguments_array = context.vm.pop();
let arguments_array_object = arguments_array let arguments_array_object = arguments_array
@ -158,19 +131,6 @@ pub(crate) struct Call;
impl Call { impl Call {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
let mut arguments = Vec::with_capacity(argument_count); let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count { for _ in 0..argument_count {
arguments.push(context.vm.pop()); arguments.push(context.vm.pop());
@ -221,20 +181,6 @@ impl Operation for CallSpread {
const INSTRUCTION: &'static str = "INST - CallSpread"; const INSTRUCTION: &'static str = "INST - CallSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
// Get the arguments that are stored as an array object on the stack. // Get the arguments that are stored as an array object on the stack.
let arguments_array = context.vm.pop(); let arguments_array = context.vm.pop();
let arguments_array_object = arguments_array let arguments_array_object = arguments_array

26
boa_engine/src/vm/opcode/new/mod.rs

@ -13,19 +13,6 @@ pub(crate) struct New;
impl New { impl New {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
let mut arguments = Vec::with_capacity(argument_count); let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count { for _ in 0..argument_count {
arguments.push(context.vm.pop()); arguments.push(context.vm.pop());
@ -79,19 +66,6 @@ impl Operation for NewSpread {
const INSTRUCTION: &'static str = "INST - NewSpread"; const INSTRUCTION: &'static str = "INST - NewSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.runtime_limits.recursion_limit() <= context.vm.frames.len() {
return Err(JsNativeError::runtime_limit()
.with_message(format!(
"Maximum recursion limit {} exceeded",
context.vm.runtime_limits.recursion_limit()
))
.into());
}
if context.vm.runtime_limits.stack_size_limit() <= context.vm.stack.len() {
return Err(JsNativeError::runtime_limit()
.with_message("Maximum call stack size exceeded")
.into());
}
// Get the arguments that are stored as an array object on the stack. // Get the arguments that are stored as an array object on the stack.
let arguments_array = context.vm.pop(); let arguments_array = context.vm.pop();
let arguments_array_object = arguments_array let arguments_array_object = arguments_array

4
boa_engine/src/vm/tests.rs

@ -293,7 +293,7 @@ fn recursion_runtime_limit() {
TestAction::assert_native_error( TestAction::assert_native_error(
"factorial(11)", "factorial(11)",
JsNativeErrorKind::RuntimeLimit, JsNativeErrorKind::RuntimeLimit,
"Maximum recursion limit 10 exceeded", "exceeded maximum number of recursive calls",
), ),
TestAction::assert_eq("factorial(8)", JsValue::new(40_320)), TestAction::assert_eq("factorial(8)", JsValue::new(40_320)),
TestAction::assert_native_error( TestAction::assert_native_error(
@ -305,7 +305,7 @@ fn recursion_runtime_limit() {
x() x()
"#}, "#},
JsNativeErrorKind::RuntimeLimit, JsNativeErrorKind::RuntimeLimit,
"Maximum recursion limit 10 exceeded", "exceeded maximum number of recursive calls",
), ),
]); ]);
} }

Loading…
Cancel
Save