diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 07a97fd30c..b6c903643a 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -63,7 +63,8 @@ Interpreter::~Interpreter() Value Interpreter::run(GlobalObject& global_object, const Program& program) { - ASSERT(!vm().exception()); + auto& vm = this->vm(); + ASSERT(!vm.exception()); VM::InterpreterExecutionScope scope(*this); @@ -73,13 +74,12 @@ Value Interpreter::run(GlobalObject& global_object, const Program& program) global_call_frame.function_name = global_execution_context_name; global_call_frame.environment = heap().allocate(global_object, LexicalEnvironment::EnvironmentRecordType::Global); global_call_frame.environment->bind_this_value(global_object, &global_object); + ASSERT(!vm.exception()); global_call_frame.is_strict_mode = program.is_strict_mode(); - if (vm().exception()) - return {}; - - vm().push_call_frame(global_call_frame); + vm.push_call_frame(global_call_frame, global_object); + ASSERT(!vm.exception()); auto result = program.execute(*this, global_object); - vm().pop_call_frame(); + vm.pop_call_frame(); return result; } diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 43f1e813d1..48d6fcafef 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -899,24 +899,30 @@ Value Object::invoke(const StringOrSymbol& property_name, Optionalvm(); CallFrame call_frame; - call_frame.is_strict_mode = vm().in_strict_mode(); + call_frame.is_strict_mode = vm.in_strict_mode(); call_frame.this_value = this_object; - vm().push_call_frame(call_frame); - auto result = property.as_native_property().get(vm(), global_object()); - vm().pop_call_frame(); + vm.push_call_frame(call_frame, global_object()); + if (vm.exception()) + return {}; + auto result = property.as_native_property().get(vm, global_object()); + vm.pop_call_frame(); return result; } void Object::call_native_property_setter(Object* this_object, Value property, Value value) const { ASSERT(property.is_native_property()); + auto& vm = this->vm(); CallFrame call_frame; - call_frame.is_strict_mode = vm().in_strict_mode(); + call_frame.is_strict_mode = vm.in_strict_mode(); call_frame.this_value = this_object; - vm().push_call_frame(call_frame); - property.as_native_property().set(vm(), global_object(), value); - vm().pop_call_frame(); + vm.push_call_frame(call_frame, global_object()); + if (vm.exception()) + return; + property.as_native_property().set(vm, global_object(), value); + vm.pop_call_frame(); } } diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index b5d2e3647e..5aca076d1b 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -196,8 +196,10 @@ Value VM::construct(Function& function, Function& new_target, Optionalthis_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized); call_frame.environment->bind_this_value(function.global_object(), call_frame.this_value); + if (exception()) + return {}; - push_call_frame(call_frame); + push_call_frame(call_frame, function.global_object()); + if (exception()) + return {}; auto result = function.call(); pop_call_frame(); return result; diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index 7dc955bfeb..16e1a0213d 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -115,9 +116,15 @@ public: return *m_single_ascii_character_strings[character]; } - void push_call_frame(CallFrame& call_frame) + void push_call_frame(CallFrame& call_frame, GlobalObject& global_object) { - m_call_stack.append(&call_frame); + ASSERT(!exception()); + // Ensure we got some stack space left, so the next function call doesn't kill us. + // This value is merely a guess and might need tweaking at a later point. + if (m_stack_info.size_free() < 16 * KiB) + throw_exception(global_object, "RuntimeError", "Call stack size limit exceeded"); + else + m_call_stack.append(&call_frame); } void pop_call_frame() { m_call_stack.take_last(); } diff --git a/Libraries/LibJS/Tests/runtime-error-call-stack-size.js b/Libraries/LibJS/Tests/runtime-error-call-stack-size.js new file mode 100644 index 0000000000..177bb2d675 --- /dev/null +++ b/Libraries/LibJS/Tests/runtime-error-call-stack-size.js @@ -0,0 +1,21 @@ +test("infinite recursion", () => { + function infiniteRecursion() { + infiniteRecursion(); + } + + try { + infiniteRecursion(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.name).toBe("RuntimeError"); + expect(e.message).toBe("Call stack size limit exceeded"); + } + + expect(() => { + JSON.stringify({}, () => ({ foo: "bar" })); + }).toThrowWithMessage(Error, "Call stack size limit exceeded"); + + expect(() => { + new Proxy({}, { get: (_, __, p) => p.foo }).foo; + }).toThrowWithMessage(Error, "Call stack size limit exceeded"); +});