From c69d6fab8fec0c9e53d6956963657366adc7c238 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 15 Jul 2024 12:30:26 -0400 Subject: [PATCH] LibJS: Implement Uint8Array.prototype.toHex --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + .../Libraries/LibJS/Runtime/Uint8Array.cpp | 27 +++++++++ Userland/Libraries/LibJS/Runtime/Uint8Array.h | 1 + .../TypedArray/Uint8Array.prototype.toHex.js | 59 +++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/TypedArray/Uint8Array.prototype.toHex.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index af5a85508eb..9093dfdc530 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -533,6 +533,7 @@ namespace JS { P(toExponential) \ P(toFixed) \ P(toGMTString) \ + P(toHex) \ P(toInstant) \ P(toISOString) \ P(toJSON) \ diff --git a/Userland/Libraries/LibJS/Runtime/Uint8Array.cpp b/Userland/Libraries/LibJS/Runtime/Uint8Array.cpp index deb83229f19..0200f07a75c 100644 --- a/Userland/Libraries/LibJS/Runtime/Uint8Array.cpp +++ b/Userland/Libraries/LibJS/Runtime/Uint8Array.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -19,6 +20,7 @@ void Uint8ArrayPrototypeHelpers::initialize(Realm& realm, Object& prototype) static constexpr u8 attr = Attribute::Writable | Attribute::Configurable; prototype.define_native_function(realm, vm.names.toBase64, to_base64, 0, attr); + prototype.define_native_function(realm, vm.names.toHex, to_hex, 0, attr); } static ThrowCompletionOr parse_alphabet(VM& vm, Object& options) @@ -85,6 +87,31 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_base64) return PrimitiveString::create(vm, move(out_ascii)); } +// 2 Uint8Array.prototype.toHex ( ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tobase64 +JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_hex) +{ + // 1. Let O be the this value. + // 2. Perform ? ValidateUint8Array(O). + auto typed_array = TRY(validate_uint8_array(vm)); + + // 3. Let toEncode be ? GetUint8ArrayBytes(O). + auto to_encode = TRY(get_uint8_array_bytes(vm, typed_array)); + + // 4. Let out be the empty String. + StringBuilder out; + + // 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); + } + + // 6. Return out. + return PrimitiveString::create(vm, MUST(out.to_string())); +} + // 7 ValidateUint8Array ( ta ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-validateuint8array ThrowCompletionOr> validate_uint8_array(VM& vm) { diff --git a/Userland/Libraries/LibJS/Runtime/Uint8Array.h b/Userland/Libraries/LibJS/Runtime/Uint8Array.h index aab3e01309e..415e1efe707 100644 --- a/Userland/Libraries/LibJS/Runtime/Uint8Array.h +++ b/Userland/Libraries/LibJS/Runtime/Uint8Array.h @@ -17,6 +17,7 @@ public: private: JS_DECLARE_NATIVE_FUNCTION(to_base64); + JS_DECLARE_NATIVE_FUNCTION(to_hex); }; enum class Alphabet { diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/Uint8Array.prototype.toHex.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/Uint8Array.prototype.toHex.js new file mode 100644 index 00000000000..02bcfd09414 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/Uint8Array.prototype.toHex.js @@ -0,0 +1,59 @@ +describe("errors", () => { + test("called on non-Uint8Array object", () => { + expect(() => { + Uint8Array.prototype.toHex.call(1); + }).toThrowWithMessage(TypeError, "Not an object of type Uint8Array"); + + expect(() => { + Uint8Array.prototype.toHex.call(new Uint16Array()); + }).toThrowWithMessage(TypeError, "Not an object of type Uint8Array"); + }); + + test("detached ArrayBuffer", () => { + let arrayBuffer = new ArrayBuffer(5, { maxByteLength: 10 }); + let typedArray = new Uint8Array(arrayBuffer, Uint8Array.BYTES_PER_ELEMENT, 1); + detachArrayBuffer(arrayBuffer); + + expect(() => { + typedArray.toHex(); + }).toThrowWithMessage( + TypeError, + "TypedArray contains a property which references a value at an index not contained within its buffer's bounds" + ); + }); + + test("ArrayBuffer out of bounds", () => { + let arrayBuffer = new ArrayBuffer(Uint8Array.BYTES_PER_ELEMENT * 2, { + maxByteLength: Uint8Array.BYTES_PER_ELEMENT * 4, + }); + + let typedArray = new Uint8Array(arrayBuffer, Uint8Array.BYTES_PER_ELEMENT, 1); + arrayBuffer.resize(Uint8Array.BYTES_PER_ELEMENT); + + expect(() => { + typedArray.toHex(); + }).toThrowWithMessage( + TypeError, + "TypedArray contains a property which references a value at an index not contained within its buffer's bounds" + ); + }); +}); + +describe("correct behavior", () => { + test("length is 0", () => { + expect(Uint8Array.prototype.toHex).toHaveLength(0); + }); + + const encodeEqual = (input, expected) => { + const encoded = toUTF8Bytes(input).toHex(); + expect(encoded).toBe(expected); + }; + + test("basic functionality", () => { + encodeEqual("", ""); + encodeEqual("a", "61"); + encodeEqual("abcdef012345", "616263646566303132333435"); + encodeEqual("🤓", "f09fa493"); + encodeEqual("🤓foo🖖", "f09fa493666f6ff09f9696"); + }); +});