summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2023-02-16 12:55:22 -0500
committerTim Flynn <trflynn89@pm.me>2023-02-17 09:14:23 -0500
commit4d10911f96eb6ad55a9798b7237a60407f645e91 (patch)
tree08ff30edf026fd06a1c68290d87ee928db96a72c /Userland/Libraries
parent93ad25fbe51d39094ea42eb9dd62b0d7203a4d5f (diff)
downloadserenity-4d10911f96eb6ad55a9798b7237a60407f645e91.zip
LibJS: Pre-allocate the out-of-memory error string on the VM
If we are out of memory, we can't try to allocate a string that could fail as well. When Error is converted to String, this would result in an endless OOM-throwing loop. Instead, pre-allocate the string on the VM, and use it to construct the Error. Note that as of this commit, the OOM string is still a DeprecatedString. This is just preporatory for Error's conversion to String.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/Runtime/Completion.h24
-rw-r--r--Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp18
-rw-r--r--Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h7
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.cpp12
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.h12
5 files changed, 42 insertions, 31 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Completion.h b/Userland/Libraries/LibJS/Runtime/Completion.h
index b5d089ab84..78e7bc8b87 100644
--- a/Userland/Libraries/LibJS/Runtime/Completion.h
+++ b/Userland/Libraries/LibJS/Runtime/Completion.h
@@ -17,18 +17,18 @@
namespace JS {
-#define TRY_OR_THROW_OOM(vm, expression) \
- ({ \
- /* Ignore -Wshadow to allow nesting the macro. */ \
- AK_IGNORE_DIAGNOSTIC("-Wshadow", \
- auto&& _temporary_result = (expression)); \
- if (_temporary_result.is_error()) { \
- VERIFY(_temporary_result.error().code() == ENOMEM); \
- return (vm).throw_completion<JS::InternalError>(JS::ErrorType::OutOfMemory); \
- } \
- static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
- "Do not return a reference from a fallible expression"); \
- _temporary_result.release_value(); \
+#define TRY_OR_THROW_OOM(vm, expression) \
+ ({ \
+ /* Ignore -Wshadow to allow nesting the macro. */ \
+ AK_IGNORE_DIAGNOSTIC("-Wshadow", \
+ auto&& _temporary_result = (expression)); \
+ if (_temporary_result.is_error()) { \
+ VERIFY(_temporary_result.error().code() == ENOMEM); \
+ return (vm).throw_completion<JS::InternalError>((vm).error_message(::JS::VM::ErrorMessage::OutOfMemory)); \
+ } \
+ static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
+ "Do not return a reference from a fallible expression"); \
+ _temporary_result.release_value(); \
})
#define MUST_OR_THROW_OOM(expression) \
diff --git a/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp b/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp
index 763a0513db..256fdb3311 100644
--- a/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp
@@ -16,39 +16,31 @@ ThrowableStringBuilder::ThrowableStringBuilder(VM& vm)
ThrowCompletionOr<void> ThrowableStringBuilder::append(char ch)
{
- if (try_append(ch).is_error())
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + 1);
+ TRY_OR_THROW_OOM(m_vm, try_append(ch));
return {};
}
ThrowCompletionOr<void> ThrowableStringBuilder::append(StringView string)
{
- if (try_append(string).is_error())
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + string.length());
+ TRY_OR_THROW_OOM(m_vm, try_append(string));
return {};
}
ThrowCompletionOr<void> ThrowableStringBuilder::append(Utf16View const& string)
{
- if (try_append(string).is_error())
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + (string.length_in_code_units() * 2));
+ TRY_OR_THROW_OOM(m_vm, try_append(string));
return {};
}
ThrowCompletionOr<void> ThrowableStringBuilder::append_code_point(u32 value)
{
- if (auto result = try_append_code_point(value); result.is_error())
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + sizeof(value));
+ TRY_OR_THROW_OOM(m_vm, try_append_code_point(value));
return {};
}
ThrowCompletionOr<String> ThrowableStringBuilder::to_string() const
{
- auto result = StringBuilder::to_string();
- if (result.is_error())
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length());
-
- return result.release_value();
+ return TRY_OR_THROW_OOM(m_vm, StringBuilder::to_string());
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h b/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h
index 463b590f35..bbee3e3cbe 100644
--- a/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h
+++ b/Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h
@@ -30,12 +30,7 @@ public:
ThrowCompletionOr<void> appendff(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
AK::VariadicFormatParams<AK::AllowDebugOnlyFormatters::No, Parameters...> variadic_format_params { parameters... };
-
- if (vformat(*this, fmtstr.view(), variadic_format_params).is_error()) {
- // The size returned here is a bit of an estimate, as we don't know what the final formatted string length would be.
- return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + fmtstr.view().length());
- }
-
+ TRY_OR_THROW_OOM(m_vm, vformat(*this, fmtstr.view(), variadic_format_params));
return {};
}
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp
index f4b7e62458..548cb86756 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.cpp
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp
@@ -153,6 +153,18 @@ VM::VM(OwnPtr<CustomData> custom_data)
m_well_known_symbol_##snake_name = Symbol::create(*this, String::from_utf8("Symbol." #SymbolName##sv).release_value_but_fixme_should_propagate_errors(), false);
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
#undef __JS_ENUMERATE
+
+ m_error_messages[to_underlying(ErrorMessage::OutOfMemory)] = ErrorType::OutOfMemory.message();
+}
+
+DeprecatedString const& VM::error_message(ErrorMessage type) const
+{
+ VERIFY(type < ErrorMessage::__Count);
+
+ auto const& message = m_error_messages[to_underlying(type)];
+ VERIFY(!message.is_empty());
+
+ return message;
}
void VM::enable_default_host_import_module_dynamically_hook()
diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h
index 2009e377f2..b163921b3e 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.h
+++ b/Userland/Libraries/LibJS/Runtime/VM.h
@@ -91,6 +91,17 @@ public:
return *m_single_ascii_character_strings[character];
}
+ // This represents the list of errors from ErrorTypes.h whose messages are used in contexts which
+ // must not fail to allocate when they are used. For example, we cannot allocate when we raise an
+ // out-of-memory error, thus we pre-allocate that error string at VM creation time.
+ enum class ErrorMessage {
+ OutOfMemory,
+
+ // Keep this last:
+ __Count,
+ };
+ DeprecatedString const& error_message(ErrorMessage) const;
+
bool did_reach_stack_space_limit() const
{
// Address sanitizer (ASAN) used to check for more space but
@@ -285,6 +296,7 @@ private:
PrimitiveString* m_empty_string { nullptr };
PrimitiveString* m_single_ascii_character_strings[128] {};
+ AK::Array<DeprecatedString, to_underlying(ErrorMessage::__Count)> m_error_messages;
struct StoredModule {
ScriptOrModule referencing_script_or_module;