Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (195877 => 195878)
--- trunk/Source/_javascript_Core/ChangeLog 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-01-30 02:45:25 UTC (rev 195878)
@@ -1,3 +1,38 @@
+2016-01-29 Keith Miller <keith_mil...@apple.com>
+
+ Array.prototype native functions should use Symbol.species to construct the result
+ https://bugs.webkit.org/show_bug.cgi?id=153660
+
+ Reviewed by Saam Barati.
+
+ This patch adds support for Symbol.species in the Array.prototype native functions.
+ We make an optimization to avoid regressions on some benchmarks by using an
+ adaptive watchpoint to check if Array.prototype.constructor is ever changed.
+
+ * runtime/ArrayPrototype.cpp:
+ (JSC::putLength):
+ (JSC::setLength):
+ (JSC::speciesConstructArray):
+ (JSC::arrayProtoFuncConcat):
+ (JSC::arrayProtoFuncSlice):
+ (JSC::arrayProtoFuncSplice):
+ (JSC::ArrayPrototype::setConstructor):
+ (JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint):
+ (JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire):
+ * runtime/ArrayPrototype.h:
+ (JSC::ArrayPrototype::didChangeConstructorProperty):
+ * runtime/ConstructData.cpp:
+ (JSC::construct):
+ * runtime/ConstructData.h:
+ * runtime/JSGlobalObject.cpp:
+ (JSC::JSGlobalObject::init):
+ * tests/es6.yaml:
+ * tests/stress/array-species-functions.js: Added.
+ (Symbol.species):
+ (funcThrows):
+ (test.species):
+ (test):
+
2016-01-29 Filip Pizlo <fpi...@apple.com>
CallLinkStatus should trust BadCell exit sites whenever there is no stub
Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp 2016-01-30 02:45:25 UTC (rev 195878)
@@ -24,6 +24,8 @@
#include "config.h"
#include "ArrayPrototype.h"
+#include "AdaptiveInferredPropertyValueWatchpointBase.h"
+#include "ArrayConstructor.h"
#include "BuiltinNames.h"
#include "ButterflyInlines.h"
#include "CachedCall.h"
@@ -155,12 +157,64 @@
return obj->get(exec, exec->propertyNames().length).toUInt32(exec);
}
-static void putLength(ExecState* exec, JSObject* obj, JSValue value)
+static ALWAYS_INLINE void putLength(ExecState* exec, JSObject* obj, JSValue value)
{
PutPropertySlot slot(obj);
obj->methodTable()->put(obj, exec, exec->propertyNames().length, value, slot);
}
+static ALWAYS_INLINE void setLength(ExecState* exec, JSObject* obj, unsigned value)
+{
+ if (isJSArray(obj))
+ jsCast<JSArray*>(obj)->setLength(exec, value);
+ putLength(exec, obj, jsNumber(value));
+}
+
+enum class SpeciesConstructResult {
+ FastPath,
+ Exception,
+ CreatedObject
+};
+
+static ALWAYS_INLINE std::pair<SpeciesConstructResult, JSObject*> speciesConstructArray(ExecState* exec, JSObject* thisObject, unsigned length)
+{
+ // ECMA 9.4.2.3: https://tc39.github.io/ecma262/#sec-arrayspeciescreate
+ JSValue constructor = jsUndefined();
+ if (LIKELY(isJSArray(thisObject))) {
+ // Fast path in the normal case where the user has not set an own constructor and the Array.prototype.constructor is normal.
+ // We need prototype check for subclasses of Array, which are Array objects but have a different prototype by default.
+ if (LIKELY(!thisObject->hasCustomProperties()
+ && thisObject->globalObject()->arrayPrototype() == thisObject->prototype()
+ && !thisObject->globalObject()->arrayPrototype()->didChangeConstructorProperty()))
+ return std::make_pair(SpeciesConstructResult::FastPath, nullptr);
+
+ constructor = thisObject->get(exec, exec->propertyNames().constructor);
+ if (exec->hadException())
+ return std::make_pair(SpeciesConstructResult::Exception, nullptr);
+ if (constructor.isConstructor()) {
+ JSObject* constructorObject = jsCast<JSObject*>(constructor);
+ if (exec->lexicalGlobalObject() != constructorObject->globalObject())
+ return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
+ }
+ if (constructor.isObject()) {
+ constructor = constructor.get(exec, exec->propertyNames().speciesSymbol);
+ if (exec->hadException())
+ return std::make_pair(SpeciesConstructResult::Exception, nullptr);
+ if (constructor.isNull())
+ return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
+ }
+ }
+ if (constructor.isUndefined())
+ return std::make_pair(SpeciesConstructResult::FastPath, nullptr);;
+
+ MarkedArgumentBuffer args;
+ args.append(jsNumber(length));
+ JSObject* newObject = construct(exec, constructor, args, "Species construction did not get a valid constructor");
+ if (exec->hadException())
+ return std::make_pair(SpeciesConstructResult::Exception, nullptr);
+ return std::make_pair(SpeciesConstructResult::CreatedObject, newObject);
+}
+
static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
@@ -532,6 +586,11 @@
JSValue curArg = thisValue.toObject(exec);
Checked<unsigned, RecordOverflow> finalArraySize = 0;
+ // We need to do species construction before geting the rest of the elements.
+ std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, curArg.getObject(), 0);
+ if (speciesResult.first == SpeciesConstructResult::Exception)
+ return JSValue::encode(jsUndefined());
+
JSArray* currentArray = nullptr;
JSArray* previousArray = nullptr;
for (unsigned i = 0; ; ++i) {
@@ -552,16 +611,22 @@
if (finalArraySize.hasOverflowed())
return JSValue::encode(throwOutOfMemoryError(exec));
- if (argCount == 1 && previousArray && currentArray && finalArraySize.unsafeGet() < MIN_SPARSE_ARRAY_INDEX) {
+ if (speciesResult.first == SpeciesConstructResult::FastPath && argCount == 1 && previousArray && currentArray && finalArraySize.unsafeGet() < MIN_SPARSE_ARRAY_INDEX) {
IndexingType type = JSArray::fastConcatType(exec->vm(), *previousArray, *currentArray);
if (type != NonArray)
return previousArray->fastConcatWith(*exec, *currentArray);
}
- JSArray* arr = constructEmptyArray(exec, nullptr, finalArraySize.unsafeGet());
- if (exec->hadException())
- return JSValue::encode(jsUndefined());
+ ASSERT(speciesResult.first != SpeciesConstructResult::Exception);
+ JSObject* result;
+ if (speciesResult.first == SpeciesConstructResult::CreatedObject)
+ result = speciesResult.second;
+ else {
+ // We add the newTarget because the compiler gets confused between 0 being a number and a pointer.
+ result = constructEmptyArray(exec, nullptr, 0, JSValue());
+ }
+
curArg = thisValue.toObject(exec);
unsigned n = 0;
for (unsigned i = 0; ; ++i) {
@@ -575,19 +640,19 @@
if (exec->hadException())
return JSValue::encode(jsUndefined());
if (v)
- arr->putDirectIndex(exec, n, v);
+ result->putDirectIndex(exec, n, v);
n++;
}
} else {
- arr->putDirectIndex(exec, n, curArg);
+ result->putDirectIndex(exec, n, curArg);
n++;
}
if (i == argCount)
break;
curArg = exec->uncheckedArgument(i);
}
- arr->setLength(exec, n);
- return JSValue::encode(arr);
+ setLength(exec, result, n);
+ return JSValue::encode(result);
}
EncodedJSValue JSC_HOST_CALL arrayProtoFuncPop(ExecState* exec)
@@ -757,12 +822,21 @@
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
- if (isJSArray(thisObj)) {
+ std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
+ // We can only get an exception if we call some user function.
+ if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
+ return JSValue::encode(jsUndefined());
+
+ if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
- JSArray* result = constructEmptyArray(exec, nullptr, end - begin);
+ JSObject* result;
+ if (speciesResult.first == SpeciesConstructResult::CreatedObject)
+ result = speciesResult.second;
+ else
+ result = constructEmptyArray(exec, nullptr, end - begin);
unsigned n = 0;
for (unsigned k = begin; k < end; k++, n++) {
@@ -772,7 +846,7 @@
if (v)
result->putDirectIndex(exec, n, v);
}
- result->setLength(exec, n);
+ setLength(exec, result, n);
return JSValue::encode(result);
}
@@ -786,10 +860,22 @@
unsigned length = getLength(exec, thisObj);
if (exec->hadException())
return JSValue::encode(jsUndefined());
-
- if (!exec->argumentCount())
- return JSValue::encode(constructEmptyArray(exec, nullptr));
+ if (!exec->argumentCount()) {
+ std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, 0);
+ if (speciesResult.first == SpeciesConstructResult::Exception)
+ return JSValue::encode(jsUndefined());
+
+ JSObject* result;
+ if (speciesResult.first == SpeciesConstructResult::CreatedObject)
+ result = speciesResult.second;
+ else
+ result = constructEmptyArray(exec, nullptr);
+
+ setLength(exec, result, 0);
+ return JSValue::encode(result);
+ }
+
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned deleteCount = length - begin;
@@ -803,15 +889,22 @@
deleteCount = static_cast<unsigned>(deleteDouble);
}
- JSArray* result = nullptr;
+ std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, deleteCount);
+ if (speciesResult.first == SpeciesConstructResult::Exception)
+ return JSValue::encode(jsUndefined());
- if (isJSArray(thisObj))
+ JSObject* result = nullptr;
+ if (speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))
result = asArray(thisObj)->fastSlice(*exec, begin, deleteCount);
if (!result) {
- result = JSArray::tryCreateUninitialized(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), deleteCount);
- if (!result)
- return JSValue::encode(throwOutOfMemoryError(exec));
+ if (speciesResult.first == SpeciesConstructResult::CreatedObject)
+ result = speciesResult.second;
+ else {
+ result = JSArray::tryCreateUninitialized(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), deleteCount);
+ if (!result)
+ return JSValue::encode(throwOutOfMemoryError(exec));
+ }
for (unsigned k = 0; k < deleteCount; ++k) {
JSValue v = getProperty(exec, thisObj, k + begin);
@@ -837,7 +930,7 @@
return JSValue::encode(jsUndefined());
}
- putLength(exec, thisObj, jsNumber(length - deleteCount + additionalArgs));
+ setLength(exec, thisObj, length - deleteCount + additionalArgs);
return JSValue::encode(result);
}
@@ -943,4 +1036,48 @@
return JSValue::encode(JSArrayIterator::create(exec, exec->callee()->globalObject()->arrayIteratorStructure(), ArrayIterateKey, thisObj));
}
+// -------------------- ArrayPrototype.constructor Watchpoint ------------------
+
+class ArrayPrototypeAdaptiveInferredPropertyWatchpoint : public AdaptiveInferredPropertyValueWatchpointBase {
+public:
+ typedef AdaptiveInferredPropertyValueWatchpointBase Base;
+ ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition&, ArrayPrototype*);
+
+private:
+ virtual void handleFire(const FireDetail&) override;
+
+ ArrayPrototype* m_arrayPrototype;
+};
+
+void ArrayPrototype::setConstructor(VM& vm, JSObject* constructorProperty, unsigned attributes)
+{
+ putDirectWithoutTransition(vm, vm.propertyNames->constructor, constructorProperty, attributes);
+
+ PropertyOffset offset = this->structure()->get(vm, vm.propertyNames->constructor);
+ ASSERT(isValidOffset(offset));
+ this->structure()->startWatchingPropertyForReplacements(vm, offset);
+
+ ObjectPropertyCondition condition = ObjectPropertyCondition::equivalence(vm, this, this, vm.propertyNames->constructor.impl(), constructorProperty);
+ ASSERT(condition.isWatchable());
+
+ m_constructorWatchpoint = std::make_unique<ArrayPrototypeAdaptiveInferredPropertyWatchpoint>(condition, this);
+ m_constructorWatchpoint->install();
+}
+
+ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition& key, ArrayPrototype* prototype)
+ : Base(key)
+ , m_arrayPrototype(prototype)
+{
+}
+
+void ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire(const FireDetail& detail)
+{
+ StringPrintStream out;
+ out.print("ArrayPrototype adaption of ", key(), " failed: ", detail);
+
+ StringFireDetail stringDetail(out.toCString().data());
+
+ m_arrayPrototype->m_didChangeConstructorProperty = true;
+}
+
} // namespace JSC
Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.h (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.h 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.h 2016-01-30 02:45:25 UTC (rev 195878)
@@ -26,6 +26,8 @@
namespace JSC {
+class ArrayPrototypeAdaptiveInferredPropertyWatchpoint;
+
class ArrayPrototype : public JSArray {
private:
ArrayPrototype(VM&, Structure*);
@@ -42,8 +44,18 @@
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info(), ArrayClass);
}
+ void setConstructor(VM&, JSObject* constructorProperty, unsigned attributes);
+
+ bool didChangeConstructorProperty() const { return m_didChangeConstructorProperty; }
+
protected:
void finishCreation(VM&, JSGlobalObject*);
+
+private:
+ // This bit is set if any user modifies the constructor property Array.prototype. This is used to optimize species creation for JSArrays.
+ friend ArrayPrototypeAdaptiveInferredPropertyWatchpoint;
+ std::unique_ptr<ArrayPrototypeAdaptiveInferredPropertyWatchpoint> m_constructorWatchpoint;
+ bool m_didChangeConstructorProperty = false;
};
EncodedJSValue JSC_HOST_CALL arrayProtoFuncToString(ExecState*);
Modified: trunk/Source/_javascript_Core/runtime/ConstructData.cpp (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/ConstructData.cpp 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/ConstructData.cpp 2016-01-30 02:45:25 UTC (rev 195878)
@@ -35,6 +35,17 @@
namespace JSC {
+JSObject* construct(ExecState* exec, JSValue constructorObject, const ArgList& args, const String& errorMessage)
+{
+ ConstructData constructData;
+ ConstructType constructType = getConstructData(constructorObject, constructData);
+ if (constructType == ConstructTypeNone)
+ return throwTypeError(exec, errorMessage);
+
+ return construct(exec, constructorObject, constructType, constructData, args, constructorObject);
+}
+
+
JSObject* construct(ExecState* exec, JSValue constructorObject, ConstructType constructType, const ConstructData& constructData, const ArgList& args, JSValue newTarget)
{
ASSERT(constructType == ConstructTypeJS || constructType == ConstructTypeHost);
Modified: trunk/Source/_javascript_Core/runtime/ConstructData.h (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/ConstructData.h 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/ConstructData.h 2016-01-30 02:45:25 UTC (rev 195878)
@@ -56,6 +56,8 @@
} js;
};
+// Convenience wrapper so you don't need to deal with CallData and CallType unless you are going to use them.
+JSObject* construct(ExecState*, JSValue functionObject, const ArgList&, const String& errorMessage);
JS_EXPORT_PRIVATE JSObject* construct(ExecState*, JSValue constructor, ConstructType, const ConstructData&, const ArgList&, JSValue newTarget);
ALWAYS_INLINE JSObject* construct(ExecState* exec, JSValue constructorObject, ConstructType constructType, const ConstructData& constructData, const ArgList& args)
Modified: trunk/Source/_javascript_Core/runtime/JSCJSValue.h (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/JSCJSValue.h 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/JSCJSValue.h 2016-01-30 02:45:25 UTC (rev 195878)
@@ -219,6 +219,7 @@
// Querying the type.
bool isEmpty() const;
bool isFunction() const;
+ bool isConstructor() const;
bool isUndefined() const;
bool isNull() const;
bool isUndefinedOrNull() const;
Modified: trunk/Source/_javascript_Core/runtime/JSCJSValueInlines.h (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/JSCJSValueInlines.h 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/JSCJSValueInlines.h 2016-01-30 02:45:25 UTC (rev 195878)
@@ -682,6 +682,16 @@
return isCell() && (asCell()->inherits(JSFunction::info()) || asCell()->inherits(InternalFunction::info()));
}
+// FIXME: We could do this in a smarter way. See: https://bugs.webkit.org/show_bug.cgi?id=153670
+inline bool JSValue::isConstructor() const
+{
+ if (isFunction()) {
+ ConstructData data;
+ return getConstructData(*this, data) != ConstructTypeNone;
+ }
+ return false;
+}
+
// this method is here to be after the inline declaration of JSCell::inherits
inline bool JSValue::inherits(const ClassInfo* classInfo) const
{
Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (195877 => 195878)
--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2016-01-30 02:45:25 UTC (rev 195878)
@@ -404,7 +404,7 @@
m_definePropertyFunction.set(vm, this, definePropertyFunction);
JSCell* functionConstructor = FunctionConstructor::create(vm, FunctionConstructor::createStructure(vm, this, m_functionPrototype.get()), m_functionPrototype.get());
- JSCell* arrayConstructor = ArrayConstructor::create(vm, ArrayConstructor::createStructure(vm, this, m_functionPrototype.get()), m_arrayPrototype.get(), speciesGetterSetter);
+ JSObject* arrayConstructor = ArrayConstructor::create(vm, ArrayConstructor::createStructure(vm, this, m_functionPrototype.get()), m_arrayPrototype.get(), speciesGetterSetter);
m_regExpConstructor.set(vm, this, RegExpConstructor::create(vm, RegExpConstructor::createStructure(vm, this, m_functionPrototype.get()), m_regExpPrototype.get(), speciesGetterSetter));
@@ -439,7 +439,7 @@
m_objectPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, objectConstructor, DontEnum);
m_functionPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, functionConstructor, DontEnum);
- m_arrayPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, arrayConstructor, DontEnum);
+ m_arrayPrototype->setConstructor(vm, arrayConstructor, DontEnum);
m_regExpPrototype->putDirectWithoutTransition(vm, vm.propertyNames->constructor, m_regExpConstructor.get(), DontEnum);
putDirectWithoutTransition(vm, vm.propertyNames->Object, objectConstructor, DontEnum);
Modified: trunk/Source/_javascript_Core/tests/es6.yaml (195877 => 195878)
--- trunk/Source/_javascript_Core/tests/es6.yaml 2016-01-30 02:36:14 UTC (rev 195877)
+++ trunk/Source/_javascript_Core/tests/es6.yaml 2016-01-30 02:45:25 UTC (rev 195878)
@@ -717,15 +717,15 @@
- path: es6/Array_is_subclassable_Array.of.js
cmd: runES6 :normal
- path: es6/Array_is_subclassable_Array.prototype.concat.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Array_is_subclassable_Array.prototype.filter.js
cmd: runES6 :fail
- path: es6/Array_is_subclassable_Array.prototype.map.js
cmd: runES6 :fail
- path: es6/Array_is_subclassable_Array.prototype.slice.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Array_is_subclassable_Array.prototype.splice.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/Array_is_subclassable_correct_prototype_chain.js
cmd: runES6 :normal
- path: es6/Array_static_methods_Array.from_generator_instances.js
@@ -1195,15 +1195,15 @@
- path: es6/well-known_symbols_Symbol.search.js
cmd: runES6 :fail
- path: es6/well-known_symbols_Symbol.species_Array.prototype.concat.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/well-known_symbols_Symbol.species_Array.prototype.filter.js
cmd: runES6 :fail
- path: es6/well-known_symbols_Symbol.species_Array.prototype.map.js
cmd: runES6 :fail
- path: es6/well-known_symbols_Symbol.species_Array.prototype.slice.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/well-known_symbols_Symbol.species_Array.prototype.splice.js
- cmd: runES6 :fail
+ cmd: runES6 :normal
- path: es6/well-known_symbols_Symbol.species_existence.js
cmd: runES6 :normal
- path: es6/well-known_symbols_Symbol.species_RegExp.prototype[Symbol.split].js
Added: trunk/Source/_javascript_Core/tests/stress/array-species-functions.js (0 => 195878)
--- trunk/Source/_javascript_Core/tests/stress/array-species-functions.js (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/array-species-functions.js 2016-01-30 02:45:25 UTC (rev 195878)
@@ -0,0 +1,79 @@
+C = class extends Array { }
+N = class { }
+N[Symbol.species] = function() { throw "this should never be called"; }
+
+testFunctions = [
+ [Array.prototype.concat, []],
+ [Array.prototype.slice, [1,2]],
+ [Array.prototype.splice, []],
+ [Array.prototype.splice, [0,1]]
+];
+
+objProp = Object.defineProperty;
+
+function funcThrows(func, args) {
+ try {
+ func.call(...args)
+ return false;
+ } catch (e) {
+ return true;
+ }
+}
+
+function test(testData) {
+ "use strict";
+ let [protoFunction, args] = testData;
+ let foo = new C(10);
+ let n = new N();
+
+ // Test non-array ignores constructor.
+ objProp(n, "constructor", { value: C });
+ let bar = protoFunction.call(...[n, ...args]);
+ if (!(bar instanceof Array) || bar instanceof N || bar instanceof C)
+ throw Error();
+
+ objProp(foo, "constructor", { value: null });
+ if (!funcThrows(protoFunction, [foo, ...args]))
+ throw "didn't throw";
+
+ // Test array defaults cases.
+ foo = new C(10);
+
+ objProp(C, Symbol.species, { value: undefined, writable: true});
+ bar = protoFunction.call(...[foo, ...args]);
+ if (!(bar instanceof Array) || bar instanceof C)
+ throw Error();
+
+ C[Symbol.species] = null;
+ bar = protoFunction.call(...[foo, ...args]);
+ if (!(bar instanceof Array) || bar instanceof C)
+ throw Error();
+
+ // Test species is custom constructor.
+ let called = false;
+ function species(...args) {
+ called = true;
+ return new C(...args);
+ }
+
+ C[Symbol.species] = species;
+ bar = protoFunction.call(...[foo, ...args]);
+
+ if (!(bar instanceof Array) || !(bar instanceof C) || !called)
+ throw Error("failed on " + protoFunction);
+
+ function speciesThrows() {
+ throw Error();
+ }
+
+ C[Symbol.species] = speciesThrows;
+ if (!funcThrows(protoFunction, [foo, ...args]))
+ throw "didn't throw";
+
+ C[Symbol.species] = true;
+ if (!funcThrows(protoFunction, [foo, ...args]))
+ throw "didn't throw";
+
+}
+
+testFunctions.forEach(test);