mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:27:45 +00:00
LibJS: Throw RuntimeError when reaching the end of the stack
This prevents stack overflows when calling infinite/deep recursive functions, e.g.: const f = () => f(); f(); JSON.stringify({}, () => ({ foo: "bar" })); new Proxy({}, { get: (_, __, p) => p.foo }).foo; The VM caches a StackInfo object to not slow down function calls considerably. VM::push_call_frame() will throw an exception if necessary (plain Error with "RuntimeError" as its .name).
This commit is contained in:
parent
9c3ead8f91
commit
a02b9983f9
5 changed files with 58 additions and 18 deletions
|
@ -63,7 +63,8 @@ Interpreter::~Interpreter()
|
||||||
|
|
||||||
Value Interpreter::run(GlobalObject& global_object, const Program& program)
|
Value Interpreter::run(GlobalObject& global_object, const Program& program)
|
||||||
{
|
{
|
||||||
ASSERT(!vm().exception());
|
auto& vm = this->vm();
|
||||||
|
ASSERT(!vm.exception());
|
||||||
|
|
||||||
VM::InterpreterExecutionScope scope(*this);
|
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.function_name = global_execution_context_name;
|
||||||
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global);
|
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global);
|
||||||
global_call_frame.environment->bind_this_value(global_object, &global_object);
|
global_call_frame.environment->bind_this_value(global_object, &global_object);
|
||||||
|
ASSERT(!vm.exception());
|
||||||
global_call_frame.is_strict_mode = program.is_strict_mode();
|
global_call_frame.is_strict_mode = program.is_strict_mode();
|
||||||
if (vm().exception())
|
vm.push_call_frame(global_call_frame, global_object);
|
||||||
return {};
|
ASSERT(!vm.exception());
|
||||||
|
|
||||||
vm().push_call_frame(global_call_frame);
|
|
||||||
auto result = program.execute(*this, global_object);
|
auto result = program.execute(*this, global_object);
|
||||||
vm().pop_call_frame();
|
vm.pop_call_frame();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -899,24 +899,30 @@ Value Object::invoke(const StringOrSymbol& property_name, Optional<MarkedValueLi
|
||||||
Value Object::call_native_property_getter(Object* this_object, Value property) const
|
Value Object::call_native_property_getter(Object* this_object, Value property) const
|
||||||
{
|
{
|
||||||
ASSERT(property.is_native_property());
|
ASSERT(property.is_native_property());
|
||||||
|
auto& vm = this->vm();
|
||||||
CallFrame call_frame;
|
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;
|
call_frame.this_value = this_object;
|
||||||
vm().push_call_frame(call_frame);
|
vm.push_call_frame(call_frame, global_object());
|
||||||
auto result = property.as_native_property().get(vm(), global_object());
|
if (vm.exception())
|
||||||
vm().pop_call_frame();
|
return {};
|
||||||
|
auto result = property.as_native_property().get(vm, global_object());
|
||||||
|
vm.pop_call_frame();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::call_native_property_setter(Object* this_object, Value property, Value value) const
|
void Object::call_native_property_setter(Object* this_object, Value property, Value value) const
|
||||||
{
|
{
|
||||||
ASSERT(property.is_native_property());
|
ASSERT(property.is_native_property());
|
||||||
|
auto& vm = this->vm();
|
||||||
CallFrame call_frame;
|
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;
|
call_frame.this_value = this_object;
|
||||||
vm().push_call_frame(call_frame);
|
vm.push_call_frame(call_frame, global_object());
|
||||||
property.as_native_property().set(vm(), global_object(), value);
|
if (vm.exception())
|
||||||
vm().pop_call_frame();
|
return;
|
||||||
|
property.as_native_property().set(vm, global_object(), value);
|
||||||
|
vm.pop_call_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,8 +196,10 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
|
||||||
{
|
{
|
||||||
CallFrame call_frame;
|
CallFrame call_frame;
|
||||||
call_frame.is_strict_mode = function.is_strict_mode();
|
call_frame.is_strict_mode = function.is_strict_mode();
|
||||||
push_call_frame(call_frame);
|
|
||||||
|
|
||||||
|
push_call_frame(call_frame, function.global_object());
|
||||||
|
if (exception())
|
||||||
|
return {};
|
||||||
ArmedScopeGuard call_frame_popper = [&] {
|
ArmedScopeGuard call_frame_popper = [&] {
|
||||||
pop_call_frame();
|
pop_call_frame();
|
||||||
};
|
};
|
||||||
|
@ -323,8 +325,12 @@ Value VM::call_internal(Function& function, Value this_value, Optional<MarkedVal
|
||||||
|
|
||||||
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
|
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
|
||||||
call_frame.environment->bind_this_value(function.global_object(), call_frame.this_value);
|
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();
|
auto result = function.call();
|
||||||
pop_call_frame();
|
pop_call_frame();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <AK/StackInfo.h>
|
#include <AK/StackInfo.h>
|
||||||
#include <LibJS/Heap/Heap.h>
|
#include <LibJS/Heap/Heap.h>
|
||||||
#include <LibJS/Runtime/CommonPropertyNames.h>
|
#include <LibJS/Runtime/CommonPropertyNames.h>
|
||||||
|
#include <LibJS/Runtime/Error.h>
|
||||||
#include <LibJS/Runtime/ErrorTypes.h>
|
#include <LibJS/Runtime/ErrorTypes.h>
|
||||||
#include <LibJS/Runtime/Exception.h>
|
#include <LibJS/Runtime/Exception.h>
|
||||||
#include <LibJS/Runtime/MarkedValueList.h>
|
#include <LibJS/Runtime/MarkedValueList.h>
|
||||||
|
@ -115,8 +116,14 @@ public:
|
||||||
return *m_single_ascii_character_strings[character];
|
return *m_single_ascii_character_strings[character];
|
||||||
}
|
}
|
||||||
|
|
||||||
void push_call_frame(CallFrame& call_frame)
|
void push_call_frame(CallFrame& call_frame, GlobalObject& global_object)
|
||||||
{
|
{
|
||||||
|
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<Error>(global_object, "RuntimeError", "Call stack size limit exceeded");
|
||||||
|
else
|
||||||
m_call_stack.append(&call_frame);
|
m_call_stack.append(&call_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
Libraries/LibJS/Tests/runtime-error-call-stack-size.js
Normal file
21
Libraries/LibJS/Tests/runtime-error-call-stack-size.js
Normal file
|
@ -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");
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue