2016-01-07 02:29:50 +00:00
|
|
|
// Copyright 2015 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.
|
|
|
|
|
|
|
|
// Test the ES2015 @@species feature
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// Subclasses of Array construct themselves under map, etc
|
|
|
|
|
|
|
|
class MyArray extends Array { }
|
|
|
|
|
|
|
|
assertEquals(MyArray, new MyArray().map(()=>{}).constructor);
|
|
|
|
assertEquals(MyArray, new MyArray().filter(()=>{}).constructor);
|
|
|
|
assertEquals(MyArray, new MyArray().slice().constructor);
|
|
|
|
assertEquals(MyArray, new MyArray().splice().constructor);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(MyArray, new MyArray().concat([1]).constructor);
|
|
|
|
assertEquals(1, new MyArray().concat([1])[0]);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
// Subclasses can override @@species to return the another class
|
|
|
|
|
|
|
|
class MyOtherArray extends Array {
|
|
|
|
static get [Symbol.species]() { return MyArray; }
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(MyArray, new MyOtherArray().map(()=>{}).constructor);
|
|
|
|
assertEquals(MyArray, new MyOtherArray().filter(()=>{}).constructor);
|
|
|
|
assertEquals(MyArray, new MyOtherArray().slice().constructor);
|
|
|
|
assertEquals(MyArray, new MyOtherArray().splice().constructor);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(MyArray, new MyOtherArray().concat().constructor);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
// Array methods on non-arrays return arrays
|
|
|
|
|
|
|
|
class MyNonArray extends Array {
|
|
|
|
static get [Symbol.species]() { return MyObject; }
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyObject { }
|
|
|
|
|
|
|
|
assertEquals(MyObject,
|
|
|
|
Array.prototype.map.call(new MyNonArray(), ()=>{}).constructor);
|
|
|
|
assertEquals(MyObject,
|
|
|
|
Array.prototype.filter.call(new MyNonArray(), ()=>{}).constructor);
|
|
|
|
assertEquals(MyObject,
|
|
|
|
Array.prototype.slice.call(new MyNonArray()).constructor);
|
|
|
|
assertEquals(MyObject,
|
|
|
|
Array.prototype.splice.call(new MyNonArray()).constructor);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(MyObject,
|
|
|
|
Array.prototype.concat.call(new MyNonArray()).constructor);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
assertEquals(undefined,
|
|
|
|
Array.prototype.map.call(new MyNonArray(), ()=>{}).length);
|
|
|
|
assertEquals(undefined,
|
|
|
|
Array.prototype.filter.call(new MyNonArray(), ()=>{}).length);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(undefined,
|
|
|
|
Array.prototype.concat.call(new MyNonArray(), ()=>{}).length);
|
2016-01-07 02:29:50 +00:00
|
|
|
// slice and splice actually do explicitly define the length for some reason
|
|
|
|
assertEquals(0, Array.prototype.slice.call(new MyNonArray()).length);
|
|
|
|
assertEquals(0, Array.prototype.splice.call(new MyNonArray()).length);
|
|
|
|
|
|
|
|
// Cross-realm Arrays build same-realm arrays
|
|
|
|
|
|
|
|
var realm = Realm.create();
|
|
|
|
assertEquals(Array,
|
|
|
|
Array.prototype.map.call(
|
|
|
|
Realm.eval(realm, "[]"), ()=>{}).constructor);
|
|
|
|
assertFalse(Array === Realm.eval(realm, "[]").map(()=>{}).constructor);
|
|
|
|
assertFalse(Array === Realm.eval(realm, "[].map(()=>{}).constructor"));
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(Array,
|
|
|
|
Array.prototype.concat.call(
|
|
|
|
Realm.eval(realm, "[]")).constructor);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
// Defaults when constructor or @@species is missing or non-constructor
|
|
|
|
|
|
|
|
class MyDefaultArray extends Array {
|
|
|
|
static get [Symbol.species]() { return undefined; }
|
|
|
|
}
|
|
|
|
assertEquals(Array, new MyDefaultArray().map(()=>{}).constructor);
|
|
|
|
|
|
|
|
class MyOtherDefaultArray extends Array { }
|
|
|
|
assertEquals(MyOtherDefaultArray,
|
|
|
|
new MyOtherDefaultArray().map(()=>{}).constructor);
|
|
|
|
MyOtherDefaultArray.prototype.constructor = undefined;
|
|
|
|
assertEquals(Array, new MyOtherDefaultArray().map(()=>{}).constructor);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertEquals(Array, new MyOtherDefaultArray().concat().constructor);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
// Exceptions propagated when getting constructor @@species throws
|
|
|
|
|
|
|
|
class SpeciesError extends Error { }
|
|
|
|
class ConstructorError extends Error { }
|
|
|
|
class MyThrowingArray extends Array {
|
|
|
|
static get [Symbol.species]() { throw new SpeciesError; }
|
|
|
|
}
|
|
|
|
assertThrows(() => new MyThrowingArray().map(()=>{}), SpeciesError);
|
|
|
|
Object.defineProperty(MyThrowingArray.prototype, 'constructor', {
|
|
|
|
get() { throw new ConstructorError; }
|
|
|
|
});
|
|
|
|
assertThrows(() => new MyThrowingArray().map(()=>{}), ConstructorError);
|
|
|
|
|
|
|
|
// Previously unexpected errors from setting properties in arrays throw
|
|
|
|
|
|
|
|
class FrozenArray extends Array {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
Object.freeze(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assertThrows(() => new FrozenArray([1]).map(()=>0), TypeError);
|
|
|
|
assertThrows(() => new FrozenArray([1]).filter(()=>true), TypeError);
|
|
|
|
assertThrows(() => new FrozenArray([1]).slice(0, 1), TypeError);
|
|
|
|
assertThrows(() => new FrozenArray([1]).splice(0, 1), TypeError);
|
2016-01-26 06:32:57 +00:00
|
|
|
assertThrows(() => new FrozenArray([]).concat([1]), TypeError);
|
2016-01-07 02:29:50 +00:00
|
|
|
|
|
|
|
// Verify call counts and constructor parameters
|
|
|
|
|
|
|
|
var count;
|
|
|
|
var params;
|
|
|
|
class MyObservedArray extends Array {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
params = args;
|
|
|
|
}
|
|
|
|
static get [Symbol.species]() {
|
|
|
|
count++
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count = 0;
|
|
|
|
params = undefined;
|
|
|
|
assertEquals(MyObservedArray,
|
|
|
|
new MyObservedArray().map(()=>{}).constructor);
|
|
|
|
assertEquals(1, count);
|
|
|
|
assertArrayEquals([0], params);
|
|
|
|
|
|
|
|
count = 0;
|
|
|
|
params = undefined;
|
|
|
|
assertEquals(MyObservedArray,
|
|
|
|
new MyObservedArray().filter(()=>{}).constructor);
|
|
|
|
assertEquals(1, count);
|
|
|
|
assertArrayEquals([0], params);
|
|
|
|
|
2016-01-26 06:32:57 +00:00
|
|
|
count = 0;
|
|
|
|
params = undefined;
|
|
|
|
assertEquals(MyObservedArray,
|
|
|
|
new MyObservedArray().concat().constructor);
|
|
|
|
assertEquals(1, count);
|
|
|
|
assertArrayEquals([0], params);
|
|
|
|
|
2016-01-07 02:29:50 +00:00
|
|
|
count = 0;
|
|
|
|
params = undefined;
|
|
|
|
assertEquals(MyObservedArray,
|
|
|
|
new MyObservedArray().slice().constructor);
|
Optimize @@species based on a global 'protector' cell
This patch makes ArraySpeciesCreate fast in V8 by avoiding two property reads
when the following conditions are met:
- No Array instance has had its __proto__ reset
- No Array instance has had a constructor property defined
- Array.prototype has not had its constructor changed
- Array[Symbol.species] has not been reset
For subclasses of Array, or for conditions where one of these assumptions is
violated, the full lookup of species is done according to the ArraySpeciesCreate
algorithm. Although this is a "performance cliff", it does not come up in the
expected typical use case of @@species (Array subclassing), so it is hoped that
this can form a good start. Array subclasses will incur the slowness of looking
up @@species, but their use won't slow down invocations of, for example,
Array.prototype.slice on Array base class instances.
Possible future optimizations:
- For the fallback case where the assumptions don't hold, optimize the two
property lookups.
- For Array.prototype.slice and Array.prototype.splice, even if the full lookup
of @@species needs to take place, we still could take the rest of the C++
fastpath. However, to do this correctly requires changing the calling convention
from C++ to JS to pass the @@species out, so it is not attempted in this patch.
With this patch, microbenchmarks of Array.prototype.slice do not suffer a
noticeable performance regression, unlike their previous 2.5x penalty.
TBR=hpayer@chromium.org
Review URL: https://codereview.chromium.org/1689733002
Cr-Commit-Position: refs/heads/master@{#34199}
2016-02-22 21:01:29 +00:00
|
|
|
assertEquals(1, count);
|
2016-01-07 02:29:50 +00:00
|
|
|
assertArrayEquals([0], params);
|
|
|
|
|
|
|
|
count = 0;
|
|
|
|
params = undefined;
|
|
|
|
assertEquals(MyObservedArray,
|
|
|
|
new MyObservedArray().splice().constructor);
|
Optimize @@species based on a global 'protector' cell
This patch makes ArraySpeciesCreate fast in V8 by avoiding two property reads
when the following conditions are met:
- No Array instance has had its __proto__ reset
- No Array instance has had a constructor property defined
- Array.prototype has not had its constructor changed
- Array[Symbol.species] has not been reset
For subclasses of Array, or for conditions where one of these assumptions is
violated, the full lookup of species is done according to the ArraySpeciesCreate
algorithm. Although this is a "performance cliff", it does not come up in the
expected typical use case of @@species (Array subclassing), so it is hoped that
this can form a good start. Array subclasses will incur the slowness of looking
up @@species, but their use won't slow down invocations of, for example,
Array.prototype.slice on Array base class instances.
Possible future optimizations:
- For the fallback case where the assumptions don't hold, optimize the two
property lookups.
- For Array.prototype.slice and Array.prototype.splice, even if the full lookup
of @@species needs to take place, we still could take the rest of the C++
fastpath. However, to do this correctly requires changing the calling convention
from C++ to JS to pass the @@species out, so it is not attempted in this patch.
With this patch, microbenchmarks of Array.prototype.slice do not suffer a
noticeable performance regression, unlike their previous 2.5x penalty.
TBR=hpayer@chromium.org
Review URL: https://codereview.chromium.org/1689733002
Cr-Commit-Position: refs/heads/master@{#34199}
2016-02-22 21:01:29 +00:00
|
|
|
assertEquals(1, count);
|
2016-01-07 02:29:50 +00:00
|
|
|
assertArrayEquals([0], params);
|
|
|
|
|
|
|
|
// @@species constructor can be a Proxy, and the realm access doesn't
|
|
|
|
// crash
|
|
|
|
|
|
|
|
class MyProxyArray extends Array { }
|
|
|
|
let ProxyArray = new Proxy(MyProxyArray, {});
|
|
|
|
MyProxyArray.constructor = ProxyArray;
|
|
|
|
|
|
|
|
assertEquals(MyProxyArray, new ProxyArray().map(()=>{}).constructor);
|