From ecb7d4b40f15aa11e02d03d68d2557beb0294f13 Mon Sep 17 00:00:00 2001 From: Jess Date: Thu, 18 Apr 2024 21:35:35 +1200 Subject: [PATCH] LibJS: Throw RangeError in `StringPrototype::repeat` if OOM currently crashes with an assertion failure in `String::repeated` if malloc can't serve a `count * input_size` sized request, so add `String::repeated_with_error` to propagate the error. --- AK/String.cpp | 8 +++++--- AK/String.h | 2 +- Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + Userland/Libraries/LibJS/Runtime/StringPrototype.cpp | 6 +++++- .../Tests/builtins/String/String.prototype.repeat.js | 4 ++++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/AK/String.cpp b/AK/String.cpp index 6bcd168acba..2b63f042926 100644 --- a/AK/String.cpp +++ b/AK/String.cpp @@ -308,13 +308,14 @@ bool String::equals_ignoring_ascii_case(StringView other) const return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other); } -String String::repeated(String const& input, size_t count) +ErrorOr String::repeated(String const& input, size_t count) { - VERIFY(!Checked::multiplication_would_overflow(count, input.bytes().size())); + if (Checked::multiplication_would_overflow(count, input.bytes().size())) + return Error::from_errno(EOVERFLOW); String result; size_t input_size = input.bytes().size(); - MUST(result.replace_with_new_string(count * input_size, [&](Bytes buffer) { + TRY(result.replace_with_new_string(count * input_size, [&](Bytes buffer) { if (input_size == 1) { buffer.fill(input.bytes().first()); } else { @@ -323,6 +324,7 @@ String String::repeated(String const& input, size_t count) } return ErrorOr {}; })); + return result; } diff --git a/AK/String.h b/AK/String.h index 418426cd441..94920fe23cc 100644 --- a/AK/String.h +++ b/AK/String.h @@ -79,7 +79,7 @@ public: static ErrorOr repeated(u32 code_point, size_t count); // Creates a new String from another string, repeated N times. - static String repeated(String const&, size_t count); + static ErrorOr repeated(String const&, size_t count); // Creates a new String by case-transforming this String. Using these methods require linking LibUnicode into your application. ErrorOr to_lowercase(Optional const& locale = {}) const; diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 5bfc6600705..28fd783c61e 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -230,6 +230,7 @@ M(StringNonGlobalRegExp, "RegExp argument is non-global") \ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ + M(StringRepeatCountMustNotOverflow, "repeat count must not overflow") \ M(TemporalAmbiguousMonthOfPlainMonthDay, "Accessing month of PlainMonthDay is ambiguous, use monthCode instead") \ M(TemporalDifferentCalendars, "Cannot compare dates from two different calendars") \ M(TemporalDifferentTimeZones, "Cannot compare dates from two different time zones") \ diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index 018b9a0bd23..f5ced032159 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -776,8 +776,12 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat) if (string.is_empty()) return PrimitiveString::create(vm, String {}); + auto repeated = String::repeated(string, n); + if (repeated.is_error()) + return vm.throw_completion(ErrorType::StringRepeatCountMustNotOverflow); + // 6. Return the String value that is made from n copies of S appended together. - return PrimitiveString::create(vm, String::repeated(string, n)); + return PrimitiveString::create(vm, repeated.release_value()); } // 22.1.3.19 String.prototype.replace ( searchValue, replaceValue ), https://tc39.es/ecma262/#sec-string.prototype.replace diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js index c343c694d5e..ba819eda9e0 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.repeat.js @@ -22,6 +22,10 @@ test("throws correct range errors", () => { expect(() => { "foo".repeat(Infinity); }).toThrowWithMessage(RangeError, "repeat count must be a finite number"); + + expect(() => { + "foo".repeat(0xffffffffff); + }).toThrowWithMessage(RangeError, "repeat count must not overflow"); }); test("UTF-16", () => {