Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

normative: avoid userland re-entrance after a read in TypedArray.prototype.toSpliced #89

Merged
merged 2 commits into from Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 26 additions & 11 deletions polyfill.js
Expand Up @@ -129,6 +129,27 @@
}
}

/**
* @param {TypedArray} example
* @param {unknown} value
* @description convert `value` to bigint or number based on the the type of array
* @returns {bigint | number}
* @throws if one of the override methods throws. e.g. `@@toPrimitive`, `valueOf`, `toString`
*/
function typedArrayNumberConversion(example, value) {
let asNumber;
{
if (isBigIntArray(example)) {
asNumber = 0n;
} else {
asNumber = -0; // important to use `-0` and not `0`
}
// @ts-ignore : using `+=` to emulate ToBigInt or ToNumber
asNumber += value;
}
return asNumber;
}

defineArrayMethods({
toReversed() {
const o = toObject(this);
Expand Down Expand Up @@ -239,8 +260,11 @@
const o = assertTypedArray(this);
const len = typedArrayLength(o);
const { actualStart, actualDeleteCount, newLen } = calculateSplice({ start, deleteCount, len, values, argsCount: arguments.length });
const convertedValues = values.map(v => {
return typedArrayNumberConversion(o, v);
})
const a = typedArrayCreate(o, newLen);
doSplice({ src: o, target: a, actualStart, actualDeleteCount, values, newLen });
doSplice({ src: o, target: a, actualStart, actualDeleteCount, values: convertedValues, newLen });
return a;
}
});
Expand Down Expand Up @@ -269,16 +293,7 @@
const len = typedArrayLength(o);
const relativeIndex = toIntegerOrInfinity(index);
const actualIndex = relativeIndex < 0 ? len + relativeIndex : relativeIndex;
let asNumber;
{
if (isBigIntArray(o)) {
asNumber = 0n;
} else {
asNumber = -0; // important to use `-0` and not `0`
}
// @ts-ignore : using `+=` to emulate ToBigInt or ToNumber
asNumber += value;
}
const asNumber = typedArrayNumberConversion(o, value);
if (actualIndex < 0 || actualIndex >= len) {
throw new RangeError();
}
Expand Down
114 changes: 85 additions & 29 deletions polyfill.test.js
Expand Up @@ -219,6 +219,34 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.toSpliced performs type conversion early`, (t) => {
const orig = new TypedArray([1]);
const valueUserCodeWillInsert = 4;
const userCodeReturnValue = 5;
const expected = new TypedArray([valueUserCodeWillInsert, userCodeReturnValue]);

let userCodeExecuted = false;
/** @type any */
const val = {
valueOf() {
userCodeExecuted = true;
orig[0] = valueUserCodeWillInsert;
return userCodeReturnValue;
}
};

const idx = 1;
const delNum = 0;
const ins = [val];

const copy = orig.toSpliced(idx, delNum, ...ins);

t.deepEqual(copy, expected);
t.notEqual(orig, copy);
t.notDeepEqual(orig, copy);
t.end();
});

tape(`${TypedArray.name}.prototype.with`, (t) => {
const orig = new TypedArray([1, 1, 3]);
const expected = new TypedArray([1, 2, 3]);
Expand Down Expand Up @@ -306,10 +334,10 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
[
BigInt64Array,
BigUint64Array
].forEach((TypedArray) => {
tape(`${TypedArray.name}.prototype.toReversed`, (t) => {
const orig = new TypedArray([3n, 2n, 1n]);
const expected = new TypedArray([1n, 2n, 3n]);
].forEach((BigIntArray) => {
tape(`${BigIntArray.name}.prototype.toReversed`, (t) => {
const orig = new BigIntArray([3n, 2n, 1n]);
const expected = new BigIntArray([1n, 2n, 3n]);

const copy = orig.toReversed();

Expand All @@ -319,9 +347,9 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.toSorted`, (t) => {
const orig = new TypedArray([3n, 1n, 2n]);
const expected = new TypedArray([1n, 2n, 3n]);
tape(`${BigIntArray.name}.prototype.toSorted`, (t) => {
const orig = new BigIntArray([3n, 1n, 2n]);
const expected = new BigIntArray([1n, 2n, 3n]);

const copy = orig.toSorted();

Expand All @@ -331,9 +359,9 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.toSorted(compareFn)`, (t) => {
const orig = new TypedArray([3n, 1n, 2n]);
const expected = new TypedArray([3n, 2n, 1n]);
tape(`${BigIntArray.name}.prototype.toSorted(compareFn)`, (t) => {
const orig = new BigIntArray([3n, 1n, 2n]);
const expected = new BigIntArray([3n, 2n, 1n]);
function compareFn(a, b) {
return a > b ? -1 : 1;
}
Expand All @@ -346,9 +374,9 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.toSpliced`, (t) => {
const orig = new TypedArray([1n, -1n, 0n, -1n, 4n]);
const expected = new TypedArray([1n, 2n, 3n, 4n]);
tape(`${BigIntArray.name}.prototype.toSpliced`, (t) => {
const orig = new BigIntArray([1n, -1n, 0n, -1n, 4n]);
const expected = new BigIntArray([1n, 2n, 3n, 4n]);
const idx = 1;
const delNum = 3;
const ins = [2n, 3n];
Expand All @@ -361,9 +389,37 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.with`, (t) => {
const orig = new TypedArray([1n, 1n, 3n]);
const expected = new TypedArray([1n, 2n, 3n]);
tape(`${BigIntArray.name}.prototype.toSpliced performs type conversion early`, (t) => {
const orig = new BigIntArray([1n]);
const valueUserCodeWillInsert = 4n;
const userCodeReturnValue = 5n;
const expected = new BigIntArray([valueUserCodeWillInsert, userCodeReturnValue]);

let userCodeExecuted = false;
/** @type any */
const val = {
valueOf() {
userCodeExecuted = true;
orig[0] = valueUserCodeWillInsert;
return userCodeReturnValue;
}
};

const idx = 1;
const delNum = 0;
const ins = [val];

const copy = orig.toSpliced(idx, delNum, ...ins);

t.deepEqual(copy, expected);
t.notEqual(orig, copy);
t.notDeepEqual(orig, copy);
t.end();
});

tape(`${BigIntArray.name}.prototype.with`, (t) => {
const orig = new BigIntArray([1n, 1n, 3n]);
const expected = new BigIntArray([1n, 2n, 3n]);
const idx = 1;
const val = 2n;

Expand All @@ -375,8 +431,8 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.with non bigint throws`, (t) => {
const orig = new TypedArray([1n, 2n, 2n]);
tape(`${BigIntArray.name}.prototype.with non bigint throws`, (t) => {
const orig = new BigIntArray([1n, 2n, 2n]);
const idx = 3;
const val = 4;

Expand All @@ -388,9 +444,9 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.with negativeIndex`, (t) => {
const orig = new TypedArray([1n, 2n, 2n]);
const expected = new TypedArray([1n, 2n, 3n]);
tape(`${BigIntArray.name}.prototype.with negativeIndex`, (t) => {
const orig = new BigIntArray([1n, 2n, 2n]);
const expected = new BigIntArray([1n, 2n, 3n]);
const idx = -1;
const val = 3n;

Expand All @@ -402,8 +458,8 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.with out of bounds`, (t) => {
const orig = new TypedArray([1n, 2n, 2n]);
tape(`${BigIntArray.name}.prototype.with out of bounds`, (t) => {
const orig = new BigIntArray([1n, 2n, 2n]);
const idx = 3;
const val = 4n;

Expand All @@ -414,12 +470,12 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name}.prototype.with executes 'user code' before starting copy`, (t) => {
const orig = new TypedArray([1n, 2n, 3n]);
tape(`${BigIntArray.name}.prototype.with executes 'user code' before starting copy`, (t) => {
const orig = new BigIntArray([1n, 2n, 3n]);
const idx = 1;
const valueUserCodeWillInsert = 4n;
const userCodeReturnValue = 5n;
const expected = new TypedArray([valueUserCodeWillInsert, userCodeReturnValue, 3n]);
const expected = new BigIntArray([valueUserCodeWillInsert, userCodeReturnValue, 3n]);
let userCodeExecuted = false;
/** @type any */
const val = {
Expand All @@ -437,12 +493,12 @@ tape("Array.prototype[Symbol.unscopables]", (t) => {
t.end();
});

tape(`${TypedArray.name} does not use Symbol.species for the new methods`, (t) => {
class SubClass extends TypedArray { }
tape(`${BigIntArray.name} does not use Symbol.species for the new methods`, (t) => {
class SubClass extends BigIntArray { }

function assertType(arr) {
t.equal(arr instanceof SubClass, false);
t.equal(arr instanceof TypedArray, true);
t.equal(arr instanceof BigIntArray, true);
}

/** @type {BigInt64Array} */
Expand Down
12 changes: 7 additions & 5 deletions spec.html
Expand Up @@ -425,6 +425,11 @@ <h1>%TypedArray%.prototype.toSpliced ( _start_, _deleteCount_, ..._items_ )</h1>
1. Else if _relativeStart_ &lt; 0, let _actualStart_ be max(_len_ + _relativeStart_, 0).
1. Else, let _actualStart_ be min(_relativeStart_, _len_).
1. Let _insertCount_ be the number of elements in _items_.
acutmore marked this conversation as resolved.
Show resolved Hide resolved
1. Let _convertedItems_ be a new empty List.
1. For each element _E_ of _items_, do
1. If _O_.[[ContentType]] is ~BigInt~, let _convertedValue_ be ? ToBigInt(_E_).
1. Else, let _convertedValue_ be ? ToNumber(_E_).
1. Append _convertedValue_ as the last element of _convertedItems_.
1. If _start_ is not present, then
1. Let _actualDeleteCount_ be 0.
1. Else if _deleteCount_ is not present, then
Expand All @@ -441,9 +446,9 @@ <h1>%TypedArray%.prototype.toSpliced ( _start_, _deleteCount_, ..._items_ )</h1>
1. Let _iValue_ be ! Get(_src_, _Pi_).
1. Perform ! Set(_target_, _Pi_, _iValue_, *true*).
1. Set _i_ to _i_ + 1.
1. For each element _E_ of _items_, do
1. For each element _E_ of _convertedItems_, do
1. Let _Pi_ be ! ToString(𝔽(_i_)).
1. [id="step-typedarray-tospliced-set"] Perform ? Set(_A_, _Pi_, _E_, *true*).
1. Perform ! Set(_A_, _Pi_, _E_, *true*).
1. Set _i_ to _i_ + 1.
1. Repeat, while _r_ &lt; _newLen_,
1. Let _Pi_ be ! ToString(𝔽(_i_)).
Expand All @@ -454,9 +459,6 @@ <h1>%TypedArray%.prototype.toSpliced ( _start_, _deleteCount_, ..._items_ )</h1>
1. Set _r_ to _r_ + 1.
1. Return _A_.
</emu-alg>
<emu-note>
Step <emu-xref href="#step-typedarray-tospliced-set"></emu-xref> may return an abrupt completion because _E_ is a value of any ECMAScript language type and is passed to ToBigInt or ToNumber.
</emu-note>
</emu-clause>

<emu-clause id="sec-%typedarray%.prototype.with">
Expand Down