commit f1ac1edc87e033d6227f27441acdb5824e1a43e1
parent d915ceffa193657cb035ed140c6f3de567cc47af
Author: André Bargull <andre.bargull@gmail.com>
Date: Thu, 30 Oct 2025 18:21:57 +0000
Bug 1997029: Simplify condition when to take base64 fast path. r=iain
Differential Revision: https://phabricator.services.mozilla.com/D270539
Diffstat:
2 files changed, 117 insertions(+), 55 deletions(-)
diff --git a/js/src/tests/non262/TypedArray/base64-whitespace.js b/js/src/tests/non262/TypedArray/base64-whitespace.js
@@ -0,0 +1,61 @@
+for (let string of [
+ // No trailing padding
+ "ab",
+ "abc",
+
+ // With trailing padding
+ "ab==",
+ "abc=",
+
+ // Full chunk
+ "abcd",
+]) {
+ for (let i = 0; i <= 4; ++i) {
+ let str = "abcd".repeat(i) + string;
+
+ let expected = Uint8Array.fromBase64(str);
+
+ for (let j = 1; j < 8; ++j) {
+ let space = " ".repeat(j);
+
+ // Leading whitespace
+ assertEqArray(Uint8Array.fromBase64(space + str), expected);
+
+ // Trailing whitespace
+ assertEqArray(Uint8Array.fromBase64(str + space), expected);
+
+ // Interspersed whitespace
+ assertEqArray(Uint8Array.fromBase64(str.split("").join(space)), expected);
+ }
+ }
+}
+
+// Invalid trailing chunk
+for (let string of [
+ "a",
+ "a=",
+ "a==",
+ "a===",
+]) {
+ for (let i = 0; i <= 4; ++i) {
+ let str = "abcd".repeat(i) + string;
+
+ assertThrowsInstanceOf(() => Uint8Array.fromBase64(str), SyntaxError);
+
+ for (let j = 1; j < 8; ++j) {
+ let space = " ".repeat(j);
+
+ // Leading whitespace
+ assertThrowsInstanceOf(() => Uint8Array.fromBase64(space + str), SyntaxError);
+
+ // Trailing whitespace
+ assertThrowsInstanceOf(() => Uint8Array.fromBase64(str + space), SyntaxError);
+
+ // Interspersed whitespace
+ assertThrowsInstanceOf(() => Uint8Array.fromBase64(str.split("").join(space)), SyntaxError);
+ }
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
@@ -4773,74 +4773,81 @@ static auto FromBase64(const CharT* chars, size_t length, Alphabet alphabet,
// Initial loop to process only full chunks. Doesn't perform any error
// reporting and expects that at least four characters can be read per loop
// iteration and that the output has enough space for a decoded chunk.
+ if (length >= 4) {
+ size_t lastValidIndex = length - 4;
+ while (canAppend(3) && index <= lastValidIndex) {
+ // Fast path: Read four consecutive characters.
- size_t alignedLength = length & ~0x3;
- while (canAppend(3) && index < alignedLength) {
- // Fast path: Read four consecutive characters.
+ // Step 10.a. (Performed in slow path.)
- // Step 10.a. (Performed in slow path.)
+ // Step 10.b. (Moved out of loop.)
- // Step 10.b. (Moved out of loop.)
+ // Steps 10.c and 10.e-g.
+ uint32_t chunk = decode4Chars(chars + index);
- // Steps 10.c and 10.e-g.
- uint32_t chunk = decode4Chars(chars + index);
+ // Steps 10.h-i. (Not applicable in this loop.)
- // Steps 10.h-i. (Not applicable in this loop.)
+ // Steps 10.d and 10.j-l.
+ if (MOZ_LIKELY(int32_t(chunk) >= 0)) {
+ // Step 10.j-l.
+ decodeChunk(chunk);
- // Steps 10.d and 10.j-l.
- if (MOZ_LIKELY(int32_t(chunk) >= 0)) {
- // Step 10.j-l.
- decodeChunk(chunk);
+ // Step 10.d.
+ index += 4;
+ continue;
+ }
- // Step 10.d.
- index += 4;
- continue;
- }
+ // Slow path: Read four characters, ignoring whitespace.
- // Slow path: Read four characters, ignoring whitespace.
+ // Steps 10.a and 10.b.
+ CharT part[4];
+ size_t i = index;
+ size_t j = 0;
+ while (i < length && j < 4) {
+ auto ch = chars[i++];
- // Steps 10.a and 10.b.
- CharT part[4];
- size_t i = index;
- size_t j = 0;
- while (i < length && j < 4) {
- auto ch = chars[i++];
+ // Step 10.a.
+ if (mozilla::IsAsciiWhitespace(ch)) {
+ continue;
+ }
- // Step 10.a.
- if (mozilla::IsAsciiWhitespace(ch)) {
- continue;
+ // Step 10.c.
+ part[j++] = ch;
}
- // Step 10.c.
- part[j++] = ch;
- }
+ // Steps 10.d-l.
+ if (MOZ_LIKELY(j == 4)) {
+ // Steps 10.e-g.
+ uint32_t chunk = decode4Chars(part);
- // Steps 10.d-l.
- if (MOZ_LIKELY(j == 4)) {
- // Steps 10.e-g.
- uint32_t chunk = decode4Chars(part);
+ // Steps 10.h-i. (Not applicable in this loop.)
- // Steps 10.h-i. (Not applicable in this loop.)
+ // Steps 10.d and 10.j-l.
+ if (MOZ_LIKELY(int32_t(chunk) >= 0)) {
+ // Step 10.j-l.
+ decodeChunk(chunk);
- // Steps 10.d and 10.j-l.
- if (MOZ_LIKELY(int32_t(chunk) >= 0)) {
- // Step 10.j-l.
- decodeChunk(chunk);
-
- // Step 10.d.
- index = i;
- continue;
+ // Step 10.d.
+ index = i;
+ continue;
+ }
}
+
+ // Padding or invalid characters, or end of input. The next loop will
+ // process any characters left in the input.
+ break;
}
- // Padding or invalid characters, or end of input. The next loop will
- // process any characters left in the input.
- break;
- }
+ // Step 10.b.ii.
+ if (index == length) {
+ return Base64Result::Ok(length, written());
+ }
- // Step 10.b.ii.
- if (index == length) {
- return Base64Result::Ok(length, written());
+ // Step 10.l.v. (Reordered)
+ if (!canAppend(1)) {
+ MOZ_ASSERT(written() > 0);
+ return Base64Result::Ok(index, written());
+ }
}
// Step 4.
@@ -4848,12 +4855,6 @@ static auto FromBase64(const CharT* chars, size_t length, Alphabet alphabet,
// String index after the last fully read base64 chunk.
size_t read = index;
- // Step 10.l.v. (Reordered)
- if (!canAppend(1)) {
- MOZ_ASSERT(written() > 0);
- return Base64Result::Ok(read, written());
- }
-
// Step 5. (Not applicable in our implementation.)
// Step 6.