mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-10 19:46:03 +00:00
LibJS: Implement Uint8Array.prototype.setFromHex
This commit is contained in:
parent
c7afd175bc
commit
7d0e33cb27
Notes:
github-actions[bot]
2024-09-03 15:46:29 +00:00
Author: https://github.com/trflynn89
Commit: 7d0e33cb27
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1258
Reviewed-by: https://github.com/awesomekling
4 changed files with 147 additions and 0 deletions
|
@ -464,6 +464,7 @@ namespace JS {
|
||||||
P(setFloat32) \
|
P(setFloat32) \
|
||||||
P(setFloat64) \
|
P(setFloat64) \
|
||||||
P(setFromBase64) \
|
P(setFromBase64) \
|
||||||
|
P(setFromHex) \
|
||||||
P(setFullYear) \
|
P(setFullYear) \
|
||||||
P(setHours) \
|
P(setHours) \
|
||||||
P(setInt8) \
|
P(setInt8) \
|
||||||
|
|
|
@ -32,6 +32,7 @@ void Uint8ArrayPrototypeHelpers::initialize(Realm& realm, Object& prototype)
|
||||||
prototype.define_native_function(realm, vm.names.toBase64, to_base64, 0, attr);
|
prototype.define_native_function(realm, vm.names.toBase64, to_base64, 0, attr);
|
||||||
prototype.define_native_function(realm, vm.names.toHex, to_hex, 0, attr);
|
prototype.define_native_function(realm, vm.names.toHex, to_hex, 0, attr);
|
||||||
prototype.define_native_function(realm, vm.names.setFromBase64, set_from_base64, 1, attr);
|
prototype.define_native_function(realm, vm.names.setFromBase64, set_from_base64, 1, attr);
|
||||||
|
prototype.define_native_function(realm, vm.names.setFromHex, set_from_hex, 1, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ThrowCompletionOr<Alphabet> parse_alphabet(VM& vm, Object& options)
|
static ThrowCompletionOr<Alphabet> parse_alphabet(VM& vm, Object& options)
|
||||||
|
@ -307,6 +308,66 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayConstructorHelpers::from_hex)
|
||||||
return typed_array;
|
return typed_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6 Uint8Array.prototype.setFromHex ( string ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::set_from_hex)
|
||||||
|
{
|
||||||
|
auto& realm = *vm.current_realm();
|
||||||
|
|
||||||
|
auto string_value = vm.argument(0);
|
||||||
|
|
||||||
|
// 1. Let into be the this value.
|
||||||
|
// 2. Perform ? ValidateUint8Array(into).
|
||||||
|
auto into = TRY(validate_uint8_array(vm));
|
||||||
|
|
||||||
|
// 3. If string is not a String, throw a TypeError exception.
|
||||||
|
if (!string_value.is_string())
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::NotAString, string_value);
|
||||||
|
|
||||||
|
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(into, seq-cst).
|
||||||
|
auto typed_array_record = make_typed_array_with_buffer_witness_record(into, ArrayBuffer::Order::SeqCst);
|
||||||
|
|
||||||
|
// 5. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
|
||||||
|
if (is_typed_array_out_of_bounds(typed_array_record))
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
|
||||||
|
|
||||||
|
// 6. Let byteLength be TypedArrayLength(taRecord).
|
||||||
|
auto byte_length = typed_array_length(typed_array_record);
|
||||||
|
|
||||||
|
// 7. Let result be FromHex(string, byteLength).
|
||||||
|
auto result = JS::from_hex(vm, string_value.as_string().utf8_string_view(), byte_length);
|
||||||
|
|
||||||
|
// 8. Let bytes be result.[[Bytes]].
|
||||||
|
auto bytes = move(result.bytes);
|
||||||
|
|
||||||
|
// 9. Let written be the length of bytes.
|
||||||
|
auto written = bytes.size();
|
||||||
|
|
||||||
|
// 10. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk.
|
||||||
|
// 11. Assert: written ≤ byteLength.
|
||||||
|
VERIFY(written <= byte_length);
|
||||||
|
|
||||||
|
// 12. Perform SetUint8ArrayBytes(into, bytes).
|
||||||
|
set_uint8_array_bytes(into, bytes);
|
||||||
|
|
||||||
|
// 13. If result.[[Error]] is not none, then
|
||||||
|
if (result.error.has_value()) {
|
||||||
|
// a. Throw result.[[Error]].
|
||||||
|
return result.error.release_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
|
||||||
|
auto result_object = Object::create(realm, realm.intrinsics().object_prototype());
|
||||||
|
|
||||||
|
// 15. Perform ! CreateDataPropertyOrThrow(resultObject, "read", 𝔽(result.[[Read]])).
|
||||||
|
MUST(result_object->create_data_property(vm.names.read, Value { result.read }));
|
||||||
|
|
||||||
|
// 16. Perform ! CreateDataPropertyOrThrow(resultObject, "written", 𝔽(written)).
|
||||||
|
MUST(result_object->create_data_property(vm.names.written, Value { written }));
|
||||||
|
|
||||||
|
// 17. Return resultObject.
|
||||||
|
return result_object;
|
||||||
|
}
|
||||||
|
|
||||||
// 7 ValidateUint8Array ( ta ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-validateuint8array
|
// 7 ValidateUint8Array ( ta ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-validateuint8array
|
||||||
ThrowCompletionOr<NonnullGCPtr<TypedArrayBase>> validate_uint8_array(VM& vm)
|
ThrowCompletionOr<NonnullGCPtr<TypedArrayBase>> validate_uint8_array(VM& vm)
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,6 +33,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_base64);
|
JS_DECLARE_NATIVE_FUNCTION(to_base64);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_hex);
|
JS_DECLARE_NATIVE_FUNCTION(to_hex);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(set_from_base64);
|
JS_DECLARE_NATIVE_FUNCTION(set_from_base64);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(set_from_hex);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Alphabet {
|
enum class Alphabet {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
describe("errors", () => {
|
||||||
|
test("called on non-Uint8Array object", () => {
|
||||||
|
expect(() => {
|
||||||
|
Uint8Array.prototype.setFromHex.call("");
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type Uint8Array");
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
Uint8Array.prototype.setFromHex.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.setFromHex("");
|
||||||
|
}).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.setFromHex("");
|
||||||
|
}).toThrowWithMessage(
|
||||||
|
TypeError,
|
||||||
|
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invalid string", () => {
|
||||||
|
expect(() => {
|
||||||
|
new Uint8Array(10).setFromHex(3.14);
|
||||||
|
}).toThrowWithMessage(TypeError, "3.14 is not a string");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("odd number of characters", () => {
|
||||||
|
expect(() => {
|
||||||
|
new Uint8Array(10).setFromHex("a");
|
||||||
|
}).toThrowWithMessage(SyntaxError, "Hex string must have an even length");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invalid alphabet", () => {
|
||||||
|
expect(() => {
|
||||||
|
new Uint8Array(10).setFromHex("qq");
|
||||||
|
}).toThrowWithMessage(SyntaxError, "Hex string must only contain hex characters");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("correct behavior", () => {
|
||||||
|
test("length is 1", () => {
|
||||||
|
expect(Uint8Array.prototype.setFromHex).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const decodeEqual = (input, expected) => {
|
||||||
|
expected = toUTF8Bytes(expected);
|
||||||
|
|
||||||
|
let array = new Uint8Array(expected.length);
|
||||||
|
let result = array.setFromHex(input);
|
||||||
|
|
||||||
|
expect(result.read).toBe(input.length);
|
||||||
|
expect(result.written).toBe(expected.length);
|
||||||
|
|
||||||
|
expect(array).toEqual(expected);
|
||||||
|
};
|
||||||
|
|
||||||
|
test("basic functionality", () => {
|
||||||
|
decodeEqual("", "");
|
||||||
|
decodeEqual("61", "a");
|
||||||
|
decodeEqual("616263646566303132333435", "abcdef012345");
|
||||||
|
decodeEqual("f09fa493", "🤓");
|
||||||
|
decodeEqual("f09fa493666f6ff09f9696", "🤓foo🖖");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue