commit eaeda779e89574b31b38beb11d638fa4391e622c
parent 72609da3135a013a9b9b1b4c552d263133d22346
Author: André Bargull <andre.bargull@gmail.com>
Date: Wed, 29 Oct 2025 13:03:42 +0000
Bug 1996770: Align memory before reinterpreting as uint32. r=iain
Align the memory to avoid an assertion failure in `SharedMem::cast`.
Unaligned memory reads are always allowed in x86 and arm64, and often also in
arm32. `ARMv7` added support for unaligned memory loads and stores (`ldr` and
`str` instructions), except when SCTLR.A is set to 1, in which case an
alignment fault occurs.
Differential Revision: https://phabricator.services.mozilla.com/D270348
Diffstat:
2 files changed, 141 insertions(+), 9 deletions(-)
diff --git a/js/src/tests/non262/TypedArray/base64-and-hex-unaligned-buffer.js b/js/src/tests/non262/TypedArray/base64-and-hex-unaligned-buffer.js
@@ -0,0 +1,85 @@
+function toBase64(Buffer) {
+ for (var length = 1; length <= 32; ++length) {
+ var buffer = new Buffer(length);
+ for (var offset = 0; offset < length; ++offset) {
+ var u8 = new Uint8Array(buffer, offset);
+ assertEq(u8.length > 0, true);
+
+ var str = u8.toBase64();
+ assertEq(str.startsWith("A"), true);
+ assertEq(str.endsWith("A") || str.endsWith("="), true);
+ }
+ }
+}
+toBase64(ArrayBuffer);
+if (typeof SharedArrayBuffer === "function")
+ toBase64(SharedArrayBuffer);
+
+function toHex(Buffer) {
+ for (var length = 1; length <= 32; ++length) {
+ var buffer = new Buffer(length);
+ for (var offset = 0; offset < length; ++offset) {
+ var u8 = new Uint8Array(buffer, offset);
+ assertEq(u8.length > 0, true);
+
+ var str = u8.toHex();
+ assertEq(str.startsWith("00"), true);
+ assertEq(str.endsWith("00"), true);
+ }
+ }
+}
+toHex(ArrayBuffer);
+if (typeof SharedArrayBuffer === "function")
+ toHex(SharedArrayBuffer);
+
+function setFromHex(Buffer) {
+ var input = "aabbccddeeff00112233445566778899";
+
+ for (var length = 1; length <= 32; ++length) {
+ var buffer = new Buffer(length);
+ for (var offset = 0; offset < length; ++offset) {
+ var u8 = new Uint8Array(buffer, offset);
+ assertEq(u8.length > 0, true);
+
+ for (var str = input; str.length; str = str.slice(0, -2)) {
+ u8.setFromHex(str);
+ assertEq(u8[0], 0xaa);
+ }
+ }
+ }
+}
+setFromHex(ArrayBuffer);
+if (typeof SharedArrayBuffer === "function")
+ setFromHex(SharedArrayBuffer);
+
+function setFromBase64(Buffer) {
+ var input = "AAA".repeat(16);
+
+ for (var length = 1; length <= 32; ++length) {
+ var buffer = new Buffer(length);
+ for (var offset = 0; offset < length; ++offset) {
+ var u8 = new Uint8Array(buffer, offset);
+ assertEq(u8.length > 0, true);
+
+ for (var str = input; str.length; str = str.slice(0, -4)) {
+ u8.setFromBase64(str);
+ assertEq(u8[0], 0);
+
+ u8.setFromBase64(str.slice(0, -1) + "=");
+ assertEq(u8[0], 0);
+
+ u8.setFromBase64(str.slice(0, -2) + "==");
+ assertEq(u8[0], 0);
+
+ u8.setFromBase64(str.slice(0, -3), {lastChunkHandling: "stop-before-partial"});
+ assertEq(u8[0], 0);
+ }
+ }
+ }
+}
+setFromBase64(ArrayBuffer);
+if (typeof SharedArrayBuffer === "function")
+ setFromBase64(SharedArrayBuffer);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
@@ -4380,6 +4380,10 @@ static size_t FromHex(const CharT* chars, size_t length,
}
};
+ auto decode2Chars = [&](const CharT* chars) {
+ return (decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0);
+ };
+
auto decode4Chars = [&](const CharT* chars) {
return (decodeChar(chars[2]) << 12) | (decodeChar(chars[3]) << 8) |
(decodeChar(chars[0]) << 4) | (decodeChar(chars[1]) << 0);
@@ -4392,12 +4396,34 @@ static size_t FromHex(const CharT* chars, size_t length,
MOZ_ASSERT(length % 2 == 0);
// Process eight characters per loop iteration.
- size_t alignedLength = length & ~7;
- if (index < alignedLength) {
+ if (length >= 8) {
+ // Align |data| to uint32_t.
+ if (MOZ_UNLIKELY(data.unwrapValue() & 3)) {
+ // Performs at most three iterations until |data| is aligned, reading up
+ // to six characters.
+ while (data.unwrapValue() & 3) {
+ // Step 6.a and 6.d.
+ uint32_t byte = decode2Chars(chars + index);
+
+ // Step 6.b.
+ if (MOZ_UNLIKELY(int32_t(byte) < 0)) {
+ return index;
+ }
+ MOZ_ASSERT(byte <= 0xff);
+
+ // Step 6.c.
+ index += 2;
+
+ // Step 6.e.
+ Ops::store(data++, uint8_t(byte));
+ }
+ }
+
auto data32 = data.template cast<uint32_t*>();
// Step 6.
- while (index < alignedLength) {
+ size_t lastValidIndex = length - 8;
+ while (index <= lastValidIndex) {
// Steps 6.a and 6.d.
uint32_t word1 = decode4Chars(chars + index);
@@ -4433,12 +4459,8 @@ static size_t FromHex(const CharT* chars, size_t length,
// Step 6.
while (index < length) {
- // Step 6.a.
- auto c0 = chars[index + 0];
- auto c1 = chars[index + 1];
-
- // Step 6.d.
- uint32_t byte = (decodeChar(c0) << 4) | (decodeChar(c1) << 0);
+ // Step 6.a and 6.d.
+ uint32_t byte = decode2Chars(chars + index);
// Step 6.b.
if (MOZ_UNLIKELY(int32_t(byte) < 0)) {
@@ -5506,6 +5528,31 @@ static void ToBase64(TypedArrayObject* tarray, size_t length, Alphabet alphabet,
auto toRead = length;
if (toRead >= 12) {
+ // Align |data| to uint32_t.
+ if (MOZ_UNLIKELY(data.unwrapValue() & 3)) {
+ // Performs at most three iterations until |data| is aligned, reading up
+ // to nine bytes.
+ while (data.unwrapValue() & 3) {
+ // Combine three input bytes into a single uint24 value.
+ auto byte0 = Ops::load(data++);
+ auto byte1 = Ops::load(data++);
+ auto byte2 = Ops::load(data++);
+ auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2;
+
+ // Encode the uint24 value as base64.
+ char chars[] = {
+ encode(u24 >> 18),
+ encode(u24 >> 12),
+ encode(u24 >> 6),
+ encode(u24 >> 0),
+ };
+ sb.infallibleAppend(chars, sizeof(chars));
+
+ MOZ_ASSERT(toRead >= 3);
+ toRead -= 3;
+ }
+ }
+
auto data32 = data.template cast<uint32_t*>();
for (; toRead >= 12; toRead -= 12) {
// Read three 32-bit words.