Skip to content

Commit 76b9d06

Browse files
trflynn89linusg
authored andcommitted
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.
1 parent fab8ef3 commit 76b9d06

File tree

4 files changed

+107
-30
lines changed

4 files changed

+107
-30
lines changed

Userland/Libraries/LibJS/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ set(SOURCES
231231
Runtime/Temporal/ZonedDateTime.cpp
232232
Runtime/Temporal/ZonedDateTimeConstructor.cpp
233233
Runtime/Temporal/ZonedDateTimePrototype.cpp
234+
Runtime/ThrowableStringBuilder.cpp
234235
Runtime/TypedArray.cpp
235236
Runtime/TypedArrayConstructor.cpp
236237
Runtime/TypedArrayPrototype.cpp

Userland/Libraries/LibJS/Runtime/StringPrototype.cpp

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#include <AK/Checked.h>
99
#include <AK/Function.h>
10-
#include <AK/StringBuilder.h>
1110
#include <AK/Utf16View.h>
1211
#include <LibJS/Heap/Heap.h>
1312
#include <LibJS/Runtime/AbstractOperations.h>
@@ -24,6 +23,7 @@
2423
#include <LibJS/Runtime/StringIterator.h>
2524
#include <LibJS/Runtime/StringObject.h>
2625
#include <LibJS/Runtime/StringPrototype.h>
26+
#include <LibJS/Runtime/ThrowableStringBuilder.h>
2727
#include <LibJS/Runtime/Utf16String.h>
2828
#include <LibJS/Runtime/Value.h>
2929
#include <LibLocale/Locale.h>
@@ -528,11 +528,11 @@ static ThrowCompletionOr<Value> pad_string(VM& vm, Utf16String string, PadPlacem
528528
auto fill_code_units = fill_string.length_in_code_units();
529529
auto fill_length = max_length - string_length;
530530

531-
StringBuilder filler_builder;
531+
ThrowableStringBuilder filler_builder(vm);
532532
for (size_t i = 0; i < fill_length / fill_code_units; ++i)
533-
filler_builder.append(fill_string.view());
533+
TRY(filler_builder.append(fill_string.view()));
534534

535-
filler_builder.append(fill_string.substring_view(0, fill_length % fill_code_units));
535+
TRY(filler_builder.append(fill_string.substring_view(0, fill_length % fill_code_units)));
536536
auto filler = filler_builder.build();
537537

538538
auto formatted = placement == PadPlacement::Start
@@ -575,9 +575,9 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
575575
if (string.is_empty())
576576
return PrimitiveString::create(vm, DeprecatedString::empty());
577577

578-
StringBuilder builder;
578+
ThrowableStringBuilder builder(vm);
579579
for (size_t i = 0; i < n; ++i)
580-
builder.append(string);
580+
TRY(builder.append(string));
581581
return PrimitiveString::create(vm, builder.to_deprecated_string());
582582
}
583583

@@ -615,10 +615,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
615615
replacement = TRY(get_substitution(vm, search_string.view(), string.view(), *position, {}, js_undefined(), replace_value));
616616
}
617617

618-
StringBuilder builder;
619-
builder.append(preserved);
620-
builder.append(replacement);
621-
builder.append(string.substring_view(*position + search_string.length_in_code_units()));
618+
ThrowableStringBuilder builder(vm);
619+
TRY(builder.append(preserved));
620+
TRY(builder.append(replacement));
621+
TRY(builder.append(string.substring_view(*position + search_string.length_in_code_units())));
622622

623623
return PrimitiveString::create(vm, builder.build());
624624
}
@@ -667,7 +667,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
667667
}
668668

669669
size_t end_of_last_match = 0;
670-
StringBuilder result;
670+
ThrowableStringBuilder result(vm);
671671

672672
for (auto position : match_positions) {
673673
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)
680680
replacement = TRY(get_substitution(vm, search_string.view(), string.view(), position, {}, js_undefined(), replace_value));
681681
}
682682

683-
result.append(preserved);
684-
result.append(replacement);
683+
TRY(result.append(preserved));
684+
TRY(result.append(replacement));
685685

686686
end_of_last_match = position + search_length;
687687
}
688688

689689
if (end_of_last_match < string_length)
690-
result.append(string.substring_view(end_of_last_match));
690+
TRY(result.append(string.substring_view(end_of_last_match)));
691691

692692
return PrimitiveString::create(vm, result.build());
693693
}
@@ -995,7 +995,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed)
995995
size_t k = 0;
996996

997997
// 5. Let result be the empty String.
998-
StringBuilder result;
998+
ThrowableStringBuilder result(vm);
999999

10001000
// 6. Repeat, while k < strLen,
10011001
while (k < length) {
@@ -1005,12 +1005,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_well_formed)
10051005
// b. If cp.[[IsUnpairedSurrogate]] is true, then
10061006
if (code_point.is_unpaired_surrogate) {
10071007
// i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER).
1008-
result.append_code_point(0xfffd);
1008+
TRY(result.append_code_point(0xfffd));
10091009
}
10101010
// c. Else,
10111011
else {
10121012
// i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]).
1013-
result.append_code_point(code_point.code_point);
1013+
TRY(result.append_code_point(code_point.code_point));
10141014
}
10151015

10161016
// d. Set k to k + cp.[[CodeUnitCount]].
@@ -1119,22 +1119,22 @@ static ThrowCompletionOr<Value> create_html(VM& vm, Value string, DeprecatedStri
11191119
{
11201120
TRY(require_object_coercible(vm, string));
11211121
auto str = TRY(string.to_string(vm));
1122-
StringBuilder builder;
1123-
builder.append('<');
1124-
builder.append(tag);
1122+
ThrowableStringBuilder builder(vm);
1123+
TRY(builder.append('<'));
1124+
TRY(builder.append(tag));
11251125
if (!attribute.is_empty()) {
11261126
auto value_string = TRY(value.to_string(vm));
1127-
builder.append(' ');
1128-
builder.append(attribute);
1129-
builder.append("=\""sv);
1130-
builder.append(value_string.replace("\""sv, "&quot;"sv, ReplaceMode::All));
1131-
builder.append('"');
1127+
TRY(builder.append(' '));
1128+
TRY(builder.append(attribute));
1129+
TRY(builder.append("=\""sv));
1130+
TRY(builder.append(value_string.replace("\""sv, "&quot;"sv, ReplaceMode::All)));
1131+
TRY(builder.append('"'));
11321132
}
1133-
builder.append('>');
1134-
builder.append(str);
1135-
builder.append("</"sv);
1136-
builder.append(tag);
1137-
builder.append('>');
1133+
TRY(builder.append('>'));
1134+
TRY(builder.append(str));
1135+
TRY(builder.append("</"sv));
1136+
TRY(builder.append(tag));
1137+
TRY(builder.append('>'));
11381138
return PrimitiveString::create(vm, builder.build());
11391139
}
11401140

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/Utf16View.h>
8+
#include <LibJS/Runtime/ThrowableStringBuilder.h>
9+
10+
namespace JS {
11+
12+
ThrowableStringBuilder::ThrowableStringBuilder(VM& vm)
13+
: m_vm(vm)
14+
{
15+
}
16+
17+
ThrowCompletionOr<void> ThrowableStringBuilder::append(char ch)
18+
{
19+
if (try_append(ch).is_error())
20+
return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + 1);
21+
return {};
22+
}
23+
24+
ThrowCompletionOr<void> ThrowableStringBuilder::append(StringView string)
25+
{
26+
if (try_append(string).is_error())
27+
return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + string.length());
28+
return {};
29+
}
30+
31+
ThrowCompletionOr<void> ThrowableStringBuilder::append(Utf16View const& string)
32+
{
33+
if (try_append(string).is_error())
34+
return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + (string.length_in_code_units() * 2));
35+
return {};
36+
}
37+
38+
ThrowCompletionOr<void> ThrowableStringBuilder::append_code_point(u32 value)
39+
{
40+
if (auto result = try_append_code_point(value); result.is_error())
41+
return m_vm.throw_completion<InternalError>(ErrorType::NotEnoughMemoryToAllocate, length() + sizeof(value));
42+
return {};
43+
}
44+
45+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/StringBuilder.h>
10+
#include <AK/StringView.h>
11+
#include <LibJS/Forward.h>
12+
#include <LibJS/Runtime/Completion.h>
13+
#include <LibJS/Runtime/ErrorTypes.h>
14+
#include <LibJS/Runtime/VM.h>
15+
16+
namespace JS {
17+
18+
class ThrowableStringBuilder : public AK::StringBuilder {
19+
public:
20+
explicit ThrowableStringBuilder(VM&);
21+
22+
ThrowCompletionOr<void> append(char);
23+
ThrowCompletionOr<void> append(StringView);
24+
ThrowCompletionOr<void> append(Utf16View const&);
25+
ThrowCompletionOr<void> append_code_point(u32 value);
26+
27+
private:
28+
VM& m_vm;
29+
};
30+
31+
}

0 commit comments

Comments
 (0)