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

Commit

Permalink
Merge pull request #89 from acutmore/ta-tospliced-update
Browse files Browse the repository at this point in the history
normative: avoid userland re-entrance after a read in TypedArray.prototype.toSpliced
  • Loading branch information
Robin Ricard committed Jun 24, 2022
2 parents 435df6c + 6752fb6 commit 7d6a17a
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 45 deletions.
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_.
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

0 comments on commit 7d6a17a

Please sign in to comment.