Description
23.2.3.6 %TypedArray%.prototype.copyWithin detects length modification of the ArrayBuffer backing its receiver TypedArray caused by invoking user code during argument processing and uses the final length to set bufferByteLimit for breaking out of the copy loop before reaching the final iteration implied by count, but does so in such a way that copying stops upon encountering the first out-of-bounds index. As a result, conformance with the specification requires forward iteration to copy the longest still-in-bounds prefix but reverse iteration1 to no-op.
However, implementation reality (and presumably developer intuition) is that use of the longest applicable prefix is independent of forward vs. reverse iteration (with the sole exception of the still-immature Ladybird LibJS).
Copying by forward iteration (spec and all implementations use longest prefix)
$ eshost -sx '
const initial = [88, 0, 1, 2, 3, 88];
const baseLen = initial.length;
const buf = new ArrayBuffer(baseLen, { maxByteLength: baseLen });
const view = new Uint8Array(buf);
const ta = new Uint8Array(buf, 1);
const copyWithin = (to, [fromStart, fromEnd], newLen) => {
buf.resize(baseLen);
view.set(initial);
print(view.join(" ") + " →");
const mutateBufferAndReturnEnd = () => {
newLen !== null ? buf.resize(newLen) : buf.transfer();
return fromEnd;
};
ta.copyWithin(to, fromStart, { valueOf: mutateBufferAndReturnEnd });
print(buf.detached ? "<detached>" : view.join(" "));
};
copyWithin(1, [2, 4], 5);
copyWithin(1, [2, 4], 4);
copyWithin(1, [2, 2], null);
' | tr '#' '='
==== JavaScriptCore, LibJS, Moddable XS, SpiderMonkey, V8
88 0 1 2 3 88
88 0 2 3 3
88 0 1 2 3 88
88 0 2 2
88 0 1 2 3 88
<detached>
Copying by reverse iteration (only spec and LibJS don't use longest prefix)
$ eshost -sx '
const initial = [88, 0, 1, 2, 3, 88];
const baseLen = initial.length;
const buf = new ArrayBuffer(baseLen, { maxByteLength: baseLen });
const view = new Uint8Array(buf);
const ta = new Uint8Array(buf, 1);
const copyWithin = (to, [fromStart, fromEnd], newLen) => {
buf.resize(baseLen);
view.set(initial);
print(view.join(" ") + " →");
const mutateBufferAndReturnEnd = () => {
newLen !== null ? buf.resize(newLen) : buf.transfer();
return fromEnd;
};
ta.copyWithin(to, fromStart, { valueOf: mutateBufferAndReturnEnd });
print(buf.detached ? "<detached>" : view.join(" "));
};
copyWithin(2, [1, 3], 5);
copyWithin(2, [1, 3], 4);
copyWithin(2, [1, 1], null);
' | tr '#' '='
==== JavaScriptCore, Moddable XS, SpiderMonkey, V8
88 0 1 2 3 88
88 0 1 1 2
88 0 1 2 3 88
88 0 1 1
88 0 1 2 3 88
<detached>
==== LibJS
88 0 1 2 3 88
88 0 1 1 2
88 0 1 2 3 88
88 0 1 2
88 0 1 2 3 88
<detached>
Footnotes
-
Reverse iteration is used when the target index is itself included in a non-degenerate source range and thus otherwise at risk of being overwritten before it is read. ↩