Skip to content

Species-aware TypedArray methods have (inconsistent) feed-forward issues #3617

Open
@gibson042

Description

@gibson042

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions