v8/test/mjsunit/es6/array-copywithin.js
Timothy Gu 29c1eab92e [builtins] Clean up the use of class_name / ES5 [[Class]]
Before ES2015, the ES spec had a [[Class]] internal slot for all
objects, which Object.prototype.toString() would use to figure the
returned string. Post-ES2015, the [[Class]] slot was removed in spec for
all objects, with the @@toStringTag well-known symbol the proper way to
change Object.prototype.toString() output.

At the time, spec-identical handling without the use of [[Class]] was
implemented in V8 for all objects other than API objects, where issues
with the Web IDL spec [1] prevented Blink, and hence V8, to totally
migrate to @@toStringTag. However, since 2016 [2] Blink has been setting
@@toStringTag on API class prototypes to manage the
Object.prototype.toString() output, so the legacy [[Class]] handling in
V8 has not been necessary for the past couple of years.

This CL removes the remaining legacy [[Class]] handling in
Object.prototype.toString(), JSReceiver::class_name(), and
GetConstructorName(). However, it does not remove the class_name field
in FunctionTemplateInfo, as it is still used for the `name` property of
created functions.

This CL also cleans up other places in the codebase that still reference
[[Class]].

This change should have minimal impact on web-compatibility. For the
change to be observable, a script must do one of the following:

1. delete APIConstructor.prototype[Symbol.toStringTag];
2. Object.setPrototypeOf(apiObject, somethingElse);

Before this CL, these changes will not change the apiObject.toString()
output. But after this CL, they will make apiObject.toString() show
"[object Object]" (in the first case) or the @@toStringTag of the other
prototype (in the latter case).

However, both are deemed unlikely. @@toStringTag is not well-known
feature of JavaScript, nor does it get tampered much on API
constructors. In the second case, setting the prototype of an API object
would effectly render the object useless, as all its methods (including
property getters/setters) would no longer be accessible.

Currently, @@toStringTag-based API object branding is not yet
implemented by other browsers. This V8 bug in particular has been an
impediment to standardizing toString behavior. Fixing this bug will
unblock [3] and lead to a better Web IDL spec, and better toString()
compatibility for all.

[1]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=28244
[2]: https://crrev.com/909c0d7d5a53c8526ded351683c65ea7d17531d4
[3]: https://github.com/heycam/webidl/pull/357

Bug: chromium:793406
Cq-Include-Trybots: luci.chromium.try:linux-rel
Change-Id: Iceded24e37afa2646ec385d5018909f55b177f93
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2146996
Commit-Queue: Timothy Gu <timothygu@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67327}
2020-04-23 00:05:38 +00:00

337 lines
9.2 KiB
JavaScript

// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function copyWithinArity() {
assertEquals(Array.prototype.copyWithin.length, 2);
})();
(function copyWithinTargetAndStart() {
// works with two arguemnts
assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3));
assertArrayEquals([1, 4, 5, 4, 5], [1, 2, 3, 4, 5].copyWithin(1, 3));
assertArrayEquals([1, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(1, 2));
assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(2, 2));
})();
(function copyWithinTargetStartAndEnd() {
// works with three arguments
assertArrayEquals([1, 2, 3, 4, 5].copyWithin(0, 3, 4), [4, 2, 3, 4, 5]);
assertArrayEquals([1, 2, 3, 4, 5].copyWithin(1, 3, 4), [1, 4, 3, 4, 5]);
assertArrayEquals([1, 2, 3, 4, 5].copyWithin(1, 2, 4), [1, 3, 4, 4, 5]);
})();
(function copyWithinNegativeRelativeOffsets() {
// works with negative arguments
assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, -2));
assertArrayEquals([4, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, -2, -1));
assertArrayEquals([1, 3, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3, -2));
assertArrayEquals([1, 3, 4, 4, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3, -1));
assertArrayEquals([1, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3));
// test with arguments equal to -this.length
assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-5, 0));
})();
(function copyWithinArrayLikeValues() {
// works with array-like values
var args = (function () { return arguments; }(1, 2, 3));
Array.prototype.copyWithin.call(args, -2, 0);
assertArrayEquals([1, 1, 2], Array.prototype.slice.call(args));
// Object.prototype.toString branding does not change
assertArrayEquals("[object Arguments]", Object.prototype.toString.call(args));
})();
(function copyWithinNullThis() {
// throws on null/undefined values
assertThrows(function() {
return Array.prototype.copyWithin.call(null, 0, 3);
}, TypeError);
})();
(function copyWithinUndefinedThis() {
assertThrows(function() {
return Array.prototype.copyWithin.call(undefined, 0, 3);
}, TypeError);
})();
// TODO(caitp): indexed properties of String are read-only and setting them
// should throw in strict mode. See bug v8:4042
// (function copyWithinStringThis() {
// // test with this value as string
// assertThrows(function() {
// return Array.prototype.copyWithin.call("hello world", 0, 3);
// }, TypeError);
// })();
(function copyWithinNumberThis() {
// test with this value as number
assertEquals(34, Array.prototype.copyWithin.call(34, 0, 3).valueOf());
})();
(function copyWithinSymbolThis() {
// test with this value as number
var sym = Symbol("test");
assertEquals(sym, Array.prototype.copyWithin.call(sym, 0, 3).valueOf());
})();
(function copyyWithinTypedArray() {
// test with this value as TypedArray
var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
for (var i=0; i<int32View.length; i++) {
int32View[i] = i*2;
}
assertArrayEquals(new Int32Array([2, 4, 6, 6]),
Array.prototype.copyWithin.call(int32View, 0, 1));
})();
(function copyWithinSloppyArguments() {
// if arguments object is sloppy, copyWithin must move the arguments around
function f(a, b, c, d, e) {
[].copyWithin.call(arguments, 1, 3);
return [a, b, c, d, e];
}
assertArrayEquals([1, 4, 5, 4, 5], f(1, 2, 3, 4, 5));
})();
(function copyWithinStartLessThanTarget() {
// test with target > start on 2 arguments
assertArrayEquals([1, 2, 3, 1, 2], [1, 2, 3, 4, 5].copyWithin(3, 0));
// test with target > start on 3 arguments
assertArrayEquals([1, 2, 3, 1, 2], [1, 2, 3, 4, 5].copyWithin(3, 0, 4));
})();
(function copyWithinArrayWithHoles() {
// test on array with holes
var arr = new Array(6);
for (var i = 0; i < arr.length; i += 2) {
arr[i] = i;
}
assertArrayEquals([, 4, , , 4, , ], arr.copyWithin(0, 3));
})();
(function copyWithinArrayLikeWithHoles() {
// test on array-like object with holes
assertArrayEquals({
length: 6,
1: 4,
4: 4
}, Array.prototype.copyWithin.call({
length: 6,
0: 0,
2: 2,
4: 4
}, 0, 3));
})();
(function copyWithinNonIntegerRelativeOffsets() {
// test on fractional arguments
assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0.2, 3.9));
})();
(function copyWithinNegativeZeroTarget() {
// test with -0
assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-0, 3));
})();
(function copyWithinTargetOutsideStart() {
// test with arguments more than this.length
assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 7));
// test with arguments less than -this.length
assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-7, 0));
})();
(function copyWithinEmptyArray() {
// test on empty array
assertArrayEquals([], [].copyWithin(0, 3));
})();
(function copyWithinTargetCutOff() {
// test with target range being shorter than end - start
assertArrayEquals([1, 2, 2, 3, 4], [1, 2, 3, 4, 5].copyWithin(2, 1, 4));
})();
(function copyWithinOverlappingRanges() {
// test overlapping ranges
var arr = [1, 2, 3, 4, 5];
arr.copyWithin(2, 1, 4);
assertArrayEquals([1, 2, 2, 2, 3], arr.copyWithin(2, 1, 4));
})();
(function copyWithinStrictDelete() {
// check that [[Delete]] is strict (non-extensible via freeze)
assertThrows(function() {
return Object.freeze([1, , 3, , 4, 5]).copyWithin(2, 1, 4);
}, TypeError);
// check that [[Delete]] is strict (non-extensible via seal)
assertThrows(function() {
return Object.seal([1, , 3, , 4, 5]).copyWithin(2, 1, 4);
}, TypeError);
// check that [[Delete]] is strict (non-extensible via preventExtensions)
assertThrows(function() {
return Object.preventExtensions([1, , 3, , 4, 5]).copyWithin(2, 1, 4);
}, TypeError);
})();
(function copyWithinStrictSet() {
// check that [[Set]] is strict (non-extensible via freeze)
assertThrows(function() {
return Object.freeze([1, 2, 3, 4, 5]).copyWithin(0, 3);
}, TypeError);
// check that [[Set]] is strict (non-extensible via seal)
assertThrows(function() {
return Object.seal([, 2, 3, 4, 5]).copyWithin(0, 3);
}, TypeError);
// check that [[Set]] is strict (non-extensible via preventExtensions)
assertThrows(function() {
return Object.preventExtensions([ , 2, 3, 4, 5]).copyWithin(0, 3);
}, TypeError);
})();
(function copyWithinSetterThrows() {
function Boom() {}
// test if we throw in between
var arr = Object.defineProperty([1, 2, 3, 4, 5], 1, {
set: function () {
throw new Boom();
}
});
assertThrows(function() {
return arr.copyWithin(1, 3);
}, Boom);
assertArrayEquals([1, , 3, 4, 5], arr);
})();
(function copyWithinDefaultEnd() {
// undefined as third argument
assertArrayEquals(
[4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3, undefined));
})();
(function copyWithinGetLengthOnce() {
// test that this.length is called only once
var count = 0;
var arr = Object.defineProperty({ 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }, "length", {
get: function () {
count++;
return 5;
}
});
Array.prototype.copyWithin.call(arr, 1, 3);
assertEquals(1, count);
Array.prototype.copyWithin.call(arr, 1, 3, 4);
assertEquals(2, count);
})();
(function copyWithinLargeArray() {
var large = 10000;
// test on a large array
var arr = new Array(large);
assertArrayEquals(arr, arr.copyWithin(45, 9000));
var expected = new Array(large);
// test on floating point numbers
for (var i = 0; i < large; i++) {
arr[i] = Math.random();
expected[i] = arr[i];
if (i >= 9000) {
expected[(i - 9000) + 45] = arr[i];
}
}
assertArrayEquals(expected, arr.copyWithin(45, 9000));
// test on array of objects
for (var i = 0; i < large; i++) {
arr[i] = { num: Math.random() };
} + 45
arr.copyWithin(45, 9000);
// test copied by reference
for (var i = 9000; i < large; ++i) {
assertSame(arr[(i - 9000) + 45], arr[i]);
}
// test array length remains same
assertEquals(large, arr.length);
})();
(function copyWithinSuperLargeLength() {
// 2^53 - 1 is the maximum value returned from ToLength()
var large = Math.pow(2, 53) - 1;
var object = { length: large };
// Initialize last 10 entries
for (var i = 1; i <= 10; ++i) {
object[(large - 11) + i] = { num: i };
}
Array.prototype.copyWithin.call(object, 1, large - 10);
// Test copied values
for (var i = 1; i <= 10; ++i) {
var old_ref = object[(large - 11) + i];
var new_ref = object[i];
assertSame(old_ref, new_ref);
assertSame(new_ref.num, i);
}
// Assert length has not changed
assertEquals(large, object.length);
})();
(function copyWithinNullEnd() {
// test null on third argument is converted to +0
assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3, null));
})();
(function copyWithinElementsInObjectsPrototype() {
// tamper the global Object prototype and test this works
Object.prototype[2] = 1;
assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3));
delete Object.prototype[2];
Object.prototype[3] = "FAKE";
assertArrayEquals(["FAKE", 5, 3, "FAKE", 5], [1, 2, 3, , 5].copyWithin(0, 3));
delete Object.prototype[3];
})();