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:
Timothy Flynn 2023-02-16 12:55:22 -05:00 committed by Tim Flynn
parent 93ad25fbe5
commit 4d10911f96
Notes: sideshowbarker 2024-07-17 06:20:50 +09:00
5 changed files with 42 additions and 31 deletions

View file

@ -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) \

View file

@ -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());
}
}

View file

@ -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 {};
}

View file

@ -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()

View file

@ -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;