Parcourir la source

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.
Timothy Flynn il y a 2 ans
Parent
commit
4d10911f96

+ 12 - 12
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)                                                                  \

+ 5 - 13
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());
 }
 
 }

+ 1 - 6
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 {};
     }
 

+ 12 - 0
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()

+ 12 - 0
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;