Skip to content

Commit

Permalink
[JSC] Throw RangeError if Set methods are called on an object with ne…
Browse files Browse the repository at this point in the history
…gative "size" property

https://bugs.webkit.org/show_bug.cgi?id=267494
<rdar://problem/121310940>

Reviewed by Justin Michaud and Yusuke Suzuki.

This change implements steps 6-7 of GetSetRecord [1], ensuring a RangeError is thrown if
result of ToIntegerOrInfinity is negative, and extracts always-inlineable @getSetSizeAsInt().

These methods are at stage 3 of ECMA-262 standardization process, meaning we shouldn't worry
too much about performance impact.

Aligns JSC with V8, preventing most of newly-added Set methods from returning wrong results
when given an malformed Set-like object.

[1]: https://tc39.es/proposal-set-methods/#sec-getsetrecord

* JSTests/stress/set-prototype-difference.js:
* JSTests/stress/set-prototype-intersection.js:
* JSTests/stress/set-prototype-isDisjointfrom.js:
* JSTests/stress/set-prototype-isSubsetOf.js:
* JSTests/stress/set-prototype-isSupersetOf.js:
* JSTests/stress/set-prototype-symmetricDifference.js:
* JSTests/stress/set-prototype-union.js:
* Source/JavaScriptCore/builtins/SetPrototype.js:
(linkTimeConstant.alwaysInline.getSetSizeAsInt):
(isSubsetOf):

Canonical link: https://commits.webkit.org/274009@main
  • Loading branch information
Alexey Shvayka committed Feb 2, 2024
1 parent 91c1a5e commit eeda728
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 63 deletions.
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-difference.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,28 @@ try {
// Not an object
set1.difference(1);
} catch (e) {
if (e != "TypeError: Set.prototype.difference expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.difference({ });
} catch (e) {
if (e != "TypeError: Set.prototype.difference expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.difference({ size: NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.difference expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.difference({ size: -1 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-intersection.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,28 @@ try {
// Not an object
set1.intersection(1);
} catch (e) {
if (e != "TypeError: Set.prototype.intersection expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.intersection({ });
} catch (e) {
if (e != "TypeError: Set.prototype.intersection expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.intersection({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.intersection expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.intersection({ size: -4.5 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-isDisjointfrom.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,28 @@ try {
// Not an object
set1.isDisjointFrom(1);
} catch (e) {
if (e != "TypeError: Set.prototype.isDisjointFrom expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.isDisjointFrom({ });
} catch (e) {
if (e != "TypeError: Set.prototype.isDisjointFrom expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isDisjointFrom({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.isDisjointFrom expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isDisjointFrom({ size: -30 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-isSubsetOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,28 @@ try {
// Not an object
set1.isSubsetOf(1);
} catch (e) {
if (e != "TypeError: Set.prototype.isSubsetOf expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.isSubsetOf({ });
} catch (e) {
if (e != "TypeError: Set.prototype.isSubsetOf expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isSubsetOf({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.isSubsetOf expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isSubsetOf({ size: -3483548834553454.543543354 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-isSupersetOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,28 @@ try {
// Not an object
set1.isSupersetOf(1);
} catch (e) {
if (e != "TypeError: Set.prototype.isSupersetOf expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.isSupersetOf({ });
} catch (e) {
if (e != "TypeError: Set.prototype.isSupersetOf expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isSupersetOf({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.isSupersetOf expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.isSupersetOf({ size: -34787348578345787853478 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-symmetricDifference.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,28 @@ try {
// Not an object
set1.symmetricDifference(1);
} catch (e) {
if (e != "TypeError: Set.prototype.symmetricDifference expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.symmetricDifference({ });
} catch (e) {
if (e != "TypeError: Set.prototype.symmetricDifference expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.symmetricDifference({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.symmetricDifference expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.symmetricDifference({ size: -1 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
13 changes: 10 additions & 3 deletions JSTests/stress/set-prototype-union.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,28 @@ try {
// Not an object
set1.union(1);
} catch (e) {
if (e != "TypeError: Set.prototype.union expects the first parameter to be an object")
if (e != "TypeError: Set operation expects first argument to be an object")
throw e;
}

try {
set1.union({ });
} catch (e) {
if (e != "TypeError: Set.prototype.union expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.union({ size:NaN });
} catch (e) {
if (e != "TypeError: Set.prototype.union expects other.size to be a non-NaN number")
if (e != "TypeError: Set operation expects first argument to have non-NaN 'size' property")
throw e;
}

try {
set1.union({ size: -435.2221 });
} catch (e) {
if (e != "RangeError: Set operation expects first argument to have non-negative 'size' property")
throw e;
}

Expand Down
68 changes: 26 additions & 42 deletions Source/JavaScriptCore/builtins/SetPrototype.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ function forEach(callback /*, thisArg */)
} while (true);
}

// https://tc39.es/proposal-set-methods/#sec-getsetrecord (steps 1-7)
@linkTimeConstant
@alwaysInline
function getSetSizeAsInt(other)
{
if (!@isObject(other))
@throwTypeError("Set operation expects first argument to be an object");

var size = @toNumber(other.size);
if (size !== size) // is NaN?
@throwTypeError("Set operation expects first argument to have non-NaN 'size' property");

var sizeInt = @toIntegerOrInfinity(size);
if (sizeInt < 0)
@throwRangeError("Set operation expects first argument to have non-negative 'size' property");

return sizeInt;
}

function union(other)
{
"use strict";
Expand All @@ -53,12 +72,7 @@ function union(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.union expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.union expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other); // unused but @getSetSizeAsInt call is observable

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -88,12 +102,7 @@ function intersection(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.intersection expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.intersection expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other);

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -138,12 +147,7 @@ function difference(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.difference expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.difference expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other);

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -188,12 +192,7 @@ function symmetricDifference(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.symmetricDifference expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.symmetricDifference expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other); // unused but @getSetSizeAsInt call is observable

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -227,12 +226,7 @@ function isSubsetOf(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.isSubsetOf expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.isSubsetOf expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other);

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -267,12 +261,7 @@ function isSupersetOf(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.isSupersetOf expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.isSupersetOf expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other);

var has = other.has;
if (!@isCallable(has))
Expand Down Expand Up @@ -305,12 +294,7 @@ function isDisjointFrom(other)
@throwTypeError("Set operation called on non-Set object");

// Get Set Record
if (!@isObject(other))
@throwTypeError("Set.prototype.isDisjointFrom expects the first parameter to be an object");
var size = @toNumber(other.size);
// size is NaN
if (size !== size)
@throwTypeError("Set.prototype.isDisjointFrom expects other.size to be a non-NaN number");
var size = @getSetSizeAsInt(other);

var has = other.has;
if (!@isCallable(has))
Expand Down

0 comments on commit eeda728

Please sign in to comment.