Ver código fonte

Enable ES6IsConcatSpreadable under experimental

Enable ES6IsConcatSpreadable under experimental flag.
Optimize fastpath for cases where [@@isConcatSpreadable] is undefined by caching in ThreadContext.
Add cache invalidations when users add [@@isConcatSpreadable] properties.
Add unit tests to cover more extended usage scenarios.
Suwei Chen 9 anos atrás
pai
commit
a03aa520f4

+ 11 - 2
lib/Common/ConfigFlagsList.h

@@ -283,6 +283,7 @@ PHASE(All)
         PHASE(InlineCandidate)
         PHASE(InlineHostCandidate)
         PHASE(ScriptFunctionWithInlineCache)
+        PHASE(IsConcatSpreadableCache)
         PHASE(Arena)
         PHASE(ApplyUsage)
         PHASE(ObjectHeaderInlining)
@@ -499,7 +500,12 @@ PHASE(All)
     #define DEFAULT_CONFIG_ES6FunctionNameFull     (false)
 #endif
 #define DEFAULT_CONFIG_ES6Generators           (true)
-#define DEFAULT_CONFIG_ES6IsConcatSpreadable   (false)
+#ifdef COMPILE_DISABLE_ES6IsConcatSpreadable
+    // If ES6IsConcatSpreadable needs to be disabled by compile flag, COMPILE_DISABLE_ES6IsConcatSpreadable should be false
+    #define DEFAULT_CONFIG_ES6IsConcatSpreadable   (false)
+#else
+    #define DEFAULT_CONFIG_ES6IsConcatSpreadable   (false)
+#endif
 #define DEFAULT_CONFIG_ES6Math                 (true)
 #ifdef COMPILE_DISABLE_ES6Module
     // If ES6Module needs to be disabled by compile flag, DEFAULT_CONFIG_ES6Module should be false
@@ -952,7 +958,10 @@ FLAGPR           (Boolean, ES6, ES7ExponentiationOperator, "Enable ES7 exponenti
 FLAGPR_REGOVR_EXP(Boolean, ES6, ES7Builtins            , "Enable ES7 built-ins"                                     , DEFAULT_CONFIG_ES7Builtins)
 FLAGPR           (Boolean, ES6, ES7ValuesEntries       , "Enable ES7 Object.values and Object.entries"              , DEFAULT_CONFIG_ES7ValuesEntries)
 FLAGPR           (Boolean, ES6, ES7TrailingComma       , "Enable ES7 trailing comma in function"                    , DEFAULT_CONFIG_ES7TrailingComma)
-FLAGPR           (Boolean, ES6, ES6IsConcatSpreadable  , "Enable ES6 isConcatSpreadable Symbol"                     , DEFAULT_CONFIG_ES6IsConcatSpreadable)
+#ifndef COMPILE_DISABLE_ES6IsConcatSpreadable
+    #define COMPILE_DISABLE_ES6IsConcatSpreadable 0
+#endif
+FLAGPR_REGOVR_EXP(Boolean, ES6, ES6IsConcatSpreadable  , "Enable ES6 isConcatSpreadable Symbol"                     , DEFAULT_CONFIG_ES6IsConcatSpreadable)
 FLAGPR           (Boolean, ES6, ES6Math                , "Enable ES6 Math extensions"                               , DEFAULT_CONFIG_ES6Math)
 
 #ifndef COMPILE_DISABLE_ES6Module

+ 4 - 0
lib/Runtime/Base/ThreadContext.h

@@ -721,6 +721,8 @@ private:
     typedef JsUtil::BaseDictionary<Js::Var, Js::IsInstInlineCache*, ArenaAllocator> IsInstInlineCacheListMapByFunction;
     IsInstInlineCacheListMapByFunction isInstInlineCacheByFunction;
 
+    Js::IsConcatSpreadableCache isConcatSpreadableCache;
+
     ArenaAllocator prototypeChainEnsuredToHaveOnlyWritableDataPropertiesAllocator;
     DListBase<Js::ScriptContext *> prototypeChainEnsuredToHaveOnlyWritableDataPropertiesScriptContext;
 
@@ -841,6 +843,8 @@ public:
 
     UCrtC99MathApis* GetUCrtC99MathApis() { return &ucrtC99MathApis; }
 
+    Js::IsConcatSpreadableCache* GetIsConcatSpreadableCache() { return &isConcatSpreadableCache; }
+
 #ifdef ENABLE_GLOBALIZATION
     Js::DelayLoadWinRtString *GetWinRTStringLibrary();
 #ifdef ENABLE_PROJECTION

+ 66 - 0
lib/Runtime/Language/InlineCache.h

@@ -964,6 +964,72 @@ namespace Js
         void Unregister(ScriptContext * scriptContext);
     };
 
+    // Two-entry Type-indexed circular cache
+    //   cache IsConcatSpreadable() result unless user-defined [@@isConcatSpreadable] exists
+    class IsConcatSpreadableCache
+    {
+        Type *type0, *type1;
+        int lastAccess;
+        BOOL result0, result1;
+
+    public:
+        IsConcatSpreadableCache() :
+            type0(nullptr),
+            type1(nullptr),
+            result0(FALSE),
+            result1(FALSE),
+            lastAccess(1)
+        {
+        }
+
+        bool TryGetIsConcatSpreadable(Type *type, _Out_ BOOL *result)
+        {
+            Assert(type != nullptr);
+            Assert(result != nullptr);
+
+            *result = FALSE;
+            if (type0 == type)
+            {
+                *result = result0;
+                lastAccess = 0;
+                return true;
+            }
+
+            if (type1 == type)
+            {
+                *result = result1;
+                lastAccess = 1;
+                return true;
+            }
+
+            return false;
+        }
+
+        void CacheIsConcatSpreadable(Type *type, BOOL result)
+        {
+            Assert(type != nullptr);
+
+            if (lastAccess == 0)
+            {
+                type1 = type;
+                result1 = result;
+                lastAccess = 1;
+            }
+            else
+            {
+                type0 = type;
+                result0 = result;
+                lastAccess = 0;
+            }
+        }
+
+        void Invalidate()
+        {
+            type0 = nullptr;
+            type1 = nullptr;
+        }
+    };
+
 #if defined(_M_IX86_OR_ARM32)
     CompileAssert(sizeof(IsInstInlineCache) == 0x10);
 #else

+ 33 - 5
lib/Runtime/Language/JavascriptOperators.cpp

@@ -1357,17 +1357,45 @@ CommonNumber:
 
         RecyclableObject* instance = RecyclableObject::FromVar(instanceVar);
         ScriptContext* scriptContext = instance->GetScriptContext();
+
+        if (!PHASE_OFF1(IsConcatSpreadableCachePhase))
+        {
+            BOOL retVal = FALSE;
+            Type *instanceType = instance->GetType();
+            IsConcatSpreadableCache *isConcatSpreadableCache = scriptContext->GetThreadContext()->GetIsConcatSpreadableCache();
+
+            if (isConcatSpreadableCache->TryGetIsConcatSpreadable(instanceType, &retVal))
+            {
+                OUTPUT_TRACE(Phase::IsConcatSpreadableCachePhase, _u("IsConcatSpreadableCache hit: %p\n"), instanceType);
+                return retVal;
+            }
+
+            Var spreadable = nullptr;
+            BOOL hasUserDefinedSpreadable = JavascriptOperators::GetProperty(instance, instance, PropertyIds::_symbolIsConcatSpreadable, &spreadable, scriptContext);
+
+            if (hasUserDefinedSpreadable && spreadable != scriptContext->GetLibrary()->GetUndefined())
+            {
+                return JavascriptConversion::ToBoolean(spreadable, scriptContext);
+            }
+
+            retVal = JavascriptOperators::IsArray(instance);
+
+            if (!hasUserDefinedSpreadable)
+            {
+                OUTPUT_TRACE(Phase::IsConcatSpreadableCachePhase, _u("IsConcatSpreadableCache saved: %p\n"), instanceType);
+                isConcatSpreadableCache->CacheIsConcatSpreadable(instanceType, retVal);
+            }
+
+            return retVal;
+        }
+
         Var spreadable = JavascriptOperators::GetProperty(instance, PropertyIds::_symbolIsConcatSpreadable, scriptContext);
         if (spreadable != scriptContext->GetLibrary()->GetUndefined())
         {
             return JavascriptConversion::ToBoolean(spreadable, scriptContext);
         }
-        if (JavascriptOperators::IsArray(instance))
-        {
-            return true;
-        }
-        return false;
 
+        return JavascriptOperators::IsArray(instance);
     }
 
     Var JavascriptOperators::OP_LdCustomSpreadIteratorList(Var aRight, ScriptContext* scriptContext)

+ 31 - 14
lib/Runtime/Library/JavascriptArray.cpp

@@ -2972,6 +2972,11 @@ namespace Js
                         return;
                     }
 
+                    if (length + idxDest > FiftyThirdPowerOfTwoMinusOne) // 2^53-1: from ECMA 22.1.3.1 Array.prototype.concat(...arguments)
+                    {
+                        JavascriptError::ThrowTypeError(scriptContext, JSERR_IllegalArraySizeAndLength);
+                    }
+
                     RecyclableObject* itemObject = RecyclableObject::FromVar(aItem);
                     Var subItem;
                     uint32 lengthToUin32Max = length.IsSmallIndex() ? length.GetSmallIndex() : MaxArrayLength;
@@ -3044,7 +3049,7 @@ namespace Js
         return true;
     }
 
-    void JavascriptArray::ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
+    JavascriptArray* JavascriptArray::ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
     {
         uint idxDest = 0u;
         for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
@@ -3058,7 +3063,7 @@ namespace Js
                 if (!JavascriptNativeIntArray::Is(pDestArray)) // SetItem could convert pDestArray to a var array if aItem is not an integer if so fall back
                 {
                     ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
-                    return;
+                    return pDestArray;
                 }
                 continue;
             }
@@ -3073,13 +3078,11 @@ namespace Js
                     // Copying the last array forced a conversion, so switch over to the var version
                     // to finish.
                     ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
-                    return;
+                    return pDestArray;
                 }
             }
-            else
+            else if (!JavascriptArray::IsAnyArray(aItem) && remoteTypeIds[idxArg] != TypeIds_Array)
             {
-                Assert(!JavascriptArray::IsAnyArray(aItem) && remoteTypeIds[idxArg] != TypeIds_Array);
-
                 if (TaggedInt::Is(aItem))
                 {
                     pDestArray->DirectSetItemAt(idxDest, TaggedInt::ToInt32(aItem));
@@ -3096,14 +3099,21 @@ namespace Js
                 }
                 ++idxDest;
             }
+            else
+            {
+                JavascriptArray *pVarDestArray = JavascriptNativeIntArray::ConvertToVarArray(pDestArray);
+                ConcatArgs<uint>(pVarDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest);
+                return pVarDestArray;
+            }
         }
         if (pDestArray->GetLength() != idxDest)
         {
             pDestArray->SetLength(idxDest);
         }
+        return pDestArray;
     }
 
-    void JavascriptArray::ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
+    JavascriptArray* JavascriptArray::ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId *remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext)
     {
         uint idxDest = 0u;
         for (uint idxArg = 0; idxArg < args.Info.Count; idxArg++)
@@ -3119,7 +3129,7 @@ namespace Js
                 if (!JavascriptNativeFloatArray::Is(pDestArray)) // SetItem could convert pDestArray to a var array if aItem is not an integer if so fall back
                 {
                     ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
-                    return;
+                    return pDestArray;
                 }
                 continue;
             }
@@ -3133,18 +3143,25 @@ namespace Js
                     converted = CopyNativeIntArrayElementsToFloat(pDestArray, idxDest, pIntArray);
                     idxDest = idxDest + pIntArray->length;
                 }
-                else
+                else if (JavascriptNativeFloatArray::Is(aItem))
                 {
                     JavascriptNativeFloatArray* pItemArray = JavascriptNativeFloatArray::FromVar(aItem);
                     converted = CopyNativeFloatArrayElements(pDestArray, idxDest, pItemArray);
                     idxDest = idxDest + pItemArray->length;
                 }
+                else
+                {
+                    JavascriptArray *pVarDestArray = JavascriptNativeFloatArray::ConvertToVarArray(pDestArray);
+                    ConcatArgs<uint>(pVarDestArray, remoteTypeIds, args, scriptContext, idxArg, idxDest);
+                    return pVarDestArray;
+                }
+
                 if (converted)
                 {
                     // Copying the last array forced a conversion, so switch over to the var version
                     // to finish.
                     ConcatArgs<uint>(pDestArray, remoteTypeIds, args, scriptContext, idxArg + 1, idxDest);
-                    return;
+                    return pDestArray;
                 }
             }
             else
@@ -3167,6 +3184,8 @@ namespace Js
         {
             pDestArray->SetLength(idxDest);
         }
+
+        return pDestArray;
     }
 
     bool JavascriptArray::BoxConcatItem(Var aItem, uint idxArg, ScriptContext *scriptContext)
@@ -3318,15 +3337,13 @@ namespace Js
             {
                 JavascriptNativeIntArray *pIntArray = isArray ? JavascriptNativeIntArray::FromVar(pDestObj) : scriptContext->GetLibrary()->CreateNativeIntArray(cDestLength);
                 pIntArray->EnsureHead<int32>();
-                ConcatIntArgs(pIntArray, remoteTypeIds, args, scriptContext);
-                pDestArray = pIntArray;
+                pDestArray = ConcatIntArgs(pIntArray, remoteTypeIds, args, scriptContext);
             }
             else if (isFloat)
             {
                 JavascriptNativeFloatArray *pFArray = isArray ? JavascriptNativeFloatArray::FromVar(pDestObj) : scriptContext->GetLibrary()->CreateNativeFloatArray(cDestLength);
                 pFArray->EnsureHead<double>();
-                ConcatFloatArgs(pFArray, remoteTypeIds, args, scriptContext);
-                pDestArray = pFArray;
+                pDestArray = ConcatFloatArgs(pFArray, remoteTypeIds, args, scriptContext);
             }
             else
             {

+ 3 - 2
lib/Runtime/Library/JavascriptArray.h

@@ -116,6 +116,7 @@ namespace Js
         static uint32 const MaxArrayLength = InvalidIndex;
         static uint32 const MaxInitialDenseLength=1<<18;
         static ushort const MergeSegmentsLengthHeuristics = 128; // If the length is less than MergeSegmentsLengthHeuristics then try to merge the segments
+        static uint64 const FiftyThirdPowerOfTwoMinusOne = 0x1FFFFFFFFFFFFF;  // 2^53-1
 
         static const Var MissingItem;
         template<typename T> static T GetMissingItem();
@@ -759,10 +760,10 @@ namespace Js
         static void ConcatArgs(RecyclableObject* pDestObj, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext, uint start, BigIndex startIdxDest, BOOL firstPromotedItemIsSpreadable, BigIndex firstPromotedItemLength);
         template<typename T>
         static void ConcatArgs(RecyclableObject* pDestObj, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext, uint start = 0, uint startIdxDest = 0u, BOOL FirstPromotedItemIsSpreadable = false, BigIndex FirstPromotedItemLength = 0u);
-        static void ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext);
+        static JavascriptArray* ConcatIntArgs(JavascriptNativeIntArray* pDestArray, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext);
         static bool PromoteToBigIndex(BigIndex lhs, BigIndex rhs);
         static bool PromoteToBigIndex(BigIndex lhs, uint32 rhs);
-        static void ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext);
+        static JavascriptArray* ConcatFloatArgs(JavascriptNativeFloatArray* pDestArray, TypeId* remoteTypeIds, Js::Arguments& args, ScriptContext* scriptContext);
     private:
         template<typename T=uint32>
         static RecyclableObject* ArraySpeciesCreate(Var pThisArray, T length, ScriptContext* scriptContext, bool* pIsIntArray = nullptr, bool* pIsFloatArray = nullptr);

+ 2 - 0
lib/Runtime/Library/JavascriptLibrary.h

@@ -1037,6 +1037,8 @@ namespace Js
         RecyclableObject* TryGetStringTemplateCallsiteObject(ParseNodePtr pnode);
         RecyclableObject* TryGetStringTemplateCallsiteObject(RecyclableObject* callsite);
 
+        static void CheckAndInvalidateIsConcatSpreadableCache(PropertyId propertyId, ScriptContext *scriptContext);
+
 #if DBG_DUMP
         static const char16* GetStringTemplateCallsiteObjectKey(Var callsite);
 #endif

+ 9 - 0
lib/Runtime/Library/JavascriptLibrary.inl

@@ -33,6 +33,15 @@ namespace Js
         return AddFunctionToLibraryObject(object, scriptContext->GetOrAddPropertyIdTracked(propertyName), functionInfo, length);
     }
 
+    inline void JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(PropertyId propertyId, ScriptContext *scriptContext)
+    {
+        if (!PHASE_OFF1(IsConcatSpreadableCachePhase) && propertyId == PropertyIds::_symbolIsConcatSpreadable)
+        {
+            OUTPUT_TRACE(Phase::IsConcatSpreadableCachePhase, _u("IsConcatSpreadableCache invalidated\n"));
+            scriptContext->GetThreadContext()->GetIsConcatSpreadableCache()->Invalidate();
+        }
+    }
+
 #if ENABLE_COPYONACCESS_ARRAY
     template <>
     inline void JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(const Var instance)

+ 3 - 0
lib/Runtime/Library/JavascriptProxy.cpp

@@ -321,6 +321,9 @@ namespace Js
                 return FALSE;
             JavascriptError::ThrowTypeError(scriptContext, JSERR_ErrorOnRevokedProxy, _u("get"));
         }
+
+        RecyclableObject *target = this->target;
+
         JavascriptFunction* getGetMethod = GetMethodHelper(PropertyIds::get, scriptContext);
         Var getGetResult;
         if (nullptr == getGetMethod || scriptContext->IsHeapEnumInProgress())

+ 2 - 0
lib/Runtime/Types/DictionaryTypeHandler.cpp

@@ -779,6 +779,8 @@ namespace Js
         bool throwIfNotExtensible = (flags & (PropertyOperation_ThrowIfNotExtensible | PropertyOperation_StrictMode)) != 0;
         bool isForce = (flags & PropertyOperation_Force) != 0;
 
+        JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(propertyId, scriptContext);
+
         Assert(propertyId != Constants::NoProperty);
         PropertyRecord const* propertyRecord = scriptContext->GetPropertyName(propertyId);
         if (propertyMap->TryGetReference(propertyRecord, &descriptor))

+ 2 - 0
lib/Runtime/Types/PathTypeHandler.cpp

@@ -252,6 +252,8 @@ namespace Js
         Assert(value != nullptr || IsInternalPropertyId(propertyId));
         PropertyIndex index = PathTypeHandlerBase::GetPropertyIndex(propertyId);
 
+        JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(propertyId, instance->GetScriptContext());
+
         if (index != Constants::NoSlot)
         {
             // If type is shared then the handler must be shared as well.  This is a weaker invariant than in AddPropertyInternal,

+ 2 - 0
lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp

@@ -1302,6 +1302,8 @@ namespace Js
         Assert(propertyId != Constants::NoProperty);
         PropertyRecord const* propertyRecord = instance->GetScriptContext()->GetPropertyName(propertyId);
 
+        JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(propertyId, instance->GetScriptContext());
+
         // It can be the case that propertyRecord is a symbol property and it has the same string description as
         // another property which is in the propertyMap. If we are in a string-keyed type handler, the propertyMap
         // will find that normal property when we call TryGetReference with the symbol propertyRecord. However,

+ 3 - 0
lib/Runtime/Types/SimpleTypeHandler.cpp

@@ -447,6 +447,9 @@ namespace Js
     {
         ScriptContext* scriptContext = instance->GetScriptContext();
         int index;
+
+        JavascriptLibrary::CheckAndInvalidateIsConcatSpreadableCache(propertyId, scriptContext);
+
         if (GetDescriptor(propertyId, &index))
         {
             if (descriptors[index].Attributes & PropertyDeleted)

+ 594 - 42
test/es6/es6IsConcatSpreadable.js

@@ -5,33 +5,34 @@
 
 WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
 
-function areEqual(a,b)
+function areEqual(a, b, msg)
 {
-    assert.areEqual(a.length,b.length);
+    assert.areEqual(a.length, b.length, msg);
     for(var i = 0;i < a.length; i++)
     {
-        assert.areEqual(a[i],b[i]);
+        assert.areEqual(a[i], b[i], msg);
     }
 }
-function compareConcats(a,b)
+function compareConcats(a, b)
 {
     var c = a.concat(b);
     b[Symbol.isConcatSpreadable] = false;
     var d = a.concat(b);
-    assert.areEqual(b,d[a.length],"Indexing d at a.length should return b");
+    assert.areEqual(b, d[a.length], "Indexing d at a.length should return b");
     for(var i = 0;i < a.length; i++)
     {
-        assert.areEqual(a[i],c[i],"confirm array a makes up the first half of array c");
-        assert.areEqual(a[i],d[i],"confirm array a makes up the first half of array d");
+        assert.areEqual(a[i], c[i], "confirm array a makes up the first half of array c");
+        assert.areEqual(a[i], d[i], "confirm array a makes up the first half of array d");
     }
     for(var i = 0;i < b.length; i++)
     {
-        assert.areEqual(b[i],c[a.length+i],"confirm array b makes up the second half of array c");
-        assert.areEqual(b[i],d[a.length][i],"confirm array b makes up a sub array at d[a.length]");
+        assert.areEqual(b[i], c[a.length+i], "confirm array b makes up the second half of array c");
+        assert.areEqual(b[i], d[a.length][i], "confirm array b makes up a sub array at d[a.length]");
 
     }
-        assert.areEqual(a.length+1,d.length,"since we are not flattening the top level array grows only by 1");
-        excludeLengthCheck = false;
+
+    assert.areEqual(a.length+1, d.length, "since we are not flattening the top level array grows only by 1");
+    excludeLengthCheck = false;
 
     delete b[Symbol.isConcatSpreadable];
 }
@@ -40,80 +41,80 @@ var tests = [
        name: "nativeInt Arrays",
        body: function ()
        {
-            var a = [1,2,3];
-            var b = [4,5,6];
-            compareConcats(a,b);
+            var a = [1, 2, 3];
+            var b = [4, 5, 6];
+            compareConcats(a, b);
        }
     },
     {
        name: "nativefloat Arrays",
        body: function ()
        {
-            var a = [1.1,2.2,3.3];
-            var b = [4.4,5.5,6.6];
-            compareConcats(a,b);
+            var a = [1.1, 2.2, 3.3];
+            var b = [4.4, 5.5, 6.6];
+            compareConcats(a, b);
        }
     },
     {
        name: "Var Arrays",
        body: function ()
        {
-            var a = ["a","b","c"];
-            var b = ["d","e","f"];
-            compareConcats(a,b);
+            var a = ["a", "b", "c"];
+            var b = ["d", "e", "f"];
+            compareConcats(a, b);
        }
     },
     {
        name: "intermixed Arrays",
        body: function ()
        {
-            var a = [1.1,"b",3];
-            var b = [4,5.5,"f"];
-            compareConcats(a,b);
+            var a = [1.1, "b", 3];
+            var b = [4, 5.5, "f"];
+            compareConcats(a, b);
        }
     },
     {
        name: "concating spreadable objects",
        body: function ()
        {
-            var a = [1,2,3,4,5,6,7,8,9,10];
-            var b = [1,2,3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8, "length" : 3 }, 9, 10);
-            areEqual(a,b);
+            var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+            var b = [1, 2, 3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8, "length" : 3 }, 9, 10);
+            areEqual(a, b);
             // Test to confirm we Spread to nothing when length is not set
-            var a = [1,2,3,4,5,9,10];
-            var b = [1,2,3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8}, 9, 10);
-            areEqual(a,b);
+            var a = [1, 2, 3, 4, 5, 9, 10];
+            var b = [1, 2, 3].concat(4, 5, { [Symbol.isConcatSpreadable]: true, 0: 6, 1: 7, 2: 8}, 9, 10);
+            areEqual(a, b);
        }
     },
     {
        name: " concat with non-arrays",
        body: function ()
        {
-            var a = [1.1,2.1,3.1];
+            var a = [1.1, 2.1, 3.1];
             var b = 4.1;
-            compareConcats(a,b);
-            var a = [1,2,3];
+            compareConcats(a, b);
+            var a = [1, 2, 3];
             var b = 4;
-            compareConcats(a,b);
-            var a = [1,2,3];
+            compareConcats(a, b);
+            var a = [1, 2, 3];
             var b = "b";
-            compareConcats(a,b);
-            var a = [1,2,3];
+            compareConcats(a, b);
+            var a = [1, 2, 3];
             var b = true;
-            compareConcats(a,b);
+            compareConcats(a, b);
        }
     },
     {
        name: "override with constructors",
        body: function ()
        {
-            var a = [1,2,3];
-            var b = [4.4,5.5,6.6];
+            var a = [1, 2, 3];
+            var b = [4.4, 5.5, 6.6];
             var c = [Object, {}, undefined, Math.sin];
             for(var i = 0; i < c.length;i++)
             {
                 a['constructor'] = c[i];
-                compareConcats(a,b);
+                compareConcats(a, b);
             }
             var o  = [];
             o.constructor = function()
@@ -122,9 +123,560 @@ var tests = [
                 a[0] = 10;
                 return a;
             }
-            areEqual([1,2,3],o.concat([1,2,3]));
+            areEqual([1, 2, 3], o.concat([1, 2, 3]));
        }
-    }
+    },
+    {
+        name: "test ToBoolean on array[@@isConcatSpreadable]",
+        body: function ()
+        {
+            function test(x, y, z) {
+
+                var a = [x], b = [y, z];
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable] undefined');
+
+                b[Symbol.isConcatSpreadable] = null;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==null');
+
+                b[Symbol.isConcatSpreadable] = false;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==false');
+
+                b[Symbol.isConcatSpreadable] = '';
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==\'\'');
+
+                b[Symbol.isConcatSpreadable] = 0;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==0');
+
+                b[Symbol.isConcatSpreadable] = +0.0;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==+0.0');
+
+                b[Symbol.isConcatSpreadable] = -0.0;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==-0.0');
+
+                b[Symbol.isConcatSpreadable] = NaN;
+                areEqual([x, [y, z]], a.concat(b), '[@@isConcatSpreadable]==NaN');
+
+                b[Symbol.isConcatSpreadable] = undefined;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==undefined');
+
+                b[Symbol.isConcatSpreadable] = true;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==true');
+
+                b[Symbol.isConcatSpreadable] = 'abc';
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=\'abc\'');
+
+                b[Symbol.isConcatSpreadable] = 0.1;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==0.1');
+
+                b[Symbol.isConcatSpreadable] = -0.1;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==-0.1');
+
+                b[Symbol.isConcatSpreadable] = Symbol();
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==Symbol()');
+
+                b[Symbol.isConcatSpreadable] = {};
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=={}');
+
+                delete b[Symbol.isConcatSpreadable];
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable] deleted');
+            }
+
+            test(1, 2, 3);
+            test(1.1, 2.2, 3.3);
+            test("a", "b", "c");
+            test(1.1, "b", 3);
+            test(4, 5.5, "f");
+            test(undefined, NaN, function(){});
+        }
+    },
+    {
+        name: "test ToBoolean on object[@@isConcatSpreadable]",
+        body: function ()
+        {
+            function test(x, y, z) {
+
+                var a = [x], b = {'0':y, '1':z, 'length':2};
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable] undefined');
+
+                b[Symbol.isConcatSpreadable] = null;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==null');
+
+                b[Symbol.isConcatSpreadable] = false;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==false');
+
+                b[Symbol.isConcatSpreadable] = '';
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==\'\'');
+
+                b[Symbol.isConcatSpreadable] = 0;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==0');
+
+                b[Symbol.isConcatSpreadable] = +0.0;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==+0.0');
+
+                b[Symbol.isConcatSpreadable] = -0.0;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==-0.0');
+
+                b[Symbol.isConcatSpreadable] = NaN;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==NaN');
+
+                b[Symbol.isConcatSpreadable] = undefined;
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable]==undefined');
+
+                b[Symbol.isConcatSpreadable] = true;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==true');
+
+                b[Symbol.isConcatSpreadable] = 'abc';
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==\'abc\'');
+
+                b[Symbol.isConcatSpreadable] = 0.1;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==0.1');
+
+                b[Symbol.isConcatSpreadable] = -0.1;
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==-0.1');
+
+                b[Symbol.isConcatSpreadable] = Symbol();
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]==Symbol()');
+
+                b[Symbol.isConcatSpreadable] = {};
+                areEqual([x, y, z], a.concat(b), '[@@isConcatSpreadable]=={}');
+
+                delete b[Symbol.isConcatSpreadable];
+                areEqual([x, b], a.concat(b), '[@@isConcatSpreadable] deleted');
+            }
+
+            test(1, 2, 3);
+            test(1.1, 2.2, 3.3);
+            test("a", "b", "c");
+            test(1.1, "b", 3);
+            test(4, 5.5, "f");
+            test(undefined, NaN, function(){});
+        }
+    },
+    {
+        name: "two arrays that may share the same type",
+        body: function ()
+        {
+            function test(x, y, z) {
+                var a = [x], b = [y, z], c = [y, z];
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] undefined');
+                areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] undefined');
+
+                b[Symbol.isConcatSpreadable] = false;
+                areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
+                areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] undefined');
+
+                c[Symbol.isConcatSpreadable] = false;
+                areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                b[Symbol.isConcatSpreadable] = true;
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                c[Symbol.isConcatSpreadable] = true;
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
+                areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable]==true');
+
+                c[Symbol.isConcatSpreadable] = false;
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==true');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                b[Symbol.isConcatSpreadable] = false;
+                areEqual([x, [y, z]], a.concat(b), 'b[@@isConcatSpreadable]==false');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                b[Symbol.isConcatSpreadable] = undefined;
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable]==undefined');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                delete b[Symbol.isConcatSpreadable];
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] deleted');
+                areEqual([x, [y, z]], a.concat(c), 'c[@@isConcatSpreadable]==false');
+
+                delete c[Symbol.isConcatSpreadable];
+                areEqual([x, y, z], a.concat(b), 'b[@@isConcatSpreadable] deleted');
+                areEqual([x, y, z], a.concat(c), 'c[@@isConcatSpreadable] deleted');
+            }
+
+            test(1, 2, 3);
+            test(1.1, 2.2, 3.3);
+            test("a", "b", "c");
+            test(1.1, "b", 3);
+            test(4, 5.5, "f");
+            test(undefined, NaN, function(){});
+        }
+    },
+    {
+        name: "user-defined length",
+        body: function ()
+        {
+            function test(a, b, c) {
+                var args = (function() { return arguments; })(a, b, c);
+                args[Symbol.isConcatSpreadable] = true;
+                areEqual([a, b, c, a, b, c], [].concat(args, args), '['+a+', '+b+', '+c+', '+a+', '+b+', '+c+']');
+                Object.defineProperty(args, "length", { value: 6 });
+                areEqual([a, b, c, undefined, undefined, undefined], [].concat(args), '['+a+', '+b+', '+c+', undefined, undefined, undefined]');
+            }
+
+            test(1, 2, 3);
+            test(1.1, 2.2, 3.3);
+            test("a", "b", "c");
+            test(1.1, "b", 3);
+            test(4, 5.5, "f");
+            test(undefined, NaN, function(){});
+        }
+    },
+    {
+        name: "concat a mix of user-constructed objects and arrays",
+        body: function ()
+        {
+            class MyObj extends Object {}
+            var a = new MyObj;
+            a.length = 5;
+            a[0] = 'a';
+            a[2] = 'b';
+            a[4] = 'c';
+
+            var b = { length: 3, "0": "a", "1": "b", "2": "c" };
+
+            class MyArray extends Array {}
+            var c = new MyArray("a", "b", "c");
+            var d = ['e', 'f', 'g'];
+
+            a[Symbol.isConcatSpreadable] = true;
+            d[Symbol.isConcatSpreadable] = false;
+
+            areEqual(['a', undefined, 'b', undefined, 'c', b, 'a', 'b', 'c', ['e', 'f', 'g']], Array.prototype.concat.call(a, b, c, d));
+        }
+    },
+    {
+        name: "verify ToLength operation",
+        body: function ()
+        {
+            var obj = {"length": {valueOf: null, toString: null}};
+            obj[Symbol.isConcatSpreadable] = true;
+            assert.throws(()=>Array.prototype.concat.call(obj), TypeError, '{valueOf: null, toString: null}', "Number expected");
+
+            obj = {"length": {toString: function() { throw new Error('User-defined error in toString'); }, valueOf: null}};
+            obj[Symbol.isConcatSpreadable] = true;
+            assert.throws(()=>Array.prototype.concat.call(obj), Error, 'toString() throws', "User-defined error in toString");
+
+            obj = {"length": {toString: function() { return 'string'; }, valueOf: null}};
+            obj[Symbol.isConcatSpreadable] = true;
+            areEqual([], [].concat(obj), ' toString() returns string');
+
+            obj = {"length": {valueOf: function() { throw new Error('User-defined error in valueOf'); }, toString: null}};
+            obj[Symbol.isConcatSpreadable] = true;
+            assert.throws(()=>Array.prototype.concat.call(obj), Error, 'valueOf() throws', "User-defined error in valueOf");
+
+            obj = {"length": {valueOf: function() { return 'string'; }, toString: null}};
+            obj[Symbol.isConcatSpreadable] = true;
+            areEqual([], [].concat(obj), 'toString() returns string');
+
+            obj = { "length": -4294967294, "0": "a", "2": "b", "4": "c" };
+            obj[Symbol.isConcatSpreadable] = true;
+            areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
+
+            obj.length = -0.0;
+            areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
+
+            obj.length = "-4294967294";
+            areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
+
+            obj.length = "-0.0";
+            areEqual([], [].concat(obj), 'ToLength clamps negative to zero');
+        }
+    },
+    {
+        name: "Getter of [@@isConcatSpreadable] throws",
+        body: function ()
+        {
+            var obj = {};
+            Object.defineProperty(obj, Symbol.isConcatSpreadable, {
+                get: function() { throw Error('User-defined error in @@isConcatSpreadable getter'); }
+            });
+            assert.throws(()=>[].concat(obj), Error, '[].concat(obj)', "User-defined error in @@isConcatSpreadable getter");
+            assert.throws(()=>Array.prototype.concat.call(obj), Error, 'Array.prototype.concat.call(obj)', "User-defined error in @@isConcatSpreadable getter");
+        }
+    },
+    {
+        name: "spread Function object",
+        body: function ()
+        {
+            function test(arr) {
+                var func = function(x, y, z){};
+                areEqual([func], [].concat(func), 'Function object');
+
+                func[Symbol.isConcatSpreadable] = true;
+                areEqual([undefined, undefined, undefined], [].concat(func), 'func[Symbol.isConcatSpreadable] == true');
+
+                func[Symbol.isConcatSpreadable] = false;
+                areEqual([func], [].concat(func), 'func[Symbol.isConcatSpreadable] == false');
+
+                func[Symbol.isConcatSpreadable] = true;
+                areEqual([undefined, undefined, undefined], [].concat(func), 'func[Symbol.isConcatSpreadable] == true');
+
+                func[0] = arr[0];
+                func[1] = arr[1];
+                func[2] = arr[2];
+                areEqual(arr, [].concat(func), 'func[0..2] assigned');
+
+                delete func[0];
+                delete func[1];
+                delete func[2];
+                areEqual([undefined, undefined, undefined], [].concat(func), 'func[0..2] deleted');
+
+                delete func[Symbol.isConcatSpreadable];
+                areEqual([func], [].concat(func), 'func[Symbol.isConcatSpreadable] deleted');
+
+                Function.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual([undefined, undefined, undefined], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == true');
+
+                Function.prototype[Symbol.isConcatSpreadable] = false;
+                areEqual([func], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == false');
+
+                Function.prototype[0] = arr[0];
+                Function.prototype[1] = arr[1];
+                Function.prototype[2] = arr[2];
+                areEqual([func], [].concat(func), 'Function.prototype[0..2] assigned');
+
+                Function.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual(arr, [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] == true');
+
+                delete Function.prototype[0];
+                delete Function.prototype[1];
+                delete Function.prototype[2];
+                areEqual([undefined, undefined, undefined], [].concat(func), 'Function.prototype[0..2] deleted');
+
+                delete Function.prototype[Symbol.isConcatSpreadable];
+                areEqual([func], [].concat(func), 'Function.prototype[Symbol.isConcatSpreadable] deleted');
+            }
+
+            test([1, 2, 3]);
+            test([1.1, 2.2, 3.3]);
+            test(["a", "b", "c"]);
+            test([2, NaN, function(){}]);
+        }
+    },
+    {
+        name: "spread Number, Boolean, and RegExp",
+        body: function ()
+        {
+            function test(Type, initVal, arr) {
+                var obj = new Type(initVal);
+                areEqual([obj], [].concat(obj), Type.name+' obj');
+
+                obj[Symbol.isConcatSpreadable] = true;
+                areEqual([], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == true');
+
+                obj.length = arr.length;
+                areEqual(new Array(arr.length), [].concat(obj), Type.name+' obj[length] assigned');
+
+                for (var i = 0; i < arr.length; i++) {
+                    obj[i] = arr[i];
+                }
+                areEqual(arr, [].concat(obj), Type.name+' obj[0..length] assigned');
+
+                obj[Symbol.isConcatSpreadable] = false;
+                areEqual([obj], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == false');
+
+                obj[Symbol.isConcatSpreadable] = true;
+                areEqual(arr, [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] == true');
+
+                for (var i = 0; i < arr.length; i++) {
+                    delete obj[i];
+                }
+                areEqual(new Array(arr.length), [].concat(obj), Type.name+' obj[0..length] deleted');
+
+                delete obj.length;
+                areEqual([], [].concat(obj), Type.name+' obj[length] deleted');
+
+                delete obj[Symbol.isConcatSpreadable];
+                areEqual([obj], [].concat(obj), Type.name+' obj[Symbol.isConcatSpreadable] deleted');
+
+                Type.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual([], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == true');
+
+                Type.prototype.length = arr.length;
+                areEqual(new Array(arr.length), [].concat(obj), Type.name+'.prototype[length] assigned');
+
+                for (var i = 0; i < arr.length; i++) {
+                    Type.prototype[i] = arr[i];
+                }
+                areEqual(arr, [].concat(obj), Type.name+'.prototype[0..length] assigned');
+
+                Type.prototype[Symbol.isConcatSpreadable] = false;
+                areEqual([obj], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == false');
+
+                Type.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual(arr, [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] == true');
+
+                for (var i = 0; i < arr.length; i++) {
+                    delete Type.prototype[i];
+                }
+                areEqual(new Array(arr.length), [].concat(obj), Type.name+'.prototype[0..length] deleted');
+
+                delete Type.prototype.length;
+                areEqual([], [].concat(obj), Type.name+'.prototype[length] deleted');
+
+                delete Type.prototype[Symbol.isConcatSpreadable];
+                areEqual([obj], [].concat(obj), Type.name+'.prototype[Symbol.isConcatSpreadable] deleted');
+            }
+
+            test(Number, 0, [1, 2, 3]);
+            test(Number, -0.1, [1.1, 2.2, 3.3]);
+            test(Number, NaN, ["a", "b", "c"]);
+            test(Number, 321, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
+
+            test(Boolean, true, [1, 2, 3]);
+            test(Boolean, false, [1.1, 2.2, 3.3]);
+            test(Boolean, true, ["a", "b", "c"]);
+            test(Boolean, false, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
+
+            test(RegExp, /^/, [1, 2, 3]);
+            test(RegExp, /abc/, [1.1, 2.2, 3.3]);
+            test(RegExp, /(\d+)/, ["a", "b", "c"]);
+            test(RegExp, /^\s*\S+\s*$/, [1, "ab", 2.2, 2, NaN, 3, function(){ }]);
+        }
+    },
+    {
+        name: "spread String object",
+        body: function ()
+        {
+            function test() {
+                var s = new String("abc");
+                areEqual([s], [].concat(s), "string");
+
+                s[Symbol.isConcatSpreadable] = true;
+                areEqual(['a', 'b', 'c'], [].concat(s), "string s[Symbol.isConcatSpreadable] == true");
+
+                s[Symbol.isConcatSpreadable] = false;
+                areEqual([s], [].concat(s), "string s[Symbol.isConcatSpreadable] == false");
+
+                s[Symbol.isConcatSpreadable] = true;
+                areEqual(['a', 'b', 'c'], [].concat(s), "string s[Symbol.isConcatSpreadable] == true");
+
+                delete s[Symbol.isConcatSpreadable];
+                areEqual([s], [].concat(s), "string s[Symbol.isConcatSpreadable] deleted");
+
+                String.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual(['a', 'b', 'c'], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == true");
+
+                String.prototype[Symbol.isConcatSpreadable] = false;
+                areEqual([s], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == false");
+
+                String.prototype[Symbol.isConcatSpreadable] = true;
+                areEqual(['a', 'b', 'c'], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] == true");
+
+                delete String.prototype[Symbol.isConcatSpreadable];
+                areEqual([s], [].concat(s), "string.prototype[Symbol.isConcatSpreadable] deleted");
+            }
+
+            test();
+        }
+    },
+    {
+        name: "Revokable proxy revoked when retrieving [@@isConcatSpreadable]",
+        body: function ()
+        {
+            // proxy revoked
+            var obj = {};
+            var pobj = Proxy.revocable(obj, {
+                get: function(target, prop) {
+                    if (prop === Symbol.isConcatSpreadable) { pobj.revoke(); }
+                    return obj[prop];
+                }
+            });
+            assert.throws(()=>[].concat(pobj.proxy), TypeError, 'proxy revoked', 'method  is called on a revoked Proxy object');
+        }
+    },
+    {
+        name: "[@@isConcatSpreadable] getter altering array type",
+        body: function ()
+        {
+            function test(arr, idx, elem) {
+                var expected = arr.slice(0);
+                expected[idx] = elem;
+                Object.defineProperty(arr, Symbol.isConcatSpreadable, {
+                        get: function() {
+                            arr[idx] = elem;
+                            return true;
+                        }
+                    })
+                areEqual(expected, [].concat(arr), 'expecting ['+expected+']');
+            }
+
+            test([1, 2, 3], 1, 'abc');
+            test([1.1, 2.2, 3.3], 0, {});
+        }
+    },
+    {
+        name: "[@@isConcatSpreadable] getter altering binding",
+        body: function ()
+        {
+            function test(arr, expected) {
+                Object.defineProperty(arr, Symbol.isConcatSpreadable, {
+                        get: function() {
+                            arr = [];
+                            return true;
+                        }
+                    })
+                areEqual(expected, Array.prototype.concat.call(arr, arr), 'expecting ['+expected+']');
+                areEqual([], Array.prototype.concat.call(arr, arr), 'expecting []');
+            }
+
+            test([1, 2, 3], [1, 2, 3, 1, 2, 3]);
+            test([1.1, 2.2, 3.3], [1.1, 2.2, 3.3, 1.1, 2.2, 3.3]);
+            test(["a", "b", "c"], ["a", "b", "c", "a", "b", "c"]);
+            test([1.1, "b", 3], [1.1, "b", 3, 1.1, "b", 3]);
+            test([4, 5.5, "f"], [4, 5.5, "f", 4, 5.5, "f"]);
+            var func = function() {};
+            test([undefined, NaN, func], [undefined, NaN, func, undefined, NaN, func]);
+        }
+    },
+    {
+        name: "[@@isConcatSpreadable] getter changing array to ES5 array",
+        body: function ()
+        {
+            function test(arr, idx, elem) {
+                var expected = arr.slice(0);
+                expected[idx] = elem;
+                Object.defineProperty(arr, Symbol.isConcatSpreadable, {
+                        get: function() {
+                            Object.defineProperty(arr, idx, { 'get': function(){ return elem; } });
+                            return true;
+                        }
+                    })
+                areEqual(expected, Array.prototype.concat.call(arr), 'expecting ['+arr+']');
+            }
+
+            test([1, 2, 3], 1, 'abc');
+            test([1.1, 2.2, 3.3], 0, {});
+        }
+    },
+    {
+        name: "[@@isConcatSpreadable] getter setting illegal length property in object",
+        body: function ()
+        {
+            function test(a) {
+                var b = {"0":1, "1":2, "length": 2};
+                Object.defineProperty(b, Symbol.isConcatSpreadable, {
+                        get: function() {
+                            b.length = 9007199254740989;
+                            return true;
+                        }
+                    });
+                assert.throws(()=>a.concat(b), TypeError, a, "Illegal length and size specified for the array");
+            }
+
+            test([1, 2, 3]);
+            test([1.1, 2.2, 3.3]);
+            test(["a", "b", "c"]);
+            test([1.1, "b", 3]);
+            test([4, 5.5, "f"]);
+            test([undefined, NaN, function(){}]);
+        }
+    },
     ];
 
 testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 1 - 1
test/es6/rlexe.xml

@@ -67,7 +67,7 @@
   <test>
     <default>
       <files>es6IsConcatSpreadable.js</files>
-      <compile-flags>-es6isconcatspreadable -args summary -endargs</compile-flags>
+      <compile-flags>-es6tolength -es6isconcatspreadable -args summary -endargs</compile-flags>
     </default>
   </test>
   <test>