diff options
author | Timothy Flynn <trflynn89@pm.me> | 2023-02-16 12:55:22 -0500 |
---|---|---|
committer | Tim Flynn <trflynn89@pm.me> | 2023-02-17 09:14:23 -0500 |
commit | 4d10911f96eb6ad55a9798b7237a60407f645e91 (patch) | |
tree | 08ff30edf026fd06a1c68290d87ee928db96a72c /Userland | |
parent | 93ad25fbe51d39094ea42eb9dd62b0d7203a4d5f (diff) | |
download | serenity-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')
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Completion.h | 24 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp | 18 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h | 7 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/VM.cpp | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/VM.h | 12 |
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; |