/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved.
 *  Copyright (C) 2003 Peter Kelly (pmk@post.com)
 *  Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 *  USA
 *
 */

#include "config.h"
#include "ArrayPrototype.h"

#include "AdaptiveInferredPropertyValueWatchpointBase.h"
#include "ArrayConstructor.h"
#include "BuiltinNames.h"
#include "ButterflyInlineMethods.h"
#include "CachedCall.h"
#include "CodeBlock.h"
#include "CopiedSpaceInlineMethods.h"
#include "Error.h"
#include "GetterSetter.h"
#include "Interpreter.h"
#include "JIT.h"
#include "JSArrayInlines.h"
#include "JSArrayIterator.h"
#include "JSCBuiltins.h"
#include "JSStringBuilder.h"
#include "JSStringJoiner.h"
#include "Lookup.h"
#include "ObjectConstructor.h"
#include "ObjectPrototype.h"
#include "Operations.h"
#include "StringRecursionChecker.h"
#include <algorithm>
#include <wtf/Assertions.h>
#include <wtf/HashSet.h>
#include <wtf/FastMallocMonitor.h>
#include <wtf/MemoryMonitor.h>

namespace JSC {

static EncodedJSValue JSC_HOST_CALL arrayProtoFuncToLocaleString(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncPop(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncPush(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncShift(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncUnShift(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncIndexOf(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncLastIndexOf(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncKeys(ExecState*);
static EncodedJSValue JSC_HOST_CALL arrayProtoFuncEntries(ExecState*);

}

namespace JSC {

// ------------------------------ ArrayPrototype ----------------------------

const ClassInfo ArrayPrototype::s_info = {"Array", &JSArray::s_info, 0, CREATE_METHOD_TABLE(ArrayPrototype)};

ArrayPrototype* ArrayPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
{
	ArrayPrototype* prototype = new (NotNull, allocateCell<ArrayPrototype>(vm.heap)) ArrayPrototype(vm, structure);
    prototype->finishCreation(vm, globalObject);
    vm.heap.addFinalizer(prototype, destroy);
    return prototype;
}

// ECMA 15.4.4
ArrayPrototype::ArrayPrototype(VM& vm, Structure* structure)
    : JSArray(vm, structure, 0)
	, m_speciesWatchpointStatus(SpeciesWatchpointStatus::Uninitialized)
{
}

void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
{
    Base::finishCreation(vm);
    ASSERT(inherits(info()));
    vm.prototypeMap.addPrototype(this);

    putDirectWithoutTransition(vm, vm.propertyNames->toString, globalObject->arrayProtoToStringFunction(), DontEnum);
	putDirectWithoutTransition(vm, vm.propertyNames->values, globalObject->arrayProtoValuesFunction(), DontEnum);
    putDirectWithoutTransition(vm, vm.propertyNames->iteratorSymbol, globalObject->arrayProtoValuesFunction(), DontEnum);

    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toLocaleString, arrayProtoFuncToLocaleString, DontEnum, 0);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("concat", arrayProtoFuncConcat, DontEnum, 1);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("fill", arrayPrototypeFillCodeGenerator, DontEnum);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->join, arrayProtoFuncJoin, DontEnum, 1);
    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("pop", arrayProtoFuncPop, DontEnum, 0, ArrayPopIntrinsic);
    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().pushPublicName(), arrayProtoFuncPush, DontEnum, 1, ArrayPushIntrinsic);
    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().pushPrivateName(), arrayProtoFuncPush, DontEnum | DontDelete | ReadOnly, 1, ArrayPushIntrinsic);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("reverse", arrayProtoFuncReverse, DontEnum, 0);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPublicName(), arrayProtoFuncShift, DontEnum, 0);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPrivateName(), arrayProtoFuncShift, DontEnum | DontDelete | ReadOnly, 0);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->slice, arrayProtoFuncSlice, DontEnum, 2);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("sort", arrayPrototypeSortCodeGenerator, DontEnum);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("splice", arrayProtoFuncSplice, DontEnum, 2);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("unshift", arrayProtoFuncUnShift, DontEnum, 1);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("every", arrayPrototypeEveryCodeGenerator, DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("forEach", arrayPrototypeForEachCodeGenerator, DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("some", arrayPrototypeSomeCodeGenerator, DontEnum);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("indexOf", arrayProtoFuncIndexOf, DontEnum, 1);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", arrayProtoFuncLastIndexOf, DontEnum, 1);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("filter", arrayPrototypeFilterCodeGenerator, DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("reduce", arrayPrototypeReduceCodeGenerator, DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("reduceRight", arrayPrototypeReduceRightCodeGenerator, DontEnum);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("map", arrayPrototypeMapCodeGenerator, DontEnum);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->entries, arrayProtoFuncEntries, DontEnum, 0);
    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->keys, arrayProtoFuncKeys, DontEnum, 0);
    JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("find", arrayPrototypeFindCodeGenerator, DontEnum);
	JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("findIndex", arrayPrototypeFindIndexCodeGenerator, DontEnum);
	JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("includes", arrayPrototypeIncludesCodeGenerator, DontEnum);
	JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("copyWithin", arrayPrototypeCopyWithinCodeGenerator, DontEnum);

    {
        JSObject* unscopables = constructEmptyObject(globalObject->globalExec(), globalObject->nullPrototypeObjectStructure());
        const char* unscopableNames[] = {
            "copyWithin",
            "entries",
            "fill",
            "find",
            "findIndex",
	        "includes",
            "keys",
            "values"
        };
        for(int i = 0; i < (sizeof(unscopableNames) / sizeof(const char*)); ++i)
            unscopables->putDirect(vm, Identifier::fromString(vm, unscopableNames[i]), jsBoolean(true));
        putDirectWithoutTransition(vm, vm.propertyNames->unscopablesSymbol, unscopables, DontEnum | ReadOnly);
    }
}

void ArrayPrototype::destroy(JSC::JSCell* cell)
{
    ArrayPrototype* thisObject = static_cast<ArrayPrototype*>(cell);
    thisObject->ArrayPrototype::~ArrayPrototype();
}

// ------------------------------ Array Functions ----------------------------

static ALWAYS_INLINE JSValue getProperty(ExecState* exec, JSObject* object, unsigned index)
{
    VM& vm = exec->vm();
    if (JSValue result = object->tryGetIndexQuickly(index))
        return result;
    // We want to perform get and has in the same operation.
    // We can only do so when this behavior is not observable. The
    // only time it is observable is when we encounter an opaque objects (ProxyObject and JSModuleNamespaceObject)
	// somewhere in the prototype chain.
    PropertySlot slot(object, PropertySlot::InternalMethodType::HasProperty);
    bool hasProperty = object->getPropertySlot(exec, index, slot);
	ASSERT(!scope.exception() || !hasProperty);
	if (!hasProperty)
		return JSValue();
    if (UNLIKELY(slot.isTaintedByOpaqueObject())) {
        return object->get(exec, index);
    }
    return slot.getValue(exec, index);
}

static ALWAYS_INLINE bool putLength(ExecState* exec, VM& vm, JSObject* obj, JSValue value)
{
    PutPropertySlot slot(obj);
    return obj->methodTable(vm)->put(obj, exec, vm.propertyNames->length, value, slot);
}

static ALWAYS_INLINE void setLength(ExecState* exec, VM& vm, JSObject* obj, unsigned value)
{
    static const bool throwException = true;
    if (isJSArray(obj)) {
        jsCast<JSArray*>(obj)->setLength(exec, value, throwException);
        if(UNLIKELY(vm.exception()))
			return;
    }

    bool success = putLength(exec, vm, obj, jsNumber(value));
    if(UNLIKELY(vm.exception()))
		return;
    if (UNLIKELY(!success))
        throwTypeError(exec, String::fromLiteral(ReadonlyPropertyWriteError));
}

enum class SpeciesConstructResult {
    FastPath,
    Exception,
    CreatedObject
};

inline bool speciesWatchpointsValid(ExecState* exec, JSObject* thisObject)
{
    VM& vm = exec->vm();
    //auto scope = DECLARE_THROW_SCOPE(vm);

    ArrayPrototype* arrayPrototype = thisObject->globalObject()->arrayPrototype();
    ArrayPrototype::SpeciesWatchpointStatus status = arrayPrototype->speciesWatchpointStatus();
    if (UNLIKELY(status == ArrayPrototype::SpeciesWatchpointStatus::Uninitialized)) {
        status = arrayPrototype->attemptToInitializeSpeciesWatchpoint(exec);
        RETURN_IF_EXCEPTION(vm, false);
    }
    ASSERT(status != ArrayPrototype::SpeciesWatchpointStatus::Uninitialized);
    return !thisObject->hasCustomProperties()
        && arrayPrototype == thisObject->getPrototypeDirect()
        && status == ArrayPrototype::SpeciesWatchpointStatus::Initialized;
}

static ALWAYS_INLINE std::pair<SpeciesConstructResult, JSObject*> speciesConstructArray(ExecState* exec, JSObject* thisObject, unsigned length)
{
    VM& vm = exec->vm();

    // ECMA 9.4.2.3: https://tc39.github.io/ecma262/#sec-arrayspeciescreate
    JSValue constructor = jsUndefined();
    bool thisIsArray = isArray(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, std::make_pair(SpeciesConstructResult::Exception, static_cast<JSObject*>(0)));
    if (LIKELY(thisIsArray)) {
    	// 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.
		bool isValid = speciesWatchpointsValid(exec, thisObject);
        RETURN_IF_EXCEPTION(vm, std::make_pair(SpeciesConstructResult::Exception, static_cast<JSObject*>(0)));
		if (LIKELY(isValid))
            return std::make_pair(SpeciesConstructResult::FastPath, static_cast<JSObject*>(0));

        constructor = thisObject->get(exec, vm.propertyNames->constructor);
        RETURN_IF_EXCEPTION(vm, std::make_pair(SpeciesConstructResult::Exception, static_cast<JSObject*>(0)));
        if (constructor.isConstructor()) {
            JSObject* constructorObject = jsCast<JSObject*>(constructor);
            if (exec->lexicalGlobalObject() != constructorObject->globalObject())
                return std::make_pair(SpeciesConstructResult::FastPath, static_cast<JSObject*>(0));;
        }
        if (constructor.isObject()) {
            constructor = constructor.get(exec, vm.propertyNames->speciesSymbol);
            RETURN_IF_EXCEPTION(vm, std::make_pair(SpeciesConstructResult::Exception, static_cast<JSObject*>(0)));
            if (constructor.isNull())
                return std::make_pair(SpeciesConstructResult::FastPath, static_cast<JSObject*>(0));
        }
    } else {
        // If isArray is false, return ? ArrayCreate(length).
        return std::make_pair(SpeciesConstructResult::FastPath, static_cast<JSObject*>(0));
    }

    if (constructor.isUndefined())
        return std::make_pair(SpeciesConstructResult::FastPath, static_cast<JSObject*>(0));

    MarkedArgumentBuffer args;
    args.append(jsNumber(length));
    JSObject* newObject = construct(exec, constructor, args, "Species construction did not get a valid constructor");
    RETURN_IF_EXCEPTION(vm, std::make_pair(SpeciesConstructResult::Exception, static_cast<JSObject*>(0)));
    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);
    if (value.isUndefined())
        return undefinedValue;

    double indexDouble = value.toInteger(exec);
    if (indexDouble < 0) {
        indexDouble += length;
        return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
    }
    return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}

// The shift/unshift function implement the shift/unshift behaviour required
// by the corresponding array prototype methods, and by splice. In both cases,
// the methods are operating an an array or array like object.
//
//  header  currentCount  (remainder)
// [------][------------][-----------]
//  header  resultCount  (remainder)
// [------][-----------][-----------]
//
// The set of properties in the range 'header' must be unchanged. The set of
// properties in the range 'remainder' (where remainder = length - header -
// currentCount) will be shifted to the left or right as appropriate; in the
// case of shift this must be removing values, in the case of unshift this
// must be introducing new values.

template<JSArray::ShiftCountMode shiftCountMode>
void shift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned currentCount, unsigned resultCount, unsigned length)
{
    ASSERT(currentCount > resultCount);
    unsigned count = currentCount - resultCount;

    ASSERT(header <= length);
    ASSERT(currentCount <= (length - header));

    if (isJSArray(thisObj)) {
        JSArray* array = asArray(thisObj);
        if (array->length() == length && array->shiftCount<shiftCountMode>(exec, header, count))
            return;
    }

    VM& vm = exec->vm();
    for (unsigned k = header; k < length - currentCount; ++k) {
        unsigned from = k + currentCount;
        unsigned to = k + resultCount;
        JSValue value = getProperty(exec, thisObj, from);
		if (UNLIKELY(vm.exception()))
			return;
		if(value) {
            thisObj->putByIndexInline(exec, to, value, true);
            if (UNLIKELY(vm.exception()))
                return;
        } else {
        	bool success = thisObj->methodTable(vm)->deletePropertyByIndex(thisObj, exec, to);
            if (UNLIKELY(vm.exception()))
                return;
        	if (!success) {
        		throwTypeError(exec, "Unable to delete property.");
        		return;
        	}
        }
    }
    for (unsigned k = length; k > length - count; --k) {
    	bool success = thisObj->methodTable(vm)->deletePropertyByIndex(thisObj, exec, k - 1);
    	if (UNLIKELY(vm.exception()))
			return;
        if (UNLIKELY(!success)) {
            throwTypeError(exec, "Unable to delete property.");
            return;
        }
    }
}

template<JSArray::ShiftCountMode shiftCountMode>
void unshift(ExecState* exec, JSObject* thisObj, unsigned header, unsigned currentCount, unsigned resultCount, unsigned length)
{
    ASSERT(resultCount > currentCount);
    unsigned count = resultCount - currentCount;

    ASSERT(header <= length);
    ASSERT(currentCount <= (length - header));

    // Guard against overflow.
    if (count > UINT_MAX - length) {
        throwOutOfMemoryError(exec);
        return;
    }

    if (isJSArray(thisObj)) {
        JSArray* array = asArray(thisObj);
        if (array->length() == length) {
        	bool handled = array->unshiftCount<shiftCountMode>(exec, header, count);
        	ASSERT(!scope.exception() || handled);
			if (handled)
				return;
		}
    }
    VM& vm = exec->vm();
    for (unsigned k = length - currentCount; k > header; --k) {
        unsigned from = k + currentCount - 1;
        unsigned to = k + resultCount - 1;
        JSValue value = getProperty(exec, thisObj, from);
		if (UNLIKELY(vm.exception()))
			return;
		if(value) {
            thisObj->putByIndexInline(exec, to, value, true);
        } else {
        	bool success = thisObj->methodTable(vm)->deletePropertyByIndex(thisObj, exec, to);
    		if (UNLIKELY(vm.exception()))
    			return;
            if (UNLIKELY(!success)) {
				throwTypeError(exec, "Unable to delete property.");
				return;
            }
        }
    }
}

static bool canUseFastJoin(const JSObject*);
static JSValue fastJoin(ExecState&, JSObject*, const StringView&, unsigned);

EncodedJSValue JSC_HOST_CALL arrayProtoFuncToString(ExecState* exec)
{
    JSValue thisValue = exec->hostThisValue().toThis(exec, StrictMode);
    VM& vm = exec->vm();

    // 1. Let array be the result of calling ToObject on the this value.
    JSObject* thisObject = thisValue.toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    
    // 2. Let func be the result of calling the [[Get]] internal method of array with argument "join".
    JSValue function = JSValue(thisObject).get(exec, vm.propertyNames->join);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    // 3. If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2).
    bool customJoinCase = false;
    if (!function.isCell())
    	customJoinCase = true;
    CallData callData;
    CallType callType = getCallData(function, callData);
    if (callType == CallType::None)
        customJoinCase = true;

    if (UNLIKELY(customJoinCase))
        return JSValue::encode(jsMakeNontrivialString(vm, exec, "[object ", thisObject->methodTable()->className(thisObject), "]"));

    // 4. Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list.
    if (!isJSArray(thisObject) || callType != CallType::Host || callData.native.function != arrayProtoFuncJoin) {
        return JSValue::encode(call(exec, function, callType, callData, thisObject, *vm.emptyList));
    }

    ASSERT(isJSArray(thisValue));
    JSArray* thisArray = asArray(thisValue);
    
    unsigned length = thisArray->length();

    StringRecursionChecker checker(exec, thisArray);
    ASSERT(!scope.exception() || checker.earlyReturnValue());
    if (JSValue earlyReturnValue = checker.earlyReturnValue())
        return JSValue::encode(earlyReturnValue);

    if (LIKELY(canUseFastJoin(thisArray))) {
        const LChar comma = ',';
        return JSValue::encode(fastJoin(*exec, thisArray, StringView(&comma, 1), length));
    }

    JSStringJoiner joiner(*exec, ',', length);
    if (UNLIKELY(vm.exception()))
        return JSValue::encode(jsUndefined());

    for (unsigned i = 0; i < length; ++i) {
        JSValue element = thisArray->tryGetIndexQuickly(i);
        if (!element) {
            element = thisArray->get(exec, i);
            if (UNLIKELY(vm.exception()))
                return JSValue::encode(jsUndefined());
        }
        joiner.append(*exec, element);
        if (UNLIKELY(vm.exception()))
            return JSValue::encode(jsUndefined());
    }

    return JSValue::encode(joiner.join(*exec));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncToLocaleString(ExecState* exec)
{
    JSValue thisValue = exec->hostThisValue().toThis(exec, StrictMode);
    VM& vm = exec->vm();
    JSObject* thisObject = thisValue.toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    unsigned length = toLength(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    StringRecursionChecker checker(exec, thisObject);
    ASSERT(!scope.exception() || checker.earlyReturnValue());
    if (JSValue earlyReturnValue = checker.earlyReturnValue())
        return JSValue::encode(earlyReturnValue);

    JSStringJoiner stringJoiner(*exec, ',', length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

#if ENABLE(INTL)
    ArgList arguments(exec);
#endif
    for (unsigned i = 0; i < length; ++i) {
        JSValue element = thisObject->getIndex(exec, i);
        if (UNLIKELY(vm.exception()))
            return JSValue::encode(jsUndefined());
        if (element.isUndefinedOrNull())
            element = jsEmptyString(vm);
        else {
			JSValue conversionFunction = element.get(exec, vm.propertyNames->toLocaleString);
			RETURN_IF_EXCEPTION(vm, encodedJSValue());
			CallData callData;
			CallType callType = getCallData(conversionFunction, callData);
			if (callType != CallType::None) {
#if ENABLE(INTL)
				element = call(exec, conversionFunction, callType, callData, element, arguments);
#else
				element = call(exec, conversionFunction, callType, callData, element, *vm.emptyList);
#endif
				RETURN_IF_EXCEPTION(vm, encodedJSValue());
			}
        }
        stringJoiner.append(*exec, element);
		RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }

    return JSValue::encode(stringJoiner.join(*exec));
}

static inline bool isHole(double value)
{
    return std::isnan(value);
}

static inline bool isHole(const WriteBarrier<Unknown>& value)
{
    return !value;
}

template<typename T> static inline bool containsHole(T* data, unsigned length)
{
    for (unsigned i = 0; i < length; ++i) {
        if (isHole(data[i]))
            return true;
    }
    return false;
}

static inline bool holesMustForwardToPrototype(VM& vm, JSObject* object)
{
    return object->structure(vm)->holesMustForwardToPrototype(vm);
}

static JSValue slowJoin(ExecState& exec, JSObject* thisObject, JSString* separator, uint64_t length)
{
    VM& vm = exec.vm();

    // 5. If len is zero, return the empty String.
    if (!length)
        return jsEmptyString(vm);

    // 6. Let element0 be Get(O, "0").
    JSValue element0 = thisObject->getIndex(&exec, 0);
    RETURN_IF_EXCEPTION(vm, JSValue());

    // 7. If element0 is undefined or null, let R be the empty String; otherwise, let R be ? ToString(element0).
    JSString* r = 0;
    if (element0.isUndefinedOrNull())
        r = jsEmptyString(vm);
    else
        r = element0.toString(&exec);
    RETURN_IF_EXCEPTION(vm, JSValue());

    if(UNLIKELY(WTF::gLowMemory)) {
        throwOutOfMemoryError(&exec);
        return JSValue();
    }

    // 8. Let k be 1.
    // 9. Repeat, while k < len
    // 9.e Increase k by 1..
    MonitorMallocFailedPreventer preventer;
    for (uint64_t k = 1; k < length; ++k) {
        // b. Let element be ? Get(O, ! ToString(k)).
        JSValue element = thisObject->get(&exec, Identifier::fromString(vm, AtomicString::number(k)));
        RETURN_IF_EXCEPTION(vm, JSValue());

        // c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).
        JSString* next = 0;
        if (element.isUndefinedOrNull()) {
            if (!separator->length())
                continue;
            next = jsEmptyString(vm);
        } else
            next = element.toString(&exec);
        RETURN_IF_EXCEPTION(vm, JSValue());

        // a. Let S be the String value produced by concatenating R and sep.
        // d. Let R be a String value produced by concatenating S and next.
        r = jsString(vm, &exec, r, separator, next);
        RETURN_IF_EXCEPTION(vm, JSValue());

		if(preventer.lowMemory()){
            throwOutOfMemoryError(vm, &exec);
            return JSValue();
        }
    }
    // 10. Return R.
    return r;
}

static inline bool canUseFastJoin(const JSObject* thisObject)
{
    switch (thisObject->structure()->indexingType()) {
    case ALL_CONTIGUOUS_INDEXING_TYPES:
    case ALL_INT32_INDEXING_TYPES:
    case ALL_DOUBLE_INDEXING_TYPES:
    case ALL_UNDECIDED_INDEXING_TYPES:
        return true;
    default:
        break;
    }
    return false;
}

static inline JSValue fastJoin(ExecState& state, JSObject* thisObject, const StringView& separator, unsigned length)
{
    VM& vm = state.vm();
    switch (thisObject->structure()->indexingType()) {
    case ALL_INT32_INDEXING_TYPES: {
        auto& butterfly = *thisObject->butterfly();
        if (UNLIKELY(length > butterfly.publicLength()))
            break;
        JSStringJoiner joiner(state, separator, length);
        RETURN_IF_EXCEPTION(vm, JSValue());
        auto data = butterfly.contiguous().data();
        bool holesKnownToBeOK = false;
        for (unsigned i = 0; i < length; ++i) {
            JSValue value = data[i].get();
            if (LIKELY(value))
                joiner.appendNumber(vm, value.asInt32());
            else {
                if (!holesKnownToBeOK) {
                    if (holesMustForwardToPrototype(vm, thisObject))
                        goto generalCase;
                    holesKnownToBeOK = true;
                }
                joiner.appendEmptyString();
            }
        }
        return joiner.join(state);
    }
    case ALL_CONTIGUOUS_INDEXING_TYPES: {
        auto& butterfly = *thisObject->butterfly();
        if (UNLIKELY(length > butterfly.publicLength()))
            break;
        JSStringJoiner joiner(state, separator, length);
        RETURN_IF_EXCEPTION(vm, JSValue());
        auto data = butterfly.contiguous().data();
        bool holesKnownToBeOK = false;
        for (unsigned i = 0; i < length; ++i) {
            if (JSValue value = data[i].get()) {
                if (!joiner.appendWithoutSideEffects(state, value))
                    goto generalCase;
            } else {
                if (!holesKnownToBeOK) {
                    if (holesMustForwardToPrototype(vm, thisObject))
                        goto generalCase;
                    holesKnownToBeOK = true;
                }
                joiner.appendEmptyString();
            }
        }
        return joiner.join(state);
    }
    case ALL_DOUBLE_INDEXING_TYPES: {
        auto& butterfly = *thisObject->butterfly();
        if (UNLIKELY(length > butterfly.publicLength()))
            break;
        JSStringJoiner joiner(state, separator, length);
        RETURN_IF_EXCEPTION(vm, JSValue());
        auto data = butterfly.contiguousDouble().data();
        bool holesKnownToBeOK = false;
        for (unsigned i = 0; i < length; ++i) {
            double value = data[i];
            if (LIKELY(!isHole(value)))
                joiner.appendNumber(vm, value);
            else {
                if (!holesKnownToBeOK) {
                    if (thisObject->structure(vm)->holesMustForwardToPrototype(vm))
                        goto generalCase;
                    holesKnownToBeOK = true;
                }
                joiner.appendEmptyString();
            }
        }
        return joiner.join(state);
    }
    case ALL_UNDECIDED_INDEXING_TYPES: {
        if (length && holesMustForwardToPrototype(vm, thisObject))
            goto generalCase;
        switch (separator.length()) {
        case 0:
            return jsEmptyString(vm);
        case 1: {
            if (length <= 1)
                return jsEmptyString(vm);
            if (separator.is8Bit())
                return repeatCharacter(vm, &state, separator.characters8()[0], length - 1);
            return repeatCharacter(vm, &state, separator.characters16()[0], length - 1);
        }
        }
    }
    }

generalCase:
    JSStringJoiner joiner(state, separator, length);
    RETURN_IF_EXCEPTION(vm, JSValue());
    for (unsigned i = 0; i < length; ++i) {
        JSValue element = thisObject->getIndex(&state, i);
        RETURN_IF_EXCEPTION(vm, JSValue());
        joiner.append(state, element);
        RETURN_IF_EXCEPTION(vm, JSValue());
    }
    return joiner.join(state);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
{
    VM& vm = exec->vm();

    // 1. Let O be ? ToObject(this value).
    JSObject* thisObject = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObject);
	if (UNLIKELY(!thisObject))
		return encodedJSValue();

    StringRecursionChecker checker(exec, thisObject);
    ASSERT(!scope.exception() || checker.earlyReturnValue());
    if (JSValue earlyReturnValue = checker.earlyReturnValue())
        return JSValue::encode(earlyReturnValue);

    // 2. Let len be ? ToLength(? Get(O, "length")).
    double length = toLength(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    // 3. If separator is undefined, let separator be the single-element String ",".
    JSValue separatorValue = exec->argument(0);
    if (separatorValue.isUndefined()) {
        const LChar comma = ',';

        if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) {
            uint64_t length64 = static_cast<uint64_t>(length);
            ASSERT(static_cast<double>(length64) == length);
            JSString* jsSeparator = jsSingleCharacterString(vm, comma);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());

            return JSValue::encode(slowJoin(*exec, thisObject, jsSeparator, length64));
        }

        unsigned unsignedLength = static_cast<unsigned>(length);
        ASSERT(static_cast<double>(unsignedLength) == length);
        return JSValue::encode(fastJoin(*exec, thisObject, StringView(&comma, 1), unsignedLength));
    }

    // 4. Let sep be ? ToString(separator).
    JSString* jsSeparator = separatorValue.toString(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    if (UNLIKELY(length > std::numeric_limits<unsigned>::max() || !canUseFastJoin(thisObject))) {
        uint64_t length64 = static_cast<uint64_t>(length);
        ASSERT(static_cast<double>(length64) == length);
        return JSValue::encode(slowJoin(*exec, thisObject, jsSeparator, length64));
    }

    StringViewWithUnderlyingString viewWithString = jsSeparator->viewWithUnderlyingString(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    return JSValue::encode(fastJoin(*exec, thisObject, viewWithString.view, length));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
{
    VM& vm = exec->vm();

    JSValue thisValue = exec->thisValue().toThis(exec, StrictMode);
    unsigned argCount = exec->argumentCount();
    JSValue curArg = thisValue.toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    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 = 0;
    JSArray* previousArray = 0;
    for (unsigned i = 0; ; ++i) {
        previousArray = currentArray;
        currentArray = jsDynamicCast<JSArray*>(curArg);
        if (currentArray) {
            // Can't use JSArray::length here because this might be a RuntimeArray!
            finalArraySize += static_cast<unsigned>(toLength(exec, currentArray));
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        } else
            ++finalArraySize;
        if (i == argCount)
            break;
        curArg = exec->uncheckedArgument(i);
    }

    if (finalArraySize.hasOverflowed())
        return JSValue::encode(throwOutOfMemoryError(exec));

    if (speciesResult.first == SpeciesConstructResult::FastPath && argCount == 1 && previousArray && currentArray && finalArraySize.unsafeGet() < MIN_SPARSE_ARRAY_INDEX) {
        IndexingType type = JSArray::fastConcatType(vm, *previousArray, *currentArray);
        if (type != NonArray)
            return previousArray->fastConcatWith(*exec, *currentArray);
    }

    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, 0, 0, JSValue());
    }
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    curArg = thisValue.toObject(exec);
    ASSERT(!vm.exception());
    unsigned n = 0;
    for (unsigned i = 0; ; ++i) {
        if (JSArray* currentArray = jsDynamicCast<JSArray*>(curArg)) {
            // Can't use JSArray::length here because this might be a RuntimeArray!
            unsigned length = toLength(exec, currentArray);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
            for (unsigned k = 0; k < length; ++k) {
                JSValue v = getProperty(exec, currentArray, k);
                RETURN_IF_EXCEPTION(vm, encodedJSValue());
                if (v)
                	result->putDirectIndex(exec, n, v);
                n++;
            }
        } else {
        	result->putDirectIndex(exec, n, curArg);
            n++;
        }
        if (i == argCount)
            break;
        curArg = exec->uncheckedArgument(i);
    }
    setLength(exec, vm, result, n);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncPop(ExecState* exec)
{
    VM& vm = exec->vm();
    JSValue thisValue = exec->hostThisValue().toThis(exec, StrictMode);

    if (isJSArray(thisValue))
        return JSValue::encode(asArray(thisValue)->pop(exec));

    JSObject* thisObj = thisValue.toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
    if (UNLIKELY(!thisObj))
        return encodedJSValue();
    unsigned length = toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    JSValue result;
    if (length == 0) {
        putLength(exec, vm, thisObj, jsNumber(length));
        return JSValue::encode(jsUndefined());
    }

	result = thisObj->get(exec, length - 1);
	RETURN_IF_EXCEPTION(vm, encodedJSValue());
	bool success = thisObj->methodTable()->deletePropertyByIndex(thisObj, exec, length - 1);
	RETURN_IF_EXCEPTION(vm, encodedJSValue());
	if (UNLIKELY(!success)) {
		throwTypeError(exec, "Unable to delete property.");
		return encodedJSValue();
	}
	putLength(exec, vm, thisObj, jsNumber(length - 1));
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncPush(ExecState* exec)
{
    VM& vm = exec->vm();
    JSValue thisValue = exec->hostThisValue().toThis(exec, StrictMode);

    if (isJSArray(thisValue) && exec->argumentCount() == 1) {
        JSArray* array = asArray(thisValue);
        array->push(exec, exec->uncheckedArgument(0));
        return JSValue::encode(jsNumber(array->length()));
    }

    JSObject* thisObj = thisValue.toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
    if (UNLIKELY(!thisObj))
        return encodedJSValue();
    unsigned length = toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    for (unsigned n = 0; n < exec->argumentCount(); n++) {
        // Check for integer overflow; where safe we can do a fast put by index.
        if (length + n >= length)
            thisObj->methodTable(vm)->putByIndex(thisObj, exec, length + n, exec->uncheckedArgument(n), true);
        else {
            PutPropertySlot slot(thisObj);
            Identifier propertyName = Identifier::fromString(vm, JSValue(static_cast<int64_t>(length) + static_cast<int64_t>(n)).toWTFString(exec));
            thisObj->methodTable(vm)->put(thisObj, exec, propertyName, exec->uncheckedArgument(n), slot);
        }
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }
    JSValue newLength(static_cast<int64_t>(length) + static_cast<int64_t>(exec->argumentCount()));
    putLength(exec, vm, thisObj, newLength);
    return JSValue::encode(newLength);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
{
	VM& vm = exec->vm();

    JSObject* thisObject = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObject);
	if (UNLIKELY(!thisObject))
		return encodedJSValue();

    unsigned length = toLength(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    switch (thisObject->structure()->indexingType()) {
    case ALL_CONTIGUOUS_INDEXING_TYPES:
    case ALL_INT32_INDEXING_TYPES: {
        auto& butterfly = *thisObject->butterfly();
        if (length > butterfly.publicLength())
            break;
        auto data = butterfly.contiguous().data();
        if (containsHole(data, length) && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
            break;
        std::reverse(data, data + length);
        return JSValue::encode(thisObject);
    }
    case ALL_DOUBLE_INDEXING_TYPES: {
        auto& butterfly = *thisObject->butterfly();
        if (length > butterfly.publicLength())
            break;
        auto data = butterfly.contiguousDouble().data();
        if (containsHole(data, length) && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
            break;
        std::reverse(data, data + length);
        return JSValue::encode(thisObject);
    }
    case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
        auto& storage = *thisObject->butterfly()->arrayStorage();
        if (length > storage.vectorLength())
            break;
        if (storage.hasHoles() && thisObject->structure(vm)->holesMustForwardToPrototype(vm))
            break;
        auto data = storage.vector().data();
        std::reverse(data, data + length);
        return JSValue::encode(thisObject);
    }
    }

    unsigned middle = length / 2;
    for (unsigned lower = 0; lower < middle; lower++) {
        unsigned upper = length - lower - 1;
        bool lowerExists = thisObject->hasProperty(exec, lower);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        JSValue lowerValue;
        if (lowerExists) {
            lowerValue = thisObject->get(exec, lower);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        }

        bool upperExists = thisObject->hasProperty(exec, upper);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        JSValue upperValue;
        if (upperExists) {
            upperValue = thisObject->get(exec, upper);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        }

        if (upperExists) {
            thisObject->putByIndexInline(exec, lower, upperValue, true);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        } else {
        	bool success = thisObject->methodTable()->deletePropertyByIndex(thisObject, exec, lower);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
            if (UNLIKELY(!success)) {
                throwTypeError(exec, ASCIILiteral("Unable to delete property."));
                return encodedJSValue();
            }
        }

        if (lowerExists) {
            thisObject->putByIndexInline(exec, upper, lowerValue, true);
            if (UNLIKELY(vm.exception()))
                return JSValue::encode(JSValue());
        } else {
        	bool success = thisObject->methodTable()->deletePropertyByIndex(thisObject, exec, upper);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
            if (UNLIKELY(!success)) {
                throwTypeError(exec, ASCIILiteral("Unable to delete property."));
                return encodedJSValue();
            }
        }
    }
    return JSValue::encode(thisObject);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncShift(ExecState* exec)
{
	VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObj))
		return encodedJSValue();
    unsigned length = toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    if (length == 0) {
    	putLength(exec, vm, thisObj, jsNumber(length));
        return JSValue::encode(jsUndefined());
    }

    JSValue result = thisObj->getIndex(exec, 0);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
	shift<JSArray::ShiftCountForShift>(exec, thisObj, 0, 1, 0, length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
	putLength(exec, vm, thisObj, jsNumber(length - 1));
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
    // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
	VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObj))
		return encodedJSValue();
	unsigned length = toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    if (end < begin)
        end = begin;

    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
    // We can only get an exception if we call some user function.
    ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception));
    if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
    	 return encodedJSValue();

    bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
	if (LIKELY(okToDoFastPath)) {
		if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
            return JSValue::encode(result);
    }

    JSObject* result;
    if (speciesResult.first == SpeciesConstructResult::CreatedObject)
        result = speciesResult.second;
    else {
        result = constructEmptyArray(exec, 0, end - begin);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }

    unsigned n = 0;
    for (unsigned k = begin; k < end; k++, n++) {
        JSValue v = getProperty(exec, thisObj, k);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (v) {
        	result->putDirectIndex(exec, n, v, 0, PutDirectIndexShouldThrow);
        	RETURN_IF_EXCEPTION(vm, encodedJSValue());
        }
    }
    setLength(exec, vm, result, n);
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncSplice(ExecState* exec)
{
    // 15.4.4.12

    VM& vm = exec->vm();

    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObj))
		return encodedJSValue();
    unsigned length = toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    if (!exec->argumentCount()) {
        std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, 0);
        ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception));
		if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
			return encodedJSValue();

        JSObject* result;
        if (speciesResult.first == SpeciesConstructResult::CreatedObject)
            result = speciesResult.second;
        else {
            result = constructEmptyArray(exec, 0);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        }

        setLength(exec, vm, result, 0);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        setLength(exec, vm, thisObj, length);
        return JSValue::encode(result);
    }

    unsigned actualStart = argumentClampedIndexFromStartOrEnd(exec, 0, length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    unsigned actualDeleteCount = length - actualStart;
    if (exec->argumentCount() > 1) {
        double deleteCount = exec->uncheckedArgument(1).toInteger(exec);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (deleteCount < 0)
            actualDeleteCount = 0;
        else if (deleteCount > length - actualStart)
            actualDeleteCount = length - actualStart;
        else
            actualDeleteCount = static_cast<unsigned>(deleteCount);
    }

    std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, actualDeleteCount);
    ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception));
    if (speciesResult.first == SpeciesConstructResult::Exception)
        return JSValue::encode(jsUndefined());

    JSObject* result = 0;
    bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == toLength(exec, thisObj);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
	if (LIKELY(okToDoFastPath))
        result = asArray(thisObj)->fastSlice(*exec, actualStart, actualDeleteCount);

    if (!result) {
        DeferGC deferGC(vm.heap);

        if (speciesResult.first == SpeciesConstructResult::CreatedObject)
            result = speciesResult.second;
        else {
			result = JSArray::tryCreate(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithUndecided), actualDeleteCount);
			if (UNLIKELY(!result)) {
				throwOutOfMemoryError(vm, exec);
				return encodedJSValue();
			}
        }

        for (unsigned k = 0; k < actualDeleteCount; ++k) {
            JSValue v = getProperty(exec, thisObj, k + actualStart);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
            if (UNLIKELY(!v))
                continue;
            result->putDirectIndex(exec, k, v, 0, PutDirectIndexShouldThrow);
            RETURN_IF_EXCEPTION(vm, encodedJSValue());
        }
    }

    unsigned itemCount = std::max<int>(exec->argumentCount() - 2, 0);
    if (itemCount < actualDeleteCount) {
        shift<JSArray::ShiftCountForSplice>(exec, thisObj, actualStart, actualDeleteCount, itemCount, length);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    } else if (itemCount > actualDeleteCount) {
        unshift<JSArray::ShiftCountForSplice>(exec, thisObj, actualStart, actualDeleteCount, itemCount, length);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }
    for (unsigned k = 0; k < itemCount; ++k) {
        thisObj->putByIndexInline(exec, k + actualStart, exec->uncheckedArgument(k + 2), true);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }

    setLength(exec, vm, thisObj, length - actualDeleteCount + itemCount);
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncUnShift(ExecState* exec)
{
    // 15.4.4.13
	VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObj))
		return encodedJSValue();
    double doubleLength = toLength(exec, thisObj);
    unsigned length = doubleLength;
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    unsigned nrArgs = exec->argumentCount();
    if (nrArgs) {
    	if (UNLIKELY(doubleLength + static_cast<double>(nrArgs) > maxSafeInteger()))
    		return throwVMTypeError(exec, ASCIILiteral("Cannot shift to offset greater than (2 ** 53) - 1"));
        unshift<JSArray::ShiftCountForShift>(exec, thisObj, 0, 0, nrArgs, length);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }
    for (unsigned k = 0; k < nrArgs; ++k) {
        thisObj->putByIndexInline(exec, k, exec->uncheckedArgument(k), true);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
    }
    JSValue result = jsNumber(length + nrArgs);
    putLength(exec, vm, thisObj, result);
    return JSValue::encode(result);
}

enum class IndexOfDirection { Forward, Backward };
template<IndexOfDirection direction>
ALWAYS_INLINE JSValue fastIndexOf(ExecState* exec, VM& vm, JSArray* array, unsigned length, JSValue searchElement, unsigned index)
{
	bool canDoFastPath = array->canDoFastIndexedAccess(vm)
		&& array->getArrayLength() == length; // The effects in getting `index` could have changed the length of this array.
	if (!canDoFastPath)
        return JSValue();

	switch (array->structure()->indexingType()) {
	case ALL_INT32_INDEXING_TYPES: {
		if (!searchElement.isNumber())
			return jsNumber(-1);
		JSValue searchInt32;
		if (searchElement.isInt32())
			searchInt32 = searchElement;
		else {
			double searchNumber = searchElement.asNumber();
			if (!canBeInt32(searchNumber))
				return jsNumber(-1);
			searchInt32 = jsNumber(static_cast<int32_t>(searchNumber));
		}
		auto& butterfly = *array->butterfly();
		auto data = butterfly.contiguous().data();
        if (direction == IndexOfDirection::Forward) {
			for (; index < length; ++index) {
				// Array#indexOf uses `===` semantics (not HashMap isEqual semantics).
				// And the hole never matches against Int32 value.
				if (searchInt32 == data[index].get())
					return jsNumber(index);
			}
		} else {
			do {
				ASSERT(index < length);
				// Array#lastIndexOf uses `===` semantics (not HashMap isEqual semantics).
				// And the hole never matches against Int32 value.
				if (searchInt32 == data[index].get())
					return jsNumber(index);
			} while (index--);
		}
		return jsNumber(-1);
	}
	case ALL_CONTIGUOUS_INDEXING_TYPES: {
		auto& butterfly = *array->butterfly();
		auto data = butterfly.contiguous().data();

        if (direction == IndexOfDirection::Forward) {
			for (; index < length; ++index) {
				JSValue value = data[index].get();
				if (!value)
					continue;
				bool isEqual = JSValue::strictEqual(exec, searchElement, value);
				RETURN_IF_EXCEPTION(vm, JSValue());
				if (isEqual)
                    return jsNumber(index);
            }
        } else {
            do {
                ASSERT(index < length);
                JSValue value = data[index].get();
                if (!value)
                    continue;
                bool isEqual = JSValue::strictEqual(exec, searchElement, value);
                RETURN_IF_EXCEPTION(vm, JSValue());
                if (isEqual)
                    return jsNumber(index);
            } while (index--);
        }
        return jsNumber(-1);
	}
	case ALL_DOUBLE_INDEXING_TYPES: {
		if (!searchElement.isNumber())
			return jsNumber(-1);
		double searchNumber = searchElement.asNumber();
		auto& butterfly = *array->butterfly();
		auto data = butterfly.contiguousDouble().data();
        if (direction == IndexOfDirection::Forward) {
			for (; index < length; ++index) {
				// Array#indexOf uses `===` semantics (not HashMap isEqual semantics).
				// And the hole never matches since it is NaN.
				if (data[index] == searchNumber)
                    return jsNumber(index);
            }
        } else {
            do {
                ASSERT(index < length);
                // Array#lastIndexOf uses `===` semantics (not HashMap isEqual semantics).
                // And the hole never matches since it is NaN.
                if (data[index] == searchNumber)
                    return jsNumber(index);
            } while (index--);
        }
        return jsNumber(-1);
    }
    default:
        return JSValue();
    }
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncIndexOf(ExecState* exec)
{
	VM& vm = exec->vm();

	// 15.4.4.14
    JSObject* thisObject = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObject))
		return encodedJSValue();
    unsigned length = toLength(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());

    unsigned index = argumentClampedIndexFromStartOrEnd(exec, 1, length);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    JSValue searchElement = exec->argument(0);

	if (isJSArray(thisObject)) {
		JSValue result = fastIndexOf<IndexOfDirection::Forward>(exec, vm, asArray(thisObject), length, searchElement, index);
	    RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (result)
			return JSValue::encode(result);
	}

    for (; index < length; ++index) {
        JSValue e = getProperty(exec, thisObject, index);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (!e)
            continue;
        bool isEqual = JSValue::strictEqual(exec, searchElement, e);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
		if (isEqual)
            return JSValue::encode(jsNumber(index));
    }

    return JSValue::encode(jsNumber(-1));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncLastIndexOf(ExecState* exec)
{
    VM& vm = exec->vm();
    // 15.4.4.15
    JSObject* thisObject = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    ASSERT(!!scope.exception() == !thisObj);
	if (UNLIKELY(!thisObject))
		return encodedJSValue();
    unsigned length = toLength(exec, thisObject);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    if (!length)
        return JSValue::encode(jsNumber(-1));

    unsigned index = length - 1;
    if (exec->argumentCount() >= 2) {
        JSValue fromValue = exec->uncheckedArgument(1);
        double fromDouble = fromValue.toInteger(exec);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (fromDouble < 0) {
            fromDouble += length;
            if (fromDouble < 0)
                return JSValue::encode(jsNumber(-1));
        }
        if (fromDouble < length)
            index = static_cast<unsigned>(fromDouble);
    }

    JSValue searchElement = exec->argument(0);

    if (isJSArray(thisObject)) {
        JSValue result = fastIndexOf<IndexOfDirection::Backward>(exec, vm, asArray(thisObject), length, searchElement, index);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (result)
            return JSValue::encode(result);
    }

    do {
        ASSERT(index < length);
        JSValue e = getProperty(exec, thisObject, index);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (!e)
            continue;
        bool isEqual = JSValue::strictEqual(exec, searchElement, e);
        RETURN_IF_EXCEPTION(vm, encodedJSValue());
        if (isEqual)
            return JSValue::encode(jsNumber(index));
    } while (index--);

    return JSValue::encode(jsNumber(-1));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncValues(ExecState* exec)
{
    VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    return JSValue::encode(JSArrayIterator::create(exec, exec->callee()->globalObject()->arrayIteratorStructure(), IterateValue, thisObj));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncEntries(ExecState* exec)
{
    VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    return JSValue::encode(JSArrayIterator::create(exec, exec->callee()->globalObject()->arrayIteratorStructure(), IterateKeyValue, thisObj));
}

EncodedJSValue JSC_HOST_CALL arrayProtoFuncKeys(ExecState* exec)
{
    VM& vm = exec->vm();
    JSObject* thisObj = exec->hostThisValue().toThis(exec, StrictMode).toObject(exec);
    RETURN_IF_EXCEPTION(vm, encodedJSValue());
    return JSValue::encode(JSArrayIterator::create(exec, exec->callee()->globalObject()->arrayIteratorStructure(), IterateKey, thisObj));
}

#if 0
static bool moveElements(ExecState* exec, VM& vm, JSArray* target, unsigned targetOffset, JSArray* source, unsigned sourceLength)
{
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (LIKELY(!hasAnyArrayStorage(source->indexingType()) && !source->structure()->holesMustForwardToPrototype(vm))) {
        for (unsigned i = 0; i < sourceLength; ++i) {
            JSValue value = source->tryGetIndexQuickly(i);
            if (value) {
                target->putDirectIndex(exec, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
                RETURN_IF_EXCEPTION(scope, false);
            }
        }
    } else {
        for (unsigned i = 0; i < sourceLength; ++i) {
            JSValue value = getProperty(exec, source, i);
            RETURN_IF_EXCEPTION(scope, false);
            if (value) {
                target->putDirectIndex(exec, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
                RETURN_IF_EXCEPTION(scope, false);
            }
        }
    }
    return true;
}

static EncodedJSValue concatAppendOne(ExecState* exec, VM& vm, JSArray* first, JSValue second)
{
    auto scope = DECLARE_THROW_SCOPE(vm);

    ASSERT(!isJSArray(second));
    ASSERT(!shouldUseSlowPut(first->indexingType()));
    Butterfly* firstButterfly = first->butterfly();
    unsigned firstArraySize = firstButterfly->publicLength();

    IndexingType type = first->mergeIndexingTypeForCopying(indexingTypeForValue(second) | IsArray);

    if (type == NonArray)
        type = first->indexingType();

    Structure* resultStructure = exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(type);
    JSArray* result = JSArray::create(vm, resultStructure, firstArraySize + 1);
    if (!result)
        return JSValue::encode(throwOutOfMemoryError(exec, scope));

    bool success = result->appendMemcpy(exec, vm, 0, first);
    RETURN_IF_EXCEPTION(scope, encodedJSValue());
    if (!success) {
        bool success = moveElements(exec, vm, result, 0, first, firstArraySize);
        ASSERT(!scope.exception() == success);
        if (UNLIKELY(!success))
            return encodedJSValue();
    }

    scope.release();
    result->putDirectIndex(exec, firstArraySize, second);
    return JSValue::encode(result);

}

EncodedJSValue JSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec)
{
    ASSERT(exec->argumentCount() == 2);
    VM& vm = exec->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    JSArray* firstArray = jsCast<JSArray*>(exec->uncheckedArgument(0));

    // This code assumes that neither array has set Symbol.isConcatSpreadable. If the first array
    // has indexed accessors then one of those accessors might change the value of Symbol.isConcatSpreadable
    // on the second argument.
    if (UNLIKELY(shouldUseSlowPut(firstArray->indexingType())))
        return JSValue::encode(jsNull());

    // We need to check the species constructor here since checking it in the JS wrapper is too expensive for the non-optimizing tiers.
    bool isValid = speciesWatchpointsValid(exec, firstArray);
    ASSERT(!scope.exception() || !isValid);
    if (UNLIKELY(!isValid))
        return JSValue::encode(jsNull());

    JSValue second = exec->uncheckedArgument(1);
    if (!isJSArray(second)) {
        scope.release();
        return concatAppendOne(exec, vm, firstArray, second);
    }

    JSArray* secondArray = jsCast<JSArray*>(second);

    Butterfly* firstButterfly = firstArray->butterfly();
    Butterfly* secondButterfly = secondArray->butterfly();

    unsigned firstArraySize = firstButterfly->publicLength();
    unsigned secondArraySize = secondButterfly->publicLength();

    IndexingType secondType = secondArray->indexingType();
    IndexingType type = firstArray->mergeIndexingTypeForCopying(secondType);
    if (type == NonArray || !firstArray->canFastCopy(vm, secondArray) || firstArraySize + secondArraySize >= MIN_SPARSE_ARRAY_INDEX) {
        JSArray* result = constructEmptyArray(exec, nullptr, firstArraySize + secondArraySize);
        RETURN_IF_EXCEPTION(scope, encodedJSValue());

        bool success = moveElements(exec, vm, result, 0, firstArray, firstArraySize);
        ASSERT(!scope.exception() == success);
        if (UNLIKELY(!success))
            return encodedJSValue();
        success = moveElements(exec, vm, result, firstArraySize, secondArray, secondArraySize);
        ASSERT(!scope.exception() == success);
        if (UNLIKELY(!success))
            return encodedJSValue();

        return JSValue::encode(result);
    }

    Structure* resultStructure = exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(type);
    JSArray* result = JSArray::tryCreateUninitializedRestricted(vm, resultStructure, firstArraySize + secondArraySize);
    if (UNLIKELY(!result)) {
		throwOutOfMemoryError(exec, scope);
		return encodedJSValue();
	}

    if (type == ArrayWithDouble) {
        double* buffer = result->butterfly()->contiguousDouble().data();
        memcpy(buffer, firstButterfly->contiguousDouble().data(), sizeof(JSValue) * firstArraySize);
        memcpy(buffer + firstArraySize, secondButterfly->contiguousDouble().data(), sizeof(JSValue) * secondArraySize);
    } else if (type != ArrayWithUndecided) {
        WriteBarrier<Unknown>* buffer = result->butterfly()->contiguous().data();
        memcpy(buffer, firstButterfly->contiguous().data(), sizeof(JSValue) * firstArraySize);
        if (secondType != ArrayWithUndecided)
            memcpy(buffer + firstArraySize, secondButterfly->contiguous().data(), sizeof(JSValue) * secondArraySize);
        else {
            for (unsigned i = secondArraySize; i--;)
                buffer[i + firstArraySize].clear();
        }
    }

    result->butterfly()->setPublicLength(firstArraySize + secondArraySize);
    return JSValue::encode(result);
}

EncodedJSValue JSC_HOST_CALL arrayProtoPrivateFuncAppendMemcpy(ExecState* exec)
{
    ASSERT(exec->argumentCount() == 3);

    VM& vm = exec->vm();
    JSArray* resultArray = jsCast<JSArray*>(exec->uncheckedArgument(0));
    JSArray* otherArray = jsCast<JSArray*>(exec->uncheckedArgument(1));
    JSValue startValue = exec->uncheckedArgument(2);
    ASSERT(startValue.isUInt32AsAnyInt());
    unsigned startIndex = startValue.asUInt32AsAnyInt();
    if (!resultArray->appendMemcpy(exec, vm, startIndex, otherArray))
        moveElements(exec, vm, resultArray, startIndex, otherArray, otherArray->length());

    return JSValue::encode(jsUndefined());
}
#endif

// -------------------- ArrayPrototype.constructor Watchpoint ------------------

static bool verbose = false;

class ArrayPrototypeAdaptiveInferredPropertyWatchpoint : public AdaptiveInferredPropertyValueWatchpointBase {
public:
    typedef AdaptiveInferredPropertyValueWatchpointBase Base;
    ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition&, ArrayPrototype*);

private:
    virtual void handleFire(VM& vm) OVERRIDE;

    ArrayPrototype* m_arrayPrototype;
};

ArrayPrototype::SpeciesWatchpointStatus ArrayPrototype::attemptToInitializeSpeciesWatchpoint(ExecState* exec)
{
    ASSERT(m_speciesWatchpointStatus == SpeciesWatchpointStatus::Uninitialized);

    VM& vm = exec->vm();

    //if (verbose)
    //    dataLog("Attempting to initialize Array species watchpoints for Array.prototype: ", pointerDump(this), " with structure: ", pointerDump(this->structure()), "\nand Array: ", pointerDump(this->globalObject()->arrayConstructor()), " with structure: ", pointerDump(this->globalObject()->arrayConstructor()->structure()), "\n");
    // First we need to make sure that the Array.prototype.constructor property points to Array
    // and that Array[Symbol.species] is the primordial GetterSetter.

    // We only initialize once so flattening the structures does not have any real cost.
    Structure* prototypeStructure = this->structure(vm);
    if (prototypeStructure->isDictionary())
        prototypeStructure = prototypeStructure->flattenDictionaryStructure(vm, this);
    RELEASE_ASSERT(!prototypeStructure->isDictionary());

    JSGlobalObject* globalObject = this->globalObject();
    ArrayConstructor* arrayConstructor = globalObject->arrayConstructor();

    PropertySlot constructorSlot(this, PropertySlot::InternalMethodType::VMInquiry);
    JSValue(this).get(exec, vm.propertyNames->constructor, constructorSlot);
    if (constructorSlot.slotBase() != this
        || !constructorSlot.isCacheableValue()
        || constructorSlot.getValue(exec, vm.propertyNames->constructor) != arrayConstructor)
        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;

    Structure* constructorStructure = arrayConstructor->structure(vm);
    if (constructorStructure->isDictionary())
        constructorStructure = constructorStructure->flattenDictionaryStructure(vm, arrayConstructor);

    PropertySlot speciesSlot(arrayConstructor, PropertySlot::InternalMethodType::VMInquiry);
    JSValue(arrayConstructor).get(exec, vm.propertyNames->speciesSymbol, speciesSlot);
    if (speciesSlot.slotBase() != arrayConstructor
        || !speciesSlot.isCacheableGetter()
        || speciesSlot.getterSetter() != globalObject->speciesGetterSetter())
        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;

    // Now we need to setup the watchpoints to make sure these conditions remain valid.
    prototypeStructure->startWatchingPropertyForReplacements(vm, constructorSlot.cachedOffset());
    constructorStructure->startWatchingPropertyForReplacements(vm, speciesSlot.cachedOffset());

    ObjectPropertyCondition constructorCondition = ObjectPropertyCondition::equivalence(vm, this, this, vm.propertyNames->constructor.impl(), arrayConstructor);
    ObjectPropertyCondition speciesCondition = ObjectPropertyCondition::equivalence(vm, this, arrayConstructor, vm.propertyNames->speciesSymbol.impl(), globalObject->speciesGetterSetter());

    if (!constructorCondition.isWatchable(vm) || !speciesCondition.isWatchable(vm))
        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;

    m_constructorWatchpoint = adoptPtr(new ArrayPrototypeAdaptiveInferredPropertyWatchpoint(constructorCondition, this));
    m_constructorWatchpoint->install(vm);

    m_constructorSpeciesWatchpoint = adoptPtr(new ArrayPrototypeAdaptiveInferredPropertyWatchpoint(speciesCondition, this));
    m_constructorSpeciesWatchpoint->install(vm);

    return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Initialized;
}

ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition& key, ArrayPrototype* prototype)
    : Base(key)
    , m_arrayPrototype(prototype)
{
}

void ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire(VM& vm)
{
    //StringPrintStream out;
    //out.print("ArrayPrototype adaption of ", key(), " failed: ", detail);

    //StringFireDetail stringDetail(out.toCString().data());

    //if (verbose)
    //    WTF::dataLog(stringDetail, "\n");

    m_arrayPrototype->m_speciesWatchpointStatus = ArrayPrototype::SpeciesWatchpointStatus::Fired;
}

} // namespace JSC
