Skip to content

Fix String.prototype.repeat with a large count on an empty string - #1741

Merged
bnoordhuis merged 2 commits into
quickjs-ng:masterfrom
ethanstoner:fix-repeat-empty-string
Sep 24, 2026
Merged

bnoordhuis merged 2 commits into
quickjs-ng:masterfrom
ethanstoner:fix-repeat-empty-string

Conversation

@ethanstoner

@ethanstoner ethanstoner commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1735.

Step 4 of String.prototype.repeat throws a RangeError only when the count is
negative or +Infinity; step 6 then produces n copies of the receiver. For an
empty receiver that is the empty String for any finite n, however large, and
no allocation is needed. We threw for any count above 2^31-1, so
"".repeat(Number.MAX_SAFE_INTEGER) raised RangeError: invalid repeat count
where V8, JSC, SpiderMonkey and GraalJS all return "".

Two things stood in the way of just moving the check. The range check ran before
the len == 0 || n == 1 short circuit, and JS_ToInt64Sat saturates, so
+Infinity and a large finite count both arrive as INT64_MAX and cannot be
told apart, yet "".repeat(Infinity) must still throw while
"".repeat(2**53-1) must not.

So the conversion is now done in a double, which follows ToIntegerOrInfinity
directly: NaN maps to zero, everything else truncates toward zero, and a
negative or infinite count is rejected. The empty-receiver and n == 1 early
return comes next, and the JS_STRING_LEN_MAX check last, on a value that is
known to be finite and non-negative. A large finite count on a non-empty string
still throws, now as string too long. Working in a double also sidesteps the
val * len overflow that lifting the 2^31-1 bound would otherwise introduce.

Behavior change, empty receiver only:

expression before after
"".repeat(Number.MAX_SAFE_INTEGER) RangeError ""
"".repeat(2147483648) RangeError ""
"".repeat(1e300) RangeError ""

Everything else is unchanged except the message on "ab".repeat(2**53-1) and
friends, which goes from invalid repeat count to string too long. It
is still a RangeError.

Validation

The first three items were run on the original commit. After the message change
in 5cf8f60, make test on Linux (WSL), gcc: 0/117 errors, 9 excluded, and
tests/bug1735.js passes with its new message assertions.

  • make test on macOS/arm64, clang, Release: 0/117 errors, 9 excluded. The new
    tests/bug1735.js fails on master at the "".repeat(Number.MAX_SAFE_INTEGER)
    assertion and passes with the fix.
  • run-test262 -m -c test262.conf test262/test/built-ins/String: 0/1223 errors
    before and after, so nothing in the String suite regressed. Worth noting that
    test262 does not cover a large finite count on an empty receiver, which is why
    this went unnoticed.
  • Checked the whole decision table against V8 by hand: negative, -Infinity,
    +Infinity, NaN, undefined, -0, 0, 0.9, 1, 2.7, a large finite
    count on both an empty and a non-empty receiver, and the JS_STRING_LEN_MAX
    boundary ("a".repeat(1073741823) succeeds, 1073741824 throws). Also
    confirmed the receiver is still coerced with ToString before the count is
    coerced, and that a Symbol or BigInt count still throws a TypeError.

Step 4 of String.prototype.repeat throws a RangeError only when the count
is negative or +Infinity. We threw for any count above 2^31-1, so
"".repeat(Number.MAX_SAFE_INTEGER) raised a RangeError where the spec asks
for the empty String.

The range check ran before the len == 0 short circuit, and JS_ToInt64Sat
saturates +Infinity and a large finite count to the same value, so the two
could not be told apart. Do the ToIntegerOrInfinity conversion in a double
instead: reject a negative or infinite count, then return early for an
empty receiver, and only after that reject a count that would overflow
JS_STRING_LEN_MAX. A large finite count on a non-empty string still throws,
now as "invalid string length".

@bnoordhuis bnoordhuis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the change is functionally correct, it makes the error message for large repeat counts worse: invalid string length instead of invalid repeat count

Can you change it to something like string too long?

A finite count that makes the result exceed JS_STRING_LEN_MAX now throws
RangeError: string too long, which says more than "invalid string length".
A negative or infinite count still throws "invalid repeat count".
@ethanstoner

Copy link
Copy Markdown
Contributor Author

Changed it to string too long in 5cf8f60. Negative and infinite counts still throw invalid repeat count, and tests/bug1735.js now asserts both messages. I also fixed the description, which still said invalid string length.

@bnoordhuis
bnoordhuis merged commit 19dbe85 into quickjs-ng:master Sep 24, 2026
128 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

String.prototype.repeat throws RangeError for a count the spec allows

2 participants