Explorar o código

LibJS: Add and begin using a completion-compatible string builder

ThrowableStringBuilder is a thin wrapper around StringBuilder to map
results from the try_* methods to a throw completion. This will let us
try to throw on OOM conditions rather than just blowing up.
Timothy Flynn %!s(int64=2) %!d(string=hai) anos
pai
achega
76b9d06b19

+ 1 - 0
Userland/Libraries/LibJS/CMakeLists.txt

@@ -231,6 +231,7 @@ set(SOURCES
     Runtime/Temporal/ZonedDateTime.cpp
     Runtime/Temporal/ZonedDateTimeConstructor.cpp
     Runtime/Temporal/ZonedDateTimePrototype.cpp
+    Runtime/ThrowableStringBuilder.cpp
     Runtime/TypedArray.cpp
     Runtime/TypedArrayConstructor.cpp
     Runtime/TypedArrayPrototype.cpp

+ 30 - 30
Userland/Libraries/LibJS/Runtime/StringPrototype.cpp

@@ -7,7 +7,6 @@
 
 #include <AK/Checked.h>
 #include <AK/Function.h>
-#include <AK/StringBuilder.h>
 #include <AK/Utf16View.h>
 #include <LibJS/Heap/Heap.h>
 #include <LibJS/Runtime/AbstractOperations.h>
@@ -24,6 +23,7 @@
 #include <LibJS/Runtime/StringIterator.h>
 #include <LibJS/Runtime/StringObject.h>
 #include <LibJS/Runtime/StringPrototype.h>
+#include <LibJS/Runtime/ThrowableStringBuilder.h>
 #include <LibJS/Runtime/Utf16String.h>
 #include <LibJS/Runtime/Value.h>
 #include <LibLocale/Locale.h>
@@ -528,11 +528,11 @@ static ThrowCompletionOr<Value> pad_string(VM& vm, Utf16String string, PadPlacem
     auto fill_code_units = fill_string.length_in_code_units();
     auto fill_length = max_length - string_length;
 
-    StringBuilder filler_builder;
+    ThrowableStringBuilder filler_builder(vm);
     for (size_t i = 0; i < fill_length / fill_code_units; ++i)
-        filler_builder.append(fill_string.view());
+        TRY(filler_builder.append(fill_string.view()));
 
-    filler_builder.append(fill_string.substring_view(0, fill_length % fill_code_units));
+    TRY(filler_builder.append(fill_string.substring_view(0, fill_length % fill_code_units)));
     auto filler = filler_builder.build();
 
     auto formatted = placement == PadPlacement::Start
@@ -575,9 +575,9 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
     if (string.is_empty())
         return PrimitiveString::create(vm, DeprecatedString::empty());
 
-    StringBuilder builder;
+    ThrowableStringBuilder builder(vm);
     for (size_t i = 0; i < n; ++i)
-        builder.append(string);
+        TRY(builder.append(string));
     return PrimitiveString::create(vm, builder.to_deprecated_string());
 }
 
@@ -615,10 +615,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
         replacement = TRY(get_substitution(vm, search_string.view(), string.view(), *position, {}, js_undefined(), replace_value));
     }
 
-    StringBuilder builder;
-    builder.append(preserved);
-    builder.append(replacement);
-    builder.append(string.substring_view(*position + search_string.length_in_code_units()));
+    ThrowableStringBuilder builder(vm);
+    TRY(builder.append(preserved));
+    TRY(builder.append(replacement));
+    TRY(builder.append(string.substring_view(*position + search_string.length_in_code_units())));
 
     return PrimitiveString::create(vm, builder.build());
 }
@@ -667,7 +667,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
     }
 
     size_t end_of_last_match = 0;
-    StringBuilder result;
+    ThrowableStringBuilder result(vm);
 
     for (auto position : match_positions) {
         auto preserved = string.substring_view(end_of_last_match, position - end_of_last_match);
@@ -680,14 +680,14 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
             replacement = TRY(get_substitution(vm, search_string.view(), string.view(), position, {}, js_undefined(), replace_value));
         }
 
-        result.append(preserved);
-        result.append(replacement);
+        TRY(result.append(preserved));
+        TRY(result.append(replacement));
 
         end_of_last_match = position + search_length;
     }
 
     if (end_of_last_match < string_length)
-        result.append(string.substring_view(end_of_last_match));
+        TRY(result.append(string.substring_view(end_of_last_match)));
 
     return PrimitiveString::create(vm, result.build());
 }
@@ -995,7 +995,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed)
     size_t k = 0;
 
     // 5. Let result be the empty String.
-    StringBuilder result;
+    ThrowableStringBuilder result(vm);
 
     // 6. Repeat, while k < strLen,
     while (k < length) {
@@ -1005,12 +1005,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed)
         // b. If cp.[[IsUnpairedSurrogate]] is true, then
         if (code_point.is_unpaired_surrogate) {
             // i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER).
-            result.append_code_point(0xfffd);
+            TRY(result.append_code_point(0xfffd));
         }
         // c. Else,
         else {
             // i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]).
-            result.append_code_point(code_point.code_point);
+            TRY(result.append_code_point(code_point.code_point));
         }
 
         // d. Set k to k + cp.[[CodeUnitCount]].
@@ -1119,22 +1119,22 @@ static ThrowCompletionOr<Value> create_html(VM& vm, Value string, DeprecatedStri
 {
     TRY(require_object_coercible(vm, string));
     auto str = TRY(string.to_string(vm));
-    StringBuilder builder;
-    builder.append('<');
-    builder.append(tag);
+    ThrowableStringBuilder builder(vm);
+    TRY(builder.append('<'));
+    TRY(builder.append(tag));
     if (!attribute.is_empty()) {
         auto value_string = TRY(value.to_string(vm));
-        builder.append(' ');
-        builder.append(attribute);
-        builder.append("=\""sv);
-        builder.append(value_string.replace("\""sv, "&quot;"sv, ReplaceMode::All));
-        builder.append('"');
+        TRY(builder.append(' '));
+        TRY(builder.append(attribute));
+        TRY(builder.append("=\""sv));
+        TRY(builder.append(value_string.replace("\""sv, "&quot;"sv, ReplaceMode::All)));
+        TRY(builder.append('"'));
     }
-    builder.append('>');
-    builder.append(str);
-    builder.append("</"sv);
-    builder.append(tag);
-    builder.append('>');
+    TRY(builder.append('>'));
+    TRY(builder.append(str));
+    TRY(builder.append("</"sv));
+    TRY(builder.append(tag));
+    TRY(builder.append('>'));
     return PrimitiveString::create(vm, builder.build());
 }
 

+ 45 - 0
Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.cpp

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Utf16View.h>
+#include <LibJS/Runtime/ThrowableStringBuilder.h>
+
+namespace JS {
+
+ThrowableStringBuilder::ThrowableStringBuilder(VM& vm)
+    : m_vm(vm)
+{
+}
+
+ThrowCompletionOr<void> ThrowableStringBuilder::append(char ch)
+{
+    if (try_append(ch).is_error())
+        return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + 1);
+    return {};
+}
+
+ThrowCompletionOr<void> ThrowableStringBuilder::append(StringView string)
+{
+    if (try_append(string).is_error())
+        return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + string.length());
+    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));
+    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));
+    return {};
+}
+
+}

+ 31 - 0
Userland/Libraries/LibJS/Runtime/ThrowableStringBuilder.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Runtime/Completion.h>
+#include <LibJS/Runtime/ErrorTypes.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+class ThrowableStringBuilder : public AK::StringBuilder {
+public:
+    explicit ThrowableStringBuilder(VM&);
+
+    ThrowCompletionOr<void> append(char);
+    ThrowCompletionOr<void> append(StringView);
+    ThrowCompletionOr<void> append(Utf16View const&);
+    ThrowCompletionOr<void> append_code_point(u32 value);
+
+private:
+    VM& m_vm;
+};
+
+}