From 7c86352f34b1803f5963ff56f4c546cde36673a8 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 22 Jun 2026 01:59:18 +0200 Subject: [PATCH] LibJS: Use Utf16StringBuilder for more strings Port more UTF-16 string construction sites to Utf16StringBuilder. This covers JSON serialization, URI escaping, RegExp replacement, date and Temporal formatting, Uint8Array hex conversion, and stack string formatting. Keep byte-oriented debug, bytecode, parser, and print plumbing on StringBuilder. Add a checked JS string length sum helper and use it for accumulation paths that append JS-observable string pieces. --- .../LibJS/Runtime/AbstractOperations.cpp | 9 ++ Libraries/LibJS/Runtime/AbstractOperations.h | 1 + Libraries/LibJS/Runtime/ErrorData.cpp | 14 +-- Libraries/LibJS/Runtime/ErrorTypes.h | 1 + Libraries/LibJS/Runtime/FunctionPrototype.cpp | 1 - .../LibJS/Runtime/Intl/DurationFormat.cpp | 1 - Libraries/LibJS/Runtime/JSONObject.cpp | 5 +- Libraries/LibJS/Runtime/RegExpPrototype.cpp | 22 +++- .../LibJS/Runtime/TypedArrayPrototype.cpp | 32 ++++-- Libraries/LibJS/Runtime/Uint8Array.cpp | 16 ++- Libraries/LibJS/Runtime/Value.cpp | 104 +++++++++++++----- 11 files changed, 146 insertions(+), 60 deletions(-) diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp index 7c659dafe5..b9c9322ab0 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -52,6 +52,15 @@ size_t max_js_string_length() return NumericLimits::max(); } +ThrowCompletionOr checked_js_string_length_sum(VM& vm, size_t addend_a, size_t addend_b, ErrorType const& error_type) +{ + Checked sum = addend_a; + sum += addend_b; + if (sum.has_overflow() || sum.value() > max_js_string_length()) + return vm.throw_completion(error_type); + return sum.value(); +} + ThrowCompletionOr checked_js_string_length_product(VM& vm, size_t factor_a, size_t factor_b, ErrorType const& error_type) { Checked product = factor_a; diff --git a/Libraries/LibJS/Runtime/AbstractOperations.h b/Libraries/LibJS/Runtime/AbstractOperations.h index 2fba0a234d..07dd041e18 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/AbstractOperations.h @@ -79,6 +79,7 @@ bool all_import_attributes_supported(VM& vm, Vector const& attr ThrowCompletionOr perform_import_call(VM&, Value specifier, Value options_value); size_t max_js_string_length(); +ThrowCompletionOr checked_js_string_length_sum(VM&, size_t, size_t, ErrorType const&); ThrowCompletionOr checked_js_string_length_product(VM&, size_t, size_t, ErrorType const&); enum class CanonicalIndexMode { diff --git a/Libraries/LibJS/Runtime/ErrorData.cpp b/Libraries/LibJS/Runtime/ErrorData.cpp index 286971d7c3..8cf072539d 100644 --- a/Libraries/LibJS/Runtime/ErrorData.cpp +++ b/Libraries/LibJS/Runtime/ErrorData.cpp @@ -6,7 +6,7 @@ */ #include -#include +#include #include #include #include @@ -65,7 +65,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const if (m_traceback.is_empty()) return {}; - StringBuilder stack_string_builder(StringBuilder::Mode::UTF16); + Utf16StringBuilder stack_string_builder; // Note: We roughly follow V8's formatting auto append_frame = [&](TracebackFrame const& frame) { @@ -75,11 +75,11 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const if (!source_range.filename().is_empty() || source_range.start.line != 0 || source_range.start.column != 0) { if (function_name.is_empty()) - stack_string_builder.appendff(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column); + stack_string_builder.append(Utf16String::formatted(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column)); else - stack_string_builder.appendff(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column); + stack_string_builder.append(Utf16String::formatted(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column)); } else { - stack_string_builder.appendff(" at {}\n", function_name.is_empty() ? ""_utf16 : function_name); + stack_string_builder.append(Utf16String::formatted(" at {}\n", function_name.is_empty() ? ""_utf16 : function_name)); } }; @@ -110,7 +110,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const // the name only once and show the number of repetitions instead. This prevents // printing ridiculously large call stacks of recursive functions. append_frame(frame); - stack_string_builder.appendff(" {} more calls\n", repetitions); + stack_string_builder.append(Utf16String::formatted(" {} more calls\n", repetitions)); } else { for (size_t j = 0; j < repetitions + 1; j++) append_frame(frame); @@ -120,7 +120,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const for (size_t j = 0; j < repetitions; j++) append_frame(m_traceback[used_frames - 1]); - return stack_string_builder.to_utf16_string(); + return stack_string_builder.to_string(); } } diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h index e0686b9fdb..01f979f8b9 100644 --- a/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Libraries/LibJS/Runtime/ErrorTypes.h @@ -244,6 +244,7 @@ M(StringNonGlobalRegExp, "RegExp argument is non-global") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ M(StringRepeatCountMustNotOverflow, "repeat count must not overflow") \ + M(StringSizeMustNotOverflow, "string size must not overflow") \ M(TemporalDifferentCalendars, "Cannot compare dates from two different calendars") \ M(TemporalDifferentTimeZones, "Cannot compare dates from two different time zones") \ M(TemporalDisambiguatePossibleEpochNSRejectMoreThanOne, "Cannot disambiguate two or more possible epoch nanoseconds") \ diff --git a/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Libraries/LibJS/Runtime/FunctionPrototype.cpp index 2b201830ff..23a749603c 100644 --- a/Libraries/LibJS/Runtime/FunctionPrototype.cpp +++ b/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include #include diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp index 479b80ffec..1c7caedf2b 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include diff --git a/Libraries/LibJS/Runtime/JSONObject.cpp b/Libraries/LibJS/Runtime/JSONObject.cpp index 59455d02f1..455642b251 100644 --- a/Libraries/LibJS/Runtime/JSONObject.cpp +++ b/Libraries/LibJS/Runtime/JSONObject.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -487,7 +488,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse) // Returns {} on malformed escape sequences. static Optional unescape_json_string(StringView raw) { - StringBuilder builder(StringBuilder::Mode::UTF16, raw.length()); + Utf16StringBuilder builder(raw.length()); GenericLexer lexer { raw }; @@ -586,7 +587,7 @@ static Optional unescape_json_string(StringView raw) } } - return builder.to_utf16_string(); + return builder.to_string(); } template diff --git a/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Libraries/LibJS/Runtime/RegExpPrototype.cpp index d0c07da9fb..476781d65f 100644 --- a/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -755,6 +755,7 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re && &realm == &typed_regexp->realm(); Utf16StringBuilder accumulated_result; + size_t accumulated_result_length = 0; size_t next_source_position = 0; bool had_match = false; size_t last_match_start = 0; @@ -775,7 +776,10 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re for (int i = 0; i < num_matches; ++i) { auto [match_start, match_end] = compiled_regex->find_all_match(i); if (static_cast(match_start) >= next_source_position) { - accumulated_result.append(utf16_view.substring_view(next_source_position, match_start - next_source_position)); + auto substring = utf16_view.substring_view(next_source_position, match_start - next_source_position); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); + accumulated_result.append(substring); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replace_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); accumulated_result.append(replace_string); next_source_position = match_end; } @@ -826,7 +830,10 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re // Append the part of the string before this match + the replacement. if (match_start >= next_source_position) { - accumulated_result.append(utf16_view.substring_view(next_source_position, match_start - next_source_position)); + auto substring = utf16_view.substring_view(next_source_position, match_start - next_source_position); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); + accumulated_result.append(substring); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replace_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); accumulated_result.append(replace_string); next_source_position = match_start + match_length; } @@ -874,8 +881,11 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re return string; // Append the trailing portion of the string. - if (next_source_position < length_s) - accumulated_result.append(utf16_view.substring_view(next_source_position)); + if (next_source_position < length_s) { + auto substring = utf16_view.substring_view(next_source_position); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); + accumulated_result.append(substring); + } return PrimitiveString::create(vm, accumulated_result.to_string()); } @@ -948,6 +958,7 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re // 13. Let accumulatedResult be the empty String. Utf16StringBuilder accumulated_result; + size_t accumulated_result_length = 0; // 14. Let nextSourcePosition be 0. size_t next_source_position = 0; @@ -1042,7 +1053,9 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re // ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement. auto substring = string->utf16_string_view().substring_view(next_source_position, position - next_source_position); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); accumulated_result.append(substring); + accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replacement.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); accumulated_result.append(replacement); // iii. Set nextSourcePosition to position + matchLength. @@ -1056,6 +1069,7 @@ ThrowCompletionOr RegExpPrototype::symbol_replace_impl(VM& vm, Object& re // 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_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); accumulated_result.append(substring); return PrimitiveString::create(vm, accumulated_result.to_string()); diff --git a/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp index 46927e7dbf..fa96a64158 100644 --- a/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -1043,42 +1044,46 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::join) // 3. Let len be TypedArrayLength(taRecord). auto length = typed_array_length(typed_array_record); - String sep {}; + Utf16String sep {}; // 4. If separator is undefined, let sep be ",". if (separator.is_undefined()) - sep = String::from_code_point(','); + sep = ","_utf16; // 5. Else, let sep be ? ToString(separator). else - sep = TRY(separator.to_string(vm)); + sep = TRY(separator.to_utf16_string(vm)); // 6. Let R be the empty String. - StringBuilder builder; + Utf16StringBuilder builder; + size_t result_length = 0; // 7. Let k be 0. // 8. Repeat, while k < len, for (size_t k = 0; k < length; ++k) { // a. If k > 0, set R to the string-concatenation of R and sep. - if (k > 0) + if (k > 0) { + result_length = TRY(checked_js_string_length_sum(vm, result_length, sep.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); builder.append(sep); + } // b. Let element be ! Get(O, ! ToString(๐”ฝ(k))). auto element = MUST(typed_array->get(k)); - String next {}; + Utf16String next {}; // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). if (!element.is_undefined()) - next = MUST(element.to_string(vm)); + next = MUST(element.to_utf16_string(vm)); // d. Set R to the string-concatenation of R and next. + result_length = TRY(checked_js_string_length_sum(vm, result_length, next.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); builder.append(next); // e. Set k to k + 1. } // 9. Return R. - return PrimitiveString::create(vm, MUST(builder.to_string())); + return PrimitiveString::create(vm, builder.to_string()); } // 23.2.3.19 %TypedArray%.prototype.keys ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys @@ -2014,7 +2019,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) constexpr auto separator = ','; // 4. Let R be the empty String. - StringBuilder builder; + Utf16StringBuilder builder; + size_t result_length = 0; // 5. Let k be 0. // 6. Repeat, while k < len, @@ -2022,7 +2028,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) // a. If k > 0, then if (k > 0) { // i. Set R to the string-concatenation of R and separator. - builder.append(separator); + result_length = TRY(checked_js_string_length_sum(vm, result_length, 1, ErrorType::StringSizeMustNotOverflow)); + builder.append_ascii(separator); } // b. Let nextElement be ? Get(array, ! ToString(k)). @@ -2032,9 +2039,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) if (!next_element.is_nullish()) { // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", ยซ locales, options ยป)). auto locale_string_value = TRY(next_element.invoke(vm, vm.names.toLocaleString, locales, options)); - auto locale_string = TRY(locale_string_value.to_string(vm)); + auto locale_string = TRY(locale_string_value.to_utf16_string(vm)); // ii. Set R to the string-concatenation of R and S. + result_length = TRY(checked_js_string_length_sum(vm, result_length, locale_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow)); builder.append(locale_string); } @@ -2042,7 +2050,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) } // 7. Return R. - return PrimitiveString::create(vm, builder.to_string_without_validation()); + return PrimitiveString::create(vm, builder.to_string()); } // 23.2.3.32 %TypedArray%.prototype.toReversed ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed diff --git a/Libraries/LibJS/Runtime/Uint8Array.cpp b/Libraries/LibJS/Runtime/Uint8Array.cpp index a5469ce1b0..90c2b8010e 100644 --- a/Libraries/LibJS/Runtime/Uint8Array.cpp +++ b/Libraries/LibJS/Runtime/Uint8Array.cpp @@ -4,9 +4,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include +#include +#include #include #include #include @@ -15,6 +16,13 @@ namespace JS { +static void append_lowercase_hex_byte(Utf16StringBuilder& builder, u8 byte) +{ + constexpr auto hex_digits = "0123456789abcdef"sv; + builder.append_ascii(hex_digits[byte >> 4]); + builder.append_ascii(hex_digits[byte & 0xf]); +} + void Uint8ArrayConstructorHelpers::initialize(Realm& realm, Object& constructor) { auto& vm = constructor.vm(); @@ -377,18 +385,18 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_hex) auto to_encode = TRY(get_uint8_array_bytes(vm, typed_array)); // 4. Let out be the empty String. - StringBuilder out; + Utf16StringBuilder out(to_encode.bytes().size() * 2); // 5. For each byte byte of toEncode, do for (auto byte : to_encode.bytes()) { // a. Let hex be Number::toString(๐”ฝ(byte), 16). // b. Set hex to StringPad(hex, 2, "0", START). // c. Set out to the string-concatenation of out and hex. - out.appendff("{:02x}", byte); + append_lowercase_hex_byte(out, byte); } // 6. Return out. - return PrimitiveString::create(vm, MUST(out.to_string())); + return PrimitiveString::create(vm, out.to_string()); } // 23.3.3.1 ValidateUint8Array ( ta ), https://tc39.es/ecma262/#sec-validateuint8array diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index a2185bcdef..e26d62060c 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/Libraries/LibJS/Runtime/Value.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -75,9 +75,50 @@ static ALWAYS_INLINE bool both_bigint(Value const& lhs, Value const& rhs) return lhs.is_bigint() && rhs.is_bigint(); } +static void append_ascii_for_number(StringBuilder& builder, char code_unit) +{ + builder.append(code_unit); +} + +static void append_ascii_for_number(Utf16StringBuilder& builder, char code_unit) +{ + builder.append_ascii(code_unit); +} + +static void append_ascii_for_number(StringBuilder& builder, StringView string) +{ + builder.append(string); +} + +static void append_ascii_for_number(Utf16StringBuilder& builder, StringView string) +{ + builder.append_ascii(string); +} + +static void append_ascii_for_number(StringBuilder& builder, char const* string, size_t length) +{ + builder.append(string, length); +} + +static void append_ascii_for_number(Utf16StringBuilder& builder, char const* string, size_t length) +{ + builder.append_ascii(StringView { string, length }); +} + +static void append_repeated_ascii_for_number(StringBuilder& builder, char code_unit, size_t count) +{ + builder.append_repeated(code_unit, count); +} + +static void append_repeated_ascii_for_number(Utf16StringBuilder& builder, char code_unit, size_t count) +{ + builder.append_repeated_ascii(code_unit, count); +} + // 6.1.6.1.20 Number::toString ( x ), https://tc39.es/ecma262/#sec-numeric-types-number-tostring // Implementation for radix = 10 -void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) +template +static void number_to_string_impl(Builder& builder, double d, NumberToStringMode mode) { auto convert_to_decimal_digits_array = [](auto x, auto& digits, auto& length) { for (; x; x /= 10) @@ -88,24 +129,24 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) // 1. If x is NaN, return "NaN". if (isnan(d)) { - builder.append("NaN"sv); + append_ascii_for_number(builder, "NaN"sv); return; } // 2. If x is +0๐”ฝ or -0๐”ฝ, return "0". if (d == +0.0 || d == -0.0) { - builder.append("0"sv); + append_ascii_for_number(builder, "0"sv); return; } // 4. If x is +โˆž๐”ฝ, return "Infinity". if (isinf(d)) { if (d > 0) { - builder.append("Infinity"sv); + append_ascii_for_number(builder, "Infinity"sv); return; } - builder.append("-Infinity"sv); + append_ascii_for_number(builder, "-Infinity"sv); return; } @@ -124,7 +165,7 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) // 3. If x < -0๐”ฝ, return the string-concatenation of "-" and Number::toString(-x, radix). if (sign) - builder.append('-'); + append_ascii_for_number(builder, '-'); // Non-standard: Intl needs number-to-string conversions for extremely large numbers without any // exponential formatting, as it will handle such formatting itself in a locale-aware way. @@ -136,31 +177,31 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) if (n >= k) { // i. Return the string-concatenation of: // the code units of the k digits of the representation of s using radix radix - builder.append(mantissa_digits.data(), k); + append_ascii_for_number(builder, mantissa_digits.data(), k); // n - k occurrences of the code unit 0x0030 (DIGIT ZERO) - builder.append_repeated('0', n - k); + append_repeated_ascii_for_number(builder, '0', n - k); // b. Else if n > 0, then } else if (n > 0) { // i. Return the string-concatenation of: // the code units of the most significant n digits of the representation of s using radix radix - builder.append(mantissa_digits.data(), n); + append_ascii_for_number(builder, mantissa_digits.data(), n); // the code unit 0x002E (FULL STOP) - builder.append('.'); + append_ascii_for_number(builder, '.'); // the code units of the remaining k - n digits of the representation of s using radix radix - builder.append(mantissa_digits.data() + n, k - n); + append_ascii_for_number(builder, mantissa_digits.data() + n, k - n); // c. Else, } else { // i. Assert: n โ‰ค 0. VERIFY(n <= 0); // ii. Return the string-concatenation of: // the code unit 0x0030 (DIGIT ZERO) - builder.append('0'); + append_ascii_for_number(builder, '0'); // the code unit 0x002E (FULL STOP) - builder.append('.'); + append_ascii_for_number(builder, '.'); // -n occurrences of the code unit 0x0030 (DIGIT ZERO) - builder.append_repeated('0', -n); + append_repeated_ascii_for_number(builder, '0', -n); // the code units of the k digits of the representation of s using radix radix - builder.append(mantissa_digits.data(), k); + append_ascii_for_number(builder, mantissa_digits.data(), k); } return; @@ -182,30 +223,35 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) if (k == 1) { // a. Return the string-concatenation of: // the code unit of the single digit of s - builder.append(mantissa_digits[0]); + append_ascii_for_number(builder, mantissa_digits[0]); // the code unit 0x0065 (LATIN SMALL LETTER E) - builder.append('e'); + append_ascii_for_number(builder, 'e'); // exponentSign - builder.append(exponent_sign); + append_ascii_for_number(builder, exponent_sign); // the code units of the decimal representation of abs(n - 1) - builder.append(exponent_digits.data(), exponent_length); + append_ascii_for_number(builder, exponent_digits.data(), exponent_length); return; } // 12. Return the string-concatenation of: // the code unit of the most significant digit of the decimal representation of s - builder.append(mantissa_digits[0]); + append_ascii_for_number(builder, mantissa_digits[0]); // the code unit 0x002E (FULL STOP) - builder.append('.'); + append_ascii_for_number(builder, '.'); // the code units of the remaining k - 1 digits of the decimal representation of s - builder.append(mantissa_digits.data() + 1, k - 1); + append_ascii_for_number(builder, mantissa_digits.data() + 1, k - 1); // the code unit 0x0065 (LATIN SMALL LETTER E) - builder.append('e'); + append_ascii_for_number(builder, 'e'); // exponentSign - builder.append(exponent_sign); + append_ascii_for_number(builder, exponent_sign); // the code units of the decimal representation of abs(n - 1) - builder.append(exponent_digits.data(), exponent_length); + append_ascii_for_number(builder, exponent_digits.data(), exponent_length); +} + +void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode) +{ + number_to_string_impl(builder, d, mode); } String number_to_string(double d, NumberToStringMode mode) @@ -217,9 +263,9 @@ String number_to_string(double d, NumberToStringMode mode) Utf16String number_to_utf16_string(double d, NumberToStringMode mode) { - StringBuilder builder(StringBuilder::Mode::UTF16); - number_to_string(builder, d, mode); - return builder.to_utf16_string(); + Utf16StringBuilder builder; + number_to_string_impl(builder, d, mode); + return builder.to_string(); } ByteString number_to_byte_string(double d, NumberToStringMode mode)