mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibJS: Add %TypedArray%.prototype.copyWithin
This fixes 44 test262 cases.
This commit is contained in:
parent
1b4138e687
commit
c7a839bb24
Notes:
sideshowbarker
2024-07-18 10:04:07 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/c7a839bb24f Pull-request: https://github.com/SerenityOS/serenity/pull/8552 Reviewed-by: https://github.com/trflynn89
3 changed files with 282 additions and 0 deletions
|
@ -40,6 +40,7 @@ void TypedArrayPrototype::initialize(GlobalObject& object)
|
|||
define_native_function(vm.names.entries, entries, 0, attr);
|
||||
define_native_function(vm.names.set, set, 1, attr);
|
||||
define_native_function(vm.names.reverse, reverse, 0, attr);
|
||||
define_native_function(vm.names.copyWithin, copy_within, 2, attr);
|
||||
|
||||
define_native_accessor(*vm.well_known_symbol_to_string_tag(), to_string_tag_getter, nullptr, Attribute::Configurable);
|
||||
|
||||
|
@ -529,4 +530,183 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reverse)
|
|||
return typed_array;
|
||||
}
|
||||
|
||||
// 23.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin
|
||||
JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within)
|
||||
{
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? ValidateTypedArray(O).
|
||||
auto* typed_array = typed_array_from(vm, global_object);
|
||||
if (!typed_array)
|
||||
return {};
|
||||
|
||||
// 3. Let len be O.[[ArrayLength]].
|
||||
auto length = typed_array->array_length();
|
||||
|
||||
// 4. Let relativeTarget be ? ToIntegerOrInfinity(target).
|
||||
auto relative_target = vm.argument(0).to_integer_or_infinity(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
double to;
|
||||
if (Value(relative_target).is_negative_infinity()) {
|
||||
// 5. If relativeTarget is -∞, let to be 0.
|
||||
to = 0.0;
|
||||
} else if (relative_target < 0) {
|
||||
// 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0)
|
||||
to = max(length + relative_target, 0.0);
|
||||
} else {
|
||||
// 7. Else, let to be min(relativeTarget, len).
|
||||
to = min(relative_target, (double)length);
|
||||
}
|
||||
|
||||
// 8. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
auto relative_start = vm.argument(1).to_integer_or_infinity(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
double from;
|
||||
if (Value(relative_start).is_negative_infinity()) {
|
||||
// 9. If relativeStart is -∞, let from be 0.
|
||||
from = 0.0;
|
||||
} else if (relative_start < 0) {
|
||||
// 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
|
||||
from = max(length + relative_start, 0.0);
|
||||
} else {
|
||||
// 11. Else, let from be min(relativeStart, len).
|
||||
from = min(relative_start, (double)length);
|
||||
}
|
||||
|
||||
double relative_end;
|
||||
|
||||
// 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
if (vm.argument(2).is_undefined()) {
|
||||
relative_end = length;
|
||||
} else {
|
||||
relative_end = vm.argument(2).to_integer_or_infinity(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
|
||||
double final;
|
||||
if (Value(relative_end).is_negative_infinity()) {
|
||||
// 13. If relativeEnd is -∞, let final be 0.
|
||||
final = 0.0;
|
||||
} else if (relative_end < 0) {
|
||||
// 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
|
||||
final = max(length + relative_end, 0.0);
|
||||
} else {
|
||||
// 15. Else, let final be min(relativeEnd, len).
|
||||
final = min(relative_end, (double)length);
|
||||
}
|
||||
|
||||
// 16. Let count be min(final - from, len - to).
|
||||
double count = min(final - from, length - to);
|
||||
|
||||
// 17. If count > 0, then
|
||||
if (count > 0.0) {
|
||||
// a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data.
|
||||
|
||||
// b. Let buffer be O.[[ViewedArrayBuffer]].
|
||||
auto buffer = typed_array->viewed_array_buffer();
|
||||
|
||||
// c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (buffer->is_detached()) {
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::DetachedArrayBuffer);
|
||||
return {};
|
||||
}
|
||||
|
||||
// d. Let typedArrayName be the String value of O.[[TypedArrayName]].
|
||||
// e. Let elementSize be the Element Size value specified in Table 64 for typedArrayName.
|
||||
auto element_size = typed_array->element_size();
|
||||
|
||||
// f. Let byteOffset be O.[[ByteOffset]].
|
||||
auto byte_offset = typed_array->byte_offset();
|
||||
|
||||
// FIXME: Not exactly sure what we should do when overflow occurs.
|
||||
// Just return as if succeeded for now. (This goes for steps g to j)
|
||||
|
||||
// g. Let toByteIndex be to × elementSize + byteOffset.
|
||||
Checked<size_t> to_byte_index_checked = static_cast<size_t>(to);
|
||||
to_byte_index_checked *= element_size;
|
||||
to_byte_index_checked += byte_offset;
|
||||
if (to_byte_index_checked.has_overflow()) {
|
||||
dbgln("TypedArrayPrototype::copy_within: to_byte_index overflowed, returning as if succeeded.");
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
// h. Let fromByteIndex be from × elementSize + byteOffset.
|
||||
Checked<size_t> from_byte_index_checked = static_cast<size_t>(from);
|
||||
from_byte_index_checked *= element_size;
|
||||
from_byte_index_checked += byte_offset;
|
||||
if (from_byte_index_checked.has_overflow()) {
|
||||
dbgln("TypedArrayPrototype::copy_within: from_byte_index_checked overflowed, returning as if succeeded.");
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
// i. Let countBytes be count × elementSize.
|
||||
Checked<size_t> count_bytes_checked = static_cast<size_t>(count);
|
||||
count_bytes_checked *= element_size;
|
||||
if (count_bytes_checked.has_overflow()) {
|
||||
dbgln("TypedArrayPrototype::copy_within: count_bytes_checked overflowed, returning as if succeeded.");
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
auto to_byte_index = to_byte_index_checked.value();
|
||||
auto from_byte_index = from_byte_index_checked.value();
|
||||
auto count_bytes = count_bytes_checked.value();
|
||||
|
||||
Checked<size_t> from_plus_count = from_byte_index;
|
||||
from_plus_count += count_bytes;
|
||||
if (from_plus_count.has_overflow()) {
|
||||
dbgln("TypedArrayPrototype::copy_within: from_plus_count overflowed, returning as if succeeded.");
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
i8 direction;
|
||||
|
||||
// j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then
|
||||
if (from_byte_index < to_byte_index && to_byte_index < from_plus_count.value()) {
|
||||
// i. Let direction be -1.
|
||||
direction = -1;
|
||||
|
||||
// ii. Set fromByteIndex to fromByteIndex + countBytes - 1.
|
||||
from_byte_index = from_plus_count.value() - 1;
|
||||
|
||||
Checked<size_t> to_plus_count = to_byte_index;
|
||||
to_plus_count += count_bytes;
|
||||
if (to_plus_count.has_overflow()) {
|
||||
dbgln("TypedArrayPrototype::copy_within: to_plus_count overflowed, returning as if succeeded.");
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
// iii. Set toByteIndex to toByteIndex + countBytes - 1.
|
||||
to_byte_index = to_plus_count.value() - 1;
|
||||
} else {
|
||||
// k. Else,
|
||||
// i. Let direction be 1.
|
||||
direction = 1;
|
||||
}
|
||||
|
||||
// l. Repeat, while countBytes > 0,
|
||||
for (; count_bytes > 0; --count_bytes) {
|
||||
// i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered).
|
||||
auto value = buffer->get_value<u8>(from_byte_index, true, ArrayBuffer::Order::Unordered);
|
||||
|
||||
// ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered).
|
||||
buffer->set_value<u8>(to_byte_index, value, true, ArrayBuffer::Order::Unordered);
|
||||
|
||||
// iii. Set fromByteIndex to fromByteIndex + direction.
|
||||
from_byte_index += direction;
|
||||
|
||||
// iv. Set toByteIndex to toByteIndex + direction.
|
||||
to_byte_index += direction;
|
||||
|
||||
// v. Set countBytes to countBytes - 1.
|
||||
}
|
||||
}
|
||||
|
||||
// 18. Return O.
|
||||
return typed_array;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(entries);
|
||||
JS_DECLARE_NATIVE_FUNCTION(set);
|
||||
JS_DECLARE_NATIVE_FUNCTION(reverse);
|
||||
JS_DECLARE_NATIVE_FUNCTION(copy_within);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
const TYPED_ARRAYS = [
|
||||
Uint8Array,
|
||||
Uint8ClampedArray,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Int8Array,
|
||||
Int16Array,
|
||||
Int32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
];
|
||||
|
||||
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
|
||||
|
||||
test("length is 2", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
expect(T.prototype.copyWithin).toHaveLength(2);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
expect(T.prototype.copyWithin).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behaviour", () => {
|
||||
test("Noop", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1, 2]);
|
||||
expect(array.copyWithin(0, 0)).toEqual(array);
|
||||
expect(array).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1n, 2n]);
|
||||
expect(array.copyWithin(0, 0)).toEqual(array);
|
||||
expect(array).toEqual([1n, 2n]);
|
||||
});
|
||||
});
|
||||
|
||||
test("basic behaviour", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1, 2, 3]);
|
||||
expect(array.copyWithin(1, 2)).toEqual(array);
|
||||
expect(array).toEqual([1, 3, 3]);
|
||||
|
||||
expect(array.copyWithin(2, 0)).toEqual(array);
|
||||
expect(array).toEqual([1, 3, 1]);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1n, 2n, 3n]);
|
||||
expect(array.copyWithin(1, 2)).toEqual(array);
|
||||
expect(array).toEqual([1n, 3n, 3n]);
|
||||
|
||||
expect(array.copyWithin(2, 0)).toEqual(array);
|
||||
expect(array).toEqual([1n, 3n, 1n]);
|
||||
});
|
||||
});
|
||||
|
||||
test("start > target", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1, 2, 3]);
|
||||
expect(array.copyWithin(0, 1)).toEqual(array);
|
||||
expect(array).toEqual([2, 3, 3]);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1n, 2n, 3n]);
|
||||
expect(array.copyWithin(0, 1)).toEqual(array);
|
||||
expect(array).toEqual([2n, 3n, 3n]);
|
||||
});
|
||||
});
|
||||
|
||||
test("overwriting behavior", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1, 2, 3]);
|
||||
expect(array.copyWithin(1, 0)).toEqual(array);
|
||||
expect(array).toEqual([1, 1, 2]);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1n, 2n, 3n]);
|
||||
expect(array.copyWithin(1, 0)).toEqual(array);
|
||||
expect(array).toEqual([1n, 1n, 2n]);
|
||||
});
|
||||
});
|
||||
|
||||
test("specify end", () => {
|
||||
TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1, 2, 3]);
|
||||
expect(array.copyWithin(2, 0, 1)).toEqual(array);
|
||||
expect(array).toEqual([1, 2, 1]);
|
||||
});
|
||||
|
||||
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||
const array = new T([1n, 2n, 3n]);
|
||||
expect(array.copyWithin(2, 0, 1)).toEqual(array);
|
||||
expect(array).toEqual([1n, 2n, 1n]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue