mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-23 21:15:14 +00:00
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.
This commit is contained in:
parent
93ad25fbe5
commit
4d10911f96
Notes:
sideshowbarker
2024-07-17 06:20:50 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/4d10911f96 Pull-request: https://github.com/SerenityOS/serenity/pull/17494 Reviewed-by: https://github.com/linusg ✅
5 changed files with 42 additions and 31 deletions
|
@ -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) \
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue