LibJS: Use Utf16StringBuilder for JS string construction
Port straightforward JS string construction sites to Utf16StringBuilder. Make ASCII appends explicit with append_ascii(), and leave byte-output, formatted-output, and printer plumbing on StringBuilder where that API still matches the caller. Add a small checked JS string length product helper. Use it for String.prototype.repeat() before constructing the repeated result. Keep the maximum string length policy in LibJS while allowing the UTF-16 builder to remain purpose-built and infallible. Cover overflow from the final repeated code-unit length in the repeat tests.
This commit is contained in:
parent
bf5f24ab36
commit
595ae25e74
19 changed files with 150 additions and 106 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/MemoryStream.h>
|
||||
#include <AK/NumberFormat.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Console.h>
|
||||
#include <LibJS/Print.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
@ -77,7 +78,11 @@ ThrowCompletionOr<Value> Console::assert_()
|
|||
// 3. Otherwise:
|
||||
else {
|
||||
// 1. Let concat be the concatenation of message, U+003A (:), U+0020 SPACE, and first.
|
||||
auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", message->utf8_string(), MUST(first.to_string(vm))));
|
||||
Utf16StringBuilder builder;
|
||||
builder.append(message->utf16_string_view());
|
||||
builder.append_ascii(": "sv);
|
||||
builder.append(first.as_string().utf16_string_view());
|
||||
auto concat = builder.to_string();
|
||||
// 2. Set data[0] to concat.
|
||||
data[0] = PrimitiveString::create(vm, move(concat));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/ParserError.h>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@
|
|||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <LibJS/Bytecode/Debug.h>
|
||||
#include <LibJS/ModuleLoading.h>
|
||||
|
|
@ -44,6 +47,20 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
size_t max_js_string_length()
|
||||
{
|
||||
return NumericLimits<u32>::max();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_product(VM& vm, size_t factor_a, size_t factor_b, ErrorType const& error_type)
|
||||
{
|
||||
Checked<size_t> product = factor_a;
|
||||
product *= factor_b;
|
||||
if (product.has_overflow() || product.value() > max_js_string_length())
|
||||
return vm.throw_completion<RangeError>(error_type);
|
||||
return product.value();
|
||||
}
|
||||
|
||||
// 7.2.1 RequireObjectCoercible ( argument ), https://tc39.es/ecma262/#sec-requireobjectcoercible
|
||||
ThrowCompletionOr<Value> require_object_coercible(VM& vm, Value value)
|
||||
{
|
||||
|
|
@ -1258,7 +1275,7 @@ CanonicalIndex canonical_numeric_index_string(PropertyKey const& property_key, C
|
|||
}
|
||||
|
||||
// 22.1.3.19.1 GetSubstitution ( matched, str, position, captures, namedCaptures, replacementTemplate ), https://tc39.es/ecma262/#sec-getsubstitution
|
||||
ThrowCompletionOr<String> get_substitution(VM& vm, Utf16View const& matched, Utf16View const& str, size_t position, Span<Value> captures, Value named_captures, Value replacement_template)
|
||||
ThrowCompletionOr<Utf16String> get_substitution(VM& vm, Utf16View const& matched, Utf16View const& str, size_t position, Span<Value> captures, Value named_captures, Value replacement_template)
|
||||
{
|
||||
// 1. Let stringLength be the length of str.
|
||||
auto string_length = str.length_in_code_units();
|
||||
|
|
@ -1267,7 +1284,7 @@ ThrowCompletionOr<String> get_substitution(VM& vm, Utf16View const& matched, Utf
|
|||
VERIFY(position <= string_length);
|
||||
|
||||
// 3. Let result be the empty String.
|
||||
StringBuilder result(StringBuilder::Mode::UTF16);
|
||||
Utf16StringBuilder result;
|
||||
|
||||
// 4. Let templateRemainder be replacementTemplate.
|
||||
auto replace_template_string = TRY(replacement_template.to_utf16_string(vm));
|
||||
|
|
@ -1446,7 +1463,7 @@ ThrowCompletionOr<String> get_substitution(VM& vm, Utf16View const& matched, Utf
|
|||
}
|
||||
|
||||
// 6. Return result.
|
||||
return MUST(result.utf16_string_view().to_utf8());
|
||||
return result.to_string();
|
||||
}
|
||||
|
||||
void DisposeCapability::visit_edges(GC::Cell::Visitor& visitor) const
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/CanonicalIndex.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/ErrorTypes.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
|
|
@ -77,12 +78,15 @@ bool all_import_attributes_supported(VM& vm, Vector<ImportAttribute> const& attr
|
|||
|
||||
ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value);
|
||||
|
||||
size_t max_js_string_length();
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_product(VM&, size_t, size_t, ErrorType const&);
|
||||
|
||||
enum class CanonicalIndexMode {
|
||||
DetectNumericRoundtrip,
|
||||
IgnoreNumericRoundtrip,
|
||||
};
|
||||
[[nodiscard]] CanonicalIndex canonical_numeric_index_string(PropertyKey const&, CanonicalIndexMode needs_numeric);
|
||||
ThrowCompletionOr<String> get_substitution(VM&, Utf16View const& matched, Utf16View const& str, size_t position, Span<Value> captures, Value named_captures, Value replacement);
|
||||
ThrowCompletionOr<Utf16String> get_substitution(VM&, Utf16View const& matched, Utf16View const& str, size_t position, Span<Value> captures, Value named_captures, Value replacement);
|
||||
|
||||
enum class CallerMode {
|
||||
Strict,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <AK/HashTable.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/ArrayConstructor.h>
|
||||
|
|
@ -931,21 +931,21 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join)
|
|||
};
|
||||
|
||||
auto length = TRY(length_of_array_like(vm, this_object));
|
||||
String separator = ","_string;
|
||||
Utf16String separator = ","_utf16;
|
||||
if (!vm.argument(0).is_undefined())
|
||||
separator = TRY(vm.argument(0).to_string(vm));
|
||||
StringBuilder builder;
|
||||
separator = TRY(vm.argument(0).to_utf16_string(vm));
|
||||
Utf16StringBuilder builder;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (i > 0)
|
||||
builder.append(separator);
|
||||
builder.append(separator.utf16_view());
|
||||
auto value = TRY(this_object->get(i));
|
||||
if (value.is_nullish())
|
||||
continue;
|
||||
auto string = TRY(value.to_string(vm));
|
||||
builder.append(string);
|
||||
auto string = TRY(value.to_utf16_string(vm));
|
||||
builder.append(string.utf16_view());
|
||||
}
|
||||
|
||||
return PrimitiveString::create(vm, builder.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 23.1.3.19 Array.prototype.keys ( ), https://tc39.es/ecma262/#sec-array.prototype.keys
|
||||
|
|
@ -1822,7 +1822,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
|
|||
constexpr auto separator = ","sv;
|
||||
|
||||
// 4. Let R be the empty String.
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
|
||||
// 5. Let k be 0.
|
||||
// 6. Repeat, while k < len,
|
||||
|
|
@ -1830,7 +1830,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
|
|||
// a. If k > 0, then
|
||||
if (i > 0) {
|
||||
// i. Set R to the string-concatenation of R and separator.
|
||||
builder.append(separator);
|
||||
builder.append_ascii(separator);
|
||||
}
|
||||
|
||||
// b. Let nextElement be ? Get(array, ! ToString(k)).
|
||||
|
|
@ -1842,15 +1842,15 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
|
|||
auto locale_string_result = TRY(value.invoke(vm, vm.names.toLocaleString, locales, options));
|
||||
|
||||
// ii. Set R to the string-concatenation of R and S.
|
||||
auto string = TRY(locale_string_result.to_string(vm));
|
||||
builder.append(string);
|
||||
auto string = TRY(locale_string_result.to_utf16_string(vm));
|
||||
builder.append(string.utf16_view());
|
||||
}
|
||||
|
||||
// d. Increase k by 1.
|
||||
}
|
||||
|
||||
// 7. Return R.
|
||||
return PrimitiveString::create(vm, builder.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 23.1.3.33 Array.prototype.toReversed ( ), https://tc39.es/ecma262/#sec-array.prototype.toreversed
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include <AK/GenericShorthands.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Intl/DurationFormat.h>
|
||||
#include <LibJS/Runtime/Intl/ListFormat.h>
|
||||
|
|
@ -741,16 +743,16 @@ Vector<DurationFormatPart> list_format_parts(VM& vm, DurationFormat const& durat
|
|||
// 8. For each element parts of partitionedPartsList, do
|
||||
for (auto const& parts : partitioned_parts_list) {
|
||||
// a. Let string be the empty String.
|
||||
StringBuilder string(StringBuilder::Mode::UTF16);
|
||||
Utf16StringBuilder string;
|
||||
|
||||
// b. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do
|
||||
for (auto const& part : parts) {
|
||||
// i. Set string to the string-concatenation of string and part.[[Value]].
|
||||
string.append(part.value);
|
||||
string.append(part.value.utf16_view());
|
||||
}
|
||||
|
||||
// c. Append string to strings.
|
||||
strings.unchecked_append(string.to_utf16_string());
|
||||
strings.unchecked_append(string.to_string());
|
||||
}
|
||||
|
||||
// 9. Let formattedPartsList be CreatePartsFromList(lf, strings).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/Enumerate.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Intl/DurationFormatPrototype.h>
|
||||
|
|
@ -124,16 +124,16 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::format)
|
|||
auto parts = partition_duration_format_pattern(vm, duration_format, duration);
|
||||
|
||||
// 5. Let result be a new empty String.
|
||||
StringBuilder result;
|
||||
Utf16StringBuilder result;
|
||||
|
||||
// 6. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do
|
||||
for (auto const& part : parts) {
|
||||
// a. Set result to the string-concatenation of result and part.[[Value]].
|
||||
result.append(part.value);
|
||||
result.append(part.value.utf16_view());
|
||||
}
|
||||
|
||||
// 7. Return result.
|
||||
return PrimitiveString::create(vm, MUST(result.to_string()));
|
||||
return PrimitiveString::create(vm, result.to_string());
|
||||
}
|
||||
|
||||
// 13.3.4 Intl.DurationFormat.prototype.formatToParts ( duration ), https://tc39.es/ecma402/#sec-Intl.DurationFormat.prototype.formatToParts
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <AK/Utf16FlyString.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
@ -410,16 +411,12 @@ void RopeString::resolve(EncodingPreference preference) const
|
|||
if (preference == EncodingPreference::UTF16) {
|
||||
// The caller wants a UTF-16 string, so we can simply concatenate all the pieces
|
||||
// into a UTF-16 code unit buffer and create a Utf16String from it.
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16, length_in_utf16_code_units);
|
||||
Utf16StringBuilder builder(length_in_utf16_code_units);
|
||||
|
||||
for (auto const* current : pieces) {
|
||||
if (current->has_utf16_string())
|
||||
builder.append(current->utf16_string_view());
|
||||
else
|
||||
builder.append(current->utf8_string_view());
|
||||
}
|
||||
for (auto const* current : pieces)
|
||||
builder.append(current->utf16_string_view());
|
||||
|
||||
m_utf16_string = builder.to_utf16_string();
|
||||
m_utf16_string = builder.to_string();
|
||||
m_deferred_kind = DeferredKind::None;
|
||||
m_lhs = nullptr;
|
||||
m_rhs = nullptr;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/PrimitiveString.h>
|
||||
|
|
@ -474,14 +475,14 @@ String RegExpObject::escape_regexp_pattern() const
|
|||
for (auto code_point : m_pattern) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
builder.append_code_point('\\');
|
||||
builder.append('\\');
|
||||
|
||||
switch (code_point) {
|
||||
case '\n':
|
||||
builder.append_code_point('n');
|
||||
builder.append('n');
|
||||
break;
|
||||
case '\r':
|
||||
builder.append_code_point('r');
|
||||
builder.append('r');
|
||||
break;
|
||||
case LINE_SEPARATOR:
|
||||
builder.append("u2028"sv);
|
||||
|
|
@ -510,7 +511,7 @@ String RegExpObject::escape_regexp_pattern() const
|
|||
switch (code_point) {
|
||||
case '/':
|
||||
if (in_character_class)
|
||||
builder.append_code_point('/');
|
||||
builder.append('/');
|
||||
else
|
||||
builder.append("\\/"sv);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
|
|
@ -499,7 +500,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::flags)
|
|||
auto regexp_object = TRY(this_object(vm));
|
||||
|
||||
// 3. Let result be the empty String.
|
||||
StringBuilder builder(8);
|
||||
Utf16StringBuilder builder(8);
|
||||
|
||||
// 4. Let hasIndices be ToBoolean(? Get(R, "hasIndices")).
|
||||
// 5. If hasIndices is true, append the code unit 0x0064 (LATIN SMALL LETTER D) as the last code unit of result.
|
||||
|
|
@ -522,13 +523,13 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::flags)
|
|||
static auto& cache = *new Bytecode::StaticPropertyLookupCache; \
|
||||
auto flag_##flag_name = TRY(regexp_object->get(vm.names.flagName, cache)); \
|
||||
if (flag_##flag_name.to_boolean()) \
|
||||
builder.append(#flag_char##sv); \
|
||||
builder.append_ascii(#flag_char##sv); \
|
||||
}
|
||||
JS_ENUMERATE_REGEXP_FLAGS
|
||||
#undef __JS_ENUMERATE
|
||||
|
||||
// 20. Return result.
|
||||
return PrimitiveString::create(vm, builder.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 22.2.6.8 RegExp.prototype [ @@match ] ( string ), https://tc39.es/ecma262/#sec-regexp.prototype-@@match
|
||||
|
|
@ -704,8 +705,8 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
&& !regexp_object.storage_has(vm.names.global)
|
||||
&& !regexp_object.storage_has(vm.names.unicode)
|
||||
&& !regexp_object.storage_has(vm.names.flags)) {
|
||||
auto replace_string = TRY(replace_value.to_string(vm));
|
||||
bool has_dollar = replace_string.contains('$');
|
||||
auto replace_string = TRY(replace_value.to_utf16_string(vm));
|
||||
bool has_dollar = replace_string.utf16_view().contains('$');
|
||||
|
||||
if (!has_dollar) {
|
||||
auto flag_bits = typed_regexp->flag_bits();
|
||||
|
|
@ -753,7 +754,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
bool need_legacy = typed_regexp->legacy_features_enabled()
|
||||
&& &realm == &typed_regexp->realm();
|
||||
|
||||
StringBuilder accumulated_result;
|
||||
Utf16StringBuilder accumulated_result;
|
||||
size_t next_source_position = 0;
|
||||
bool had_match = false;
|
||||
size_t last_match_start = 0;
|
||||
|
|
@ -876,7 +877,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
if (next_source_position < length_s)
|
||||
accumulated_result.append(utf16_view.substring_view(next_source_position));
|
||||
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -946,7 +947,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
}
|
||||
|
||||
// 13. Let accumulatedResult be the empty String.
|
||||
StringBuilder accumulated_result;
|
||||
Utf16StringBuilder accumulated_result;
|
||||
|
||||
// 14. Let nextSourcePosition be 0.
|
||||
size_t next_source_position = 0;
|
||||
|
|
@ -1000,7 +1001,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
static auto& cache3 = *new Bytecode::StaticPropertyLookupCache;
|
||||
auto named_captures = TRY(result->get(vm.names.groups, cache3));
|
||||
|
||||
String replacement;
|
||||
Utf16String replacement;
|
||||
|
||||
// k. If functionalReplace is true, then
|
||||
if (replace_value.is_function()) {
|
||||
|
|
@ -1021,7 +1022,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
auto replace_result = TRY(call(vm, replace_value.as_function(), js_undefined(), replacer_args.span()));
|
||||
|
||||
// iv. Let replacement be ? ToString(replValue).
|
||||
replacement = TRY(replace_result.to_string(vm));
|
||||
replacement = TRY(replace_result.to_utf16_string(vm));
|
||||
}
|
||||
// l. Else,
|
||||
else {
|
||||
|
|
@ -1051,13 +1052,13 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
|
||||
// 16. If nextSourcePosition ≥ lengthS, return accumulatedResult.
|
||||
if (next_source_position >= string->length_in_utf16_code_units())
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string());
|
||||
|
||||
// 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition.
|
||||
auto substring = string->utf16_string_view().substring_view(next_source_position);
|
||||
accumulated_result.append(substring);
|
||||
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string());
|
||||
}
|
||||
|
||||
// 22.2.6.12 RegExp.prototype [ @@search ] ( string ), https://tc39.es/ecma262/#sec-regexp.prototype-@@search
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
|
|
@ -99,7 +99,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_char_code)
|
|||
return from_char_code_impl(vm, vm.argument(0));
|
||||
|
||||
// 1. Let result be the empty String.
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16, vm.argument_count());
|
||||
Utf16StringBuilder builder(vm.argument_count());
|
||||
|
||||
// 2. For each element next of codeUnits, do
|
||||
for (size_t i = 0; i < vm.argument_count(); ++i) {
|
||||
|
|
@ -111,7 +111,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_char_code)
|
|||
}
|
||||
|
||||
// 3. Return result.
|
||||
return PrimitiveString::create(vm, builder.to_utf16_string());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 22.1.2.2 String.fromCodePoint ( ...codePoints ), https://tc39.es/ecma262/#sec-string.fromcodepoint
|
||||
|
|
@ -119,7 +119,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_code_point)
|
|||
{
|
||||
// 1. Let result be the empty String.
|
||||
// NOTE: This will be an under-estimate if any code point is > 0xffff.
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16, vm.argument_count());
|
||||
Utf16StringBuilder builder(vm.argument_count());
|
||||
|
||||
// 2. For each element next of codePoints, do
|
||||
for (size_t i = 0; i < vm.argument_count(); ++i) {
|
||||
|
|
@ -147,7 +147,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::from_code_point)
|
|||
VERIFY(builder.is_empty());
|
||||
|
||||
// 4. Return result.
|
||||
return PrimitiveString::create(vm, builder.to_utf16_string());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 22.1.2.4 String.raw ( template, ...substitutions ), https://tc39.es/ecma262/#sec-string.raw
|
||||
|
|
@ -172,7 +172,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
|
|||
return PrimitiveString::create(vm, String {});
|
||||
|
||||
// 6. Let R be the empty String.
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
|
||||
// 7. Let nextIndex be 0.
|
||||
// 8. Repeat,
|
||||
|
|
@ -181,10 +181,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
|
|||
auto next_literal_value = TRY(literals->get(PropertyKey(i)));
|
||||
|
||||
// b. Let nextLiteral be ? ToString(nextLiteralVal).
|
||||
auto next_literal = TRY(next_literal_value.to_string(vm));
|
||||
auto next_literal = TRY(next_literal_value.to_utf16_string(vm));
|
||||
|
||||
// c. Set R to the string-concatenation of R and nextLiteral.
|
||||
builder.append(next_literal);
|
||||
builder.append(next_literal.utf16_view());
|
||||
|
||||
// d. If nextIndex + 1 = literalCount, return R.
|
||||
if (i + 1 == literal_count)
|
||||
|
|
@ -196,15 +196,15 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
|
|||
auto next_substitution_value = vm.argument(i + 1);
|
||||
|
||||
// ii. Let nextSub be ? ToString(nextSubVal).
|
||||
auto next_substitution = TRY(next_substitution_value.to_string(vm));
|
||||
auto next_substitution = TRY(next_substitution_value.to_utf16_string(vm));
|
||||
|
||||
// iii. Set R to the string-concatenation of R and nextSub.
|
||||
builder.append(next_substitution);
|
||||
builder.append(next_substitution.utf16_view());
|
||||
}
|
||||
|
||||
// f. Set nextIndex to nextIndex + 1.
|
||||
}
|
||||
return PrimitiveString::create(vm, builder.to_byte_string());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
@ -763,14 +763,14 @@ static ThrowCompletionOr<Value> pad_string(VM& vm, GC::Ref<PrimitiveString> stri
|
|||
// 8. Let fillLen be intMaxLength - stringLength.
|
||||
auto fill_length = int_max_length - string_length;
|
||||
|
||||
StringBuilder truncated_string_filler_builder;
|
||||
Utf16StringBuilder truncated_string_filler_builder;
|
||||
auto fill_code_units = filler.length_in_code_units();
|
||||
for (size_t i = 0; i < fill_length / fill_code_units; ++i)
|
||||
truncated_string_filler_builder.append(filler);
|
||||
|
||||
// 9. Let truncatedStringFiller be the String value consisting of repeated concatenations of filler truncated to length fillLen.
|
||||
truncated_string_filler_builder.append(filler.substring_view(0, fill_length % fill_code_units));
|
||||
auto truncated_string_filler = MUST(truncated_string_filler_builder.to_string());
|
||||
auto truncated_string_filler = truncated_string_filler_builder.to_string();
|
||||
|
||||
// 10. If placement is start, return the string-concatenation of truncatedStringFiller and S.
|
||||
// 11. Else, return the string-concatenation of S and truncatedStringFiller.
|
||||
|
|
@ -811,7 +811,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
|
|||
{
|
||||
// 1. Let O be ? RequireObjectCoercible(this value).
|
||||
// 2. Let S be ? ToString(O).
|
||||
auto string = TRY(utf8_string_from(vm));
|
||||
auto string = TRY(primitive_string_from(vm));
|
||||
|
||||
// 3. Let n be ? ToIntegerOrInfinity(count).
|
||||
auto n = TRY(vm.argument(0).to_integer_or_infinity(vm));
|
||||
|
|
@ -827,15 +827,21 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
|
|||
return PrimitiveString::create(vm, String {});
|
||||
|
||||
// OPTIMIZATION: If the string is empty, the result will be empty as well.
|
||||
if (string.is_empty())
|
||||
if (string->is_empty())
|
||||
return PrimitiveString::create(vm, String {});
|
||||
|
||||
auto repeated = String::repeated(string, n);
|
||||
if (repeated.is_error())
|
||||
if (n > static_cast<double>(NumericLimits<size_t>::max()))
|
||||
return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustNotOverflow);
|
||||
|
||||
auto count = static_cast<size_t>(n);
|
||||
auto string_view = string->utf16_string_view();
|
||||
|
||||
TRY(checked_js_string_length_product(vm, string_view.length_in_code_units(), count, ErrorType::StringRepeatCountMustNotOverflow));
|
||||
|
||||
// 6. Return the String value that is made from n copies of S appended together.
|
||||
return PrimitiveString::create(vm, repeated.release_value());
|
||||
Utf16StringBuilder builder;
|
||||
builder.append_repeated(string_view, count);
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 22.1.3.19 String.prototype.replace ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replace
|
||||
|
|
@ -892,7 +898,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
|
|||
|
||||
// 10. Let preceding be the substring of string from 0 to position.
|
||||
auto preceding = string->utf16_string_view().substring_view(0, *position);
|
||||
String replacement;
|
||||
Utf16String replacement;
|
||||
|
||||
// 11. Let following be the substring of string from position + searchLength.
|
||||
auto following = string->utf16_string_view().substring_view(*position + search_length);
|
||||
|
|
@ -901,7 +907,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
|
|||
if (replace_value.is_function()) {
|
||||
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
|
||||
auto result = TRY(call(vm, replace_value.as_function(), js_undefined(), search_string, Value(*position), string));
|
||||
replacement = TRY(result.to_string(vm));
|
||||
replacement = TRY(result.to_utf16_string(vm));
|
||||
}
|
||||
// 13. Else,
|
||||
else {
|
||||
|
|
@ -916,12 +922,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
|
|||
}
|
||||
|
||||
// 14. Return the string-concatenation of preceding, replacement, and following.
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
builder.append(preceding);
|
||||
builder.append(replacement);
|
||||
builder.append(replacement.utf16_view());
|
||||
builder.append(following);
|
||||
|
||||
return PrimitiveString::create(vm, MUST(builder.to_string()));
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 22.1.3.20 String.prototype.replaceAll ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replaceall
|
||||
|
|
@ -1007,18 +1013,18 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
|
|||
size_t end_of_last_match = 0;
|
||||
|
||||
// 13. Let result be the empty String.
|
||||
StringBuilder result;
|
||||
Utf16StringBuilder result;
|
||||
|
||||
// 14. For each element p of matchPositions, do
|
||||
for (auto position : match_positions) {
|
||||
// a. Let preserved be the substring of string from endOfLastMatch to p.
|
||||
auto preserved = string->utf16_string_view().substring_view(end_of_last_match, position - end_of_last_match);
|
||||
String replacement;
|
||||
Utf16String replacement;
|
||||
|
||||
// b. If functionalReplace is true, then
|
||||
if (replace_value.is_function()) {
|
||||
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
|
||||
replacement = TRY(TRY(call(vm, replace_value.as_function(), js_undefined(), search_string, Value(position), string)).to_string(vm));
|
||||
replacement = TRY(TRY(call(vm, replace_value.as_function(), js_undefined(), search_string, Value(position), string)).to_utf16_string(vm));
|
||||
}
|
||||
// c. Else,
|
||||
else {
|
||||
|
|
@ -1030,7 +1036,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
|
|||
|
||||
// d. Set result to the string-concatenation of result, preserved, and replacement.
|
||||
result.append(preserved);
|
||||
result.append(replacement);
|
||||
result.append(replacement.utf16_view());
|
||||
|
||||
// e. Set endOfLastMatch to p + searchLength.
|
||||
end_of_last_match = position + search_length;
|
||||
|
|
@ -1045,7 +1051,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
|
|||
}
|
||||
|
||||
// 16. Return result.
|
||||
return PrimitiveString::create(vm, MUST(result.to_string()));
|
||||
return PrimitiveString::create(vm, result.to_string());
|
||||
}
|
||||
|
||||
// 22.1.3.21 String.prototype.search ( regexp ), https://tc39.es/ecma262/#sec-string.prototype.search
|
||||
|
|
@ -1586,49 +1592,49 @@ static ThrowCompletionOr<Value> create_html(VM& vm, Value string, StringView tag
|
|||
TRY(require_object_coercible(vm, string));
|
||||
|
||||
// 2. Let S be ? ToString(str).
|
||||
auto str = TRY(string.to_string(vm));
|
||||
auto str = TRY(string.to_utf16_string(vm));
|
||||
|
||||
// 3. Let p1 be the string-concatenation of "<" and tag.
|
||||
StringBuilder builder;
|
||||
builder.append('<');
|
||||
builder.append(tag);
|
||||
Utf16StringBuilder builder;
|
||||
builder.append_ascii('<');
|
||||
builder.append_ascii(tag);
|
||||
|
||||
// 4. If attribute is not the empty String, then
|
||||
if (!attribute.is_empty()) {
|
||||
// a. Let V be ? ToString(value).
|
||||
auto value_string = TRY(value.to_string(vm));
|
||||
auto value_string = TRY(value.to_utf16_string(vm));
|
||||
|
||||
// b. Let escapedV be the String value that is the same as V except that each occurrence of the code unit 0x0022 (QUOTATION MARK) in V has been replaced with the six code unit sequence """.
|
||||
auto escaped_value_string = MUST(value_string.replace("\""sv, """sv, ReplaceMode::All));
|
||||
auto escaped_value_string = value_string.replace("\""sv, """sv, ReplaceMode::All);
|
||||
|
||||
// c. Set p1 to the string-concatenation of:
|
||||
// - p1
|
||||
// - the code unit 0x0020 (SPACE)
|
||||
builder.append(' ');
|
||||
builder.append_ascii(' ');
|
||||
// - attribute
|
||||
builder.append(attribute);
|
||||
builder.append_ascii(attribute);
|
||||
// - the code unit 0x003D (EQUALS SIGN)
|
||||
// - the code unit 0x0022 (QUOTATION MARK)
|
||||
builder.append("=\""sv);
|
||||
builder.append_ascii("=\""sv);
|
||||
// - escapedV
|
||||
builder.append(escaped_value_string);
|
||||
builder.append(escaped_value_string.utf16_view());
|
||||
// - the code unit 0x0022 (QUOTATION MARK)
|
||||
builder.append('"');
|
||||
builder.append_ascii('"');
|
||||
}
|
||||
|
||||
// 5. Let p2 be the string-concatenation of p1 and ">".
|
||||
builder.append('>');
|
||||
builder.append_ascii('>');
|
||||
|
||||
// 6. Let p3 be the string-concatenation of p2 and S.
|
||||
builder.append(str);
|
||||
builder.append(str.utf16_view());
|
||||
|
||||
// 7. Let p4 be the string-concatenation of p3, "</", tag, and ">".
|
||||
builder.append("</"sv);
|
||||
builder.append(tag);
|
||||
builder.append('>');
|
||||
builder.append_ascii("</"sv);
|
||||
builder.append_ascii(tag);
|
||||
builder.append_ascii('>');
|
||||
|
||||
// 8. Return p4.
|
||||
return PrimitiveString::create(vm, MUST(builder.to_string()));
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// B.2.2.2 String.prototype.anchor ( name ), https://tc39.es/ecma262/#sec-string.prototype.anchor
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
*/
|
||||
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
|
|
@ -785,7 +787,10 @@ String format_fractional_seconds(u64 sub_second_nanoseconds, Precision precision
|
|||
}
|
||||
|
||||
// 3. Return the string-concatenation of the code unit 0x002E (FULL STOP) and fractionString.
|
||||
return MUST(String::formatted(".{}", fraction_string));
|
||||
StringBuilder builder;
|
||||
builder.append('.');
|
||||
builder.append(fraction_string);
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
// 13.26 FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision [ , style ] ), https://tc39.es/proposal-temporal/#sec-temporal-formattimestring
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Intl/DurationFormat.h>
|
||||
#include <LibJS/Runtime/Intl/DurationFormatConstructor.h>
|
||||
|
|
@ -653,16 +654,16 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_locale_string)
|
|||
auto parts = partition_duration_format_pattern(vm, formatter, duration);
|
||||
|
||||
// 5. Let result be the empty String.
|
||||
StringBuilder result;
|
||||
Utf16StringBuilder result;
|
||||
|
||||
// 6. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do
|
||||
for (auto const& part : parts) {
|
||||
// a. Set result to the string-concatenation of result and part.[[Value]].
|
||||
result.append(part.value);
|
||||
result.append(part.value.utf16_view());
|
||||
}
|
||||
|
||||
// 7. Return result.
|
||||
return PrimitiveString::create(vm, MUST(result.to_string()));
|
||||
return PrimitiveString::create(vm, result.to_string());
|
||||
}
|
||||
|
||||
// 7.3.25 Temporal.Duration.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <AK/AllOf.h>
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/SourceCode.h>
|
||||
#include <LibJS/SourceRange.h>
|
||||
#include <LibJS/Token.h>
|
||||
|
|
@ -291,7 +291,7 @@ Utf16String SourceCode::decode_source_range(size_t start_offset, size_t length)
|
|||
input = input.substring_view(byte_order_mark_size);
|
||||
}
|
||||
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16, length);
|
||||
Utf16StringBuilder builder(length);
|
||||
size_t current_offset = 0;
|
||||
auto result = actual_decoder->process_code_points(input, [&](auto code_point) -> ErrorOr<void> {
|
||||
char16_t code_units[2];
|
||||
|
|
@ -303,7 +303,7 @@ Utf16String SourceCode::decode_source_range(size_t start_offset, size_t length)
|
|||
for (size_t i = 0; i < code_point_length_in_code_units; ++i) {
|
||||
auto code_unit_offset = current_offset + i;
|
||||
if (code_unit_offset >= start_offset && code_unit_offset < end_offset)
|
||||
TRY(builder.try_append_code_unit(code_units[i]));
|
||||
builder.append_code_unit(code_units[i]);
|
||||
}
|
||||
|
||||
current_offset += code_point_length_in_code_units;
|
||||
|
|
@ -311,7 +311,7 @@ Utf16String SourceCode::decode_source_range(size_t start_offset, size_t length)
|
|||
});
|
||||
result.release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
return builder.to_utf16_string();
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
void SourceCode::fill_position_cache() const
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ test("throws correct range errors", () => {
|
|||
expect(() => {
|
||||
"foo".repeat(0xffffffffff);
|
||||
}).toThrowWithMessage(RangeError, "repeat count must not overflow");
|
||||
|
||||
expect(() => {
|
||||
"aa".repeat(0x80000000);
|
||||
}).toThrowWithMessage(RangeError, "repeat count must not overflow");
|
||||
});
|
||||
|
||||
test("UTF-16", () => {
|
||||
|
|
|
|||
|
|
@ -413,4 +413,4 @@ Pass Parsing origin: <non-special:\\opaque\path> against <about:blank>
|
|||
Pass Parsing origin: <non-special:\/opaque> against <about:blank>
|
||||
Pass Parsing origin: <non-special:/\path> against <about:blank>
|
||||
Pass Parsing origin: <non-special://host/a\b> against <about:blank>
|
||||
Pass Parsing origin: <http://example.com/U+d800<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>U+dfff﷏ﷰ?U+d800<30><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD>U+dfff﷏ﷰ> against <about:blank>
|
||||
Pass Parsing origin: <http://example.com/U+d800U+dfff﷏ﷰ?U+d800U+dfff﷏ﷰ> against <about:blank>
|
||||
|
|
@ -906,4 +906,4 @@ Pass Parsing: <data:text/plain,test# | |||