Description
SetTypedArrayFromTypedArray specifies snapshotting a source ArrayBuffer when it matches the destination, preventing feed-forward in typedArray.set(typedArray, offset)
calls. But no such guards exist for the species-aware methods.
So, evaluating with the following helper, we see some surprising behavior (and nonconformances):
const spy = fn => {
// input: Int8Array[-1, -2, -3, -4] over a buffer that also includes a trailing 0
const { buffer } = Int8Array.of(-1, -2, -3, -4, 0);
const input = new Int8Array(buffer, 0, 4);
// species: return a new Int8Array over the same buffer but starting at index 1 (initial value -2)
input.constructor = {
[Symbol.species]: function custom(...args) {
delete input.constructor;
return new Int8Array(input.buffer, 1);
},
};
const output = fn(input);
if (output.buffer !== input.buffer) throw Error("buffer not reused");
return `[${input.join(" ")}]\n[${output.join(" ")}]`;
};
Call with receiver Int8Array[-1 -2 -3 -4] | Spec Requirement | Conforming Implementations | Nonconformances |
---|---|---|---|
spy(ta => { const ta2 = new Int8Array(ta.buffer, 0, ta.length - 1); ta.set(ta2, 1); return ta2; }) |
[-1 -1 -2 -3] [-1 -1 -2] (snapshot source but overwrite) |
engine262, GraalJS, Hermes, JavaScriptCore, LibJS, Moddable XS, QuickJS, SpiderMonkey, V8 | none found |
spy(ta => ta.subarray()) |
[-1 -2 -3 -4] [-2 -3 -4 0] (no writes) |
engine262, GraalJS, JavaScriptCore, LibJS, Moddable XS, QuickJS, SpiderMonkey, V8 | Hermes (byteOffset not respected?) [-1 -2 -3 -4] [-1 -2 -3 -4] |
spy(ta => ta.slice()) |
[-1 -1 -1 -1] [-1 -1 -1 -1] (byte-by-byte/element-by-element get+set) |
engine262, GraalJS, LibJS, QuickJS, SpiderMonkey, V8 | Hermes buffer not reused JavaScriptCore, Moddable XS (snapshot source?) [-1 -1 -2 -3] [-1 -2 -3 -4] |
spy(ta => ta.filter(x => x !== -4)) |
[-1 -1 -2 -3] [-1 -2 -3 0] (select matches, then write) |
engine262, GraalJS, JavaScriptCore, LibJS, QuickJS, SpiderMonkey, V8 | Hermes buffer not reused Moddable XS [-1 -2 -3 -4] [-2 -3 -4 0] |
spy(ta => ta.map(x => x - 4)) |
[-1 -5 -9 -13] [-5 -9 -13 -17] (element-by-element get+callback+set) |
engine262, GraalJS, JavaScriptCore, LibJS, Moddable XS, QuickJS, SpiderMonkey, V8 | Hermes buffer not reused |
The nonconformances imply some test262 coverage gaps, but regardless, the specification itself has a surprising mix of byte-by-byte/element-by-element operations that are susceptible to feed-forward (slice
and map
) and bulk operations that are not (set
and filter
). slice
in particular stands out, because bulk behavior would be a more obvious implementation (at least for same-type source–destination pairs). But that's probably acceptable, because trying to mimic subarray
like that is obvious abuse of the API—in which case the odd one out is filter
, which requires arbitrarily deep buffering of results before issuing writes (but on the other hand, supporting the simple "Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) ») step).
Given the certain rarity of such use, it might be worth pursuing spec changes, although on balance I think I've talked myself out of them.