Clients of JSArray::tryCreateForInitializationPrivate() should do their own null...
[WebKit-https.git] / Source / JavaScriptCore / runtime / JSArray.cpp
index ff73148..ff9e054 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
- *  Copyright (C) 2003, 2007-2009, 2012-2013, 2015-2016 Apple Inc. All rights reserved.
+ *  Copyright (C) 2003-2017 Apple Inc. All rights reserved.
  *  Copyright (C) 2003 Peter Kelly (pmk@post.com)
  *  Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
  *
 #include "ButterflyInlines.h"
 #include "CodeBlock.h"
 #include "Error.h"
-#include "Executable.h"
 #include "GetterSetter.h"
 #include "IndexingHeaderInlines.h"
 #include "JSArrayInlines.h"
 #include "JSCInlines.h"
 #include "PropertyNameArray.h"
-#include "Reject.h"
+#include "TypeError.h"
 #include <wtf/Assertions.h>
 
 using namespace std;
@@ -41,6 +40,8 @@ using namespace WTF;
 
 namespace JSC {
 
+static const char* const LengthExceededTheMaximumArrayLengthError = "Length exceeded the maximum array length";
+
 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray);
 
 const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, CREATE_METHOD_TABLE(JSArray)};
@@ -59,9 +60,9 @@ Butterfly* createArrayButterflyInDictionaryIndexingMode(
     return butterfly;
 }
 
-JSArray* JSArray::tryCreateUninitialized(VM& vm, Structure* structure, unsigned initialLength)
+JSArray* JSArray::tryCreateForInitializationPrivate(VM& vm, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength)
 {
-    if (initialLength > MAX_STORAGE_VECTOR_LENGTH)
+    if (UNLIKELY(initialLength > MAX_STORAGE_VECTOR_LENGTH))
         return 0;
 
     unsigned outOfLineStorage = structure->outOfLineCapacity();
@@ -76,8 +77,8 @@ JSArray* JSArray::tryCreateUninitialized(VM& vm, Structure* structure, unsigned
             || hasContiguous(indexingType));
 
         unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, initialLength);
-        void* temp = vm.heap.tryAllocateAuxiliary(nullptr, Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)));
-        if (!temp)
+        void* temp = vm.auxiliarySpace.tryAllocate(deferralContext, Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)));
+        if (UNLIKELY(!temp))
             return nullptr;
         butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage);
         butterfly->setVectorLength(vectorLength);
@@ -91,8 +92,8 @@ JSArray* JSArray::tryCreateUninitialized(VM& vm, Structure* structure, unsigned
         }
     } else {
         unsigned vectorLength = ArrayStorage::optimalVectorLength(0, structure, initialLength);
-        void* temp = vm.heap.tryAllocateAuxiliary(nullptr, Butterfly::totalSize(0, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength)));
-        if (!temp)
+        void* temp = vm.auxiliarySpace.tryAllocate(deferralContext, Butterfly::totalSize(0, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength)));
+        if (UNLIKELY(!temp))
             return nullptr;
         butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage);
         *butterfly->indexingHeader() = indexingHeaderForArrayStorage(initialLength, vectorLength);
@@ -104,7 +105,7 @@ JSArray* JSArray::tryCreateUninitialized(VM& vm, Structure* structure, unsigned
             storage->m_vector[i].clear();
     }
 
-    return createWithButterfly(vm, structure, butterfly);
+    return createWithButterfly(vm, deferralContext, structure, butterfly);
 }
 
 void JSArray::setLengthWritable(ExecState* exec, bool writable)
@@ -129,22 +130,22 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName
     JSArray* array = jsCast<JSArray*>(object);
 
     // 3. If P is "length", then
-    if (propertyName == exec->propertyNames().length) {
+    if (propertyName == vm.propertyNames->length) {
         // All paths through length definition call the default [[DefineOwnProperty]], hence:
         // from ES5.1 8.12.9 7.a.
         if (descriptor.configurablePresent() && descriptor.configurable())
-            return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property.");
+            return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeConfigurabilityError));
         // from ES5.1 8.12.9 7.b.
         if (descriptor.enumerablePresent() && descriptor.enumerable())
-            return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property.");
+            return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeEnumerabilityError));
 
         // a. If the [[Value]] field of Desc is absent, then
         // a.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", Desc, and Throw as arguments.
         if (descriptor.isAccessorDescriptor())
-            return reject(exec, throwException, UnconfigurablePropertyChangeAccessMechanismError);
+            return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError));
         // from ES5.1 8.12.9 10.a.
         if (!array->isLengthWritable() && descriptor.writablePresent() && descriptor.writable())
-            return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property.");
+            return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeWritabilityError));
         // This descriptor is either just making length read-only, or changing nothing!
         if (!descriptor.value()) {
             if (descriptor.writablePresent())
@@ -174,7 +175,7 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName
         // f.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments.
         // g. Reject if oldLenDesc.[[Writable]] is false.
         if (!array->isLengthWritable())
-            return reject(exec, throwException, "Attempting to change value of a readonly property.");
+            return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyChangeError));
         
         // h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
         // i. Else,
@@ -187,7 +188,9 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName
         // l.i. Set oldLen to oldLen – 1.
         // l.ii. Let deleteSucceeded be the result of calling the [[Delete]] internal method of A passing ToString(oldLen) and false as arguments.
         // l.iii. If deleteSucceeded is false, then
-        if (!array->setLength(exec, newLen, throwException)) {
+        bool success = array->setLength(exec, newLen, throwException);
+        ASSERT(!scope.exception() || !success);
+        if (!success) {
             // 1. Set newLenDesc.[[Value] to oldLen+1.
             // 2. If newWritable is false, set newLenDesc.[[Writable] to false.
             // 3. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and false as arguments.
@@ -209,21 +212,23 @@ bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName
 
     // 4. Else if P is an array index (15.4), then
     // a. Let index be ToUint32(P).
-    if (Optional<uint32_t> optionalIndex = parseIndex(propertyName)) {
+    if (std::optional<uint32_t> optionalIndex = parseIndex(propertyName)) {
         // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false.
         uint32_t index = optionalIndex.value();
         // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case.
         if (index >= array->length() && !array->isLengthWritable())
-            return reject(exec, throwException, "Attempting to define numeric property on array with non-writable length property.");
+            return typeError(exec, scope, throwException, ASCIILiteral("Attempting to define numeric property on array with non-writable length property."));
         // c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing P, Desc, and false as arguments.
         // d. Reject if succeeded is false.
         // e. If index >= oldLen
         // e.i. Set oldLenDesc.[[Value]] to index + 1.
         // e.ii. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", oldLenDesc, and false as arguments. This call will always return true.
         // f. Return true.
+        scope.release();
         return array->defineOwnIndexedProperty(exec, index, descriptor, throwException);
     }
 
+    scope.release();
     return array->JSObject::defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException);
 }
 
@@ -247,18 +252,23 @@ bool JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSVa
 
     JSArray* thisObject = jsCast<JSArray*>(cell);
 
-    if (UNLIKELY(isThisValueAltered(slot, thisObject)))
+    if (UNLIKELY(isThisValueAltered(slot, thisObject))) {
+        scope.release();
         return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
+    }
 
     if (propertyName == exec->propertyNames().length) {
         unsigned newLength = value.toUInt32(exec);
+        RETURN_IF_EXCEPTION(scope, false);
         if (value.toNumber(exec) != static_cast<double>(newLength)) {
             throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
             return false;
         }
+        scope.release();
         return thisObject->setLength(exec, newLength, slot.isStrictMode());
     }
 
+    scope.release();
     return JSObject::put(thisObject, exec, propertyName, value, slot);
 }
 
@@ -290,7 +300,7 @@ void JSArray::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, Pro
 }
 
 // This method makes room in the vector, but leaves the new space for count slots uncleared.
-bool JSArray::unshiftCountSlowCase(VM& vm, DeferGC&, bool addToFront, unsigned count)
+bool JSArray::unshiftCountSlowCase(const AbstractLocker&, VM& vm, DeferGC&, bool addToFront, unsigned count)
 {
     ArrayStorage* storage = ensureArrayStorage(vm);
     Butterfly* butterfly = storage->butterfly();
@@ -339,7 +349,7 @@ bool JSArray::unshiftCountSlowCase(VM& vm, DeferGC&, bool addToFront, unsigned c
         allocatedNewStorage = false;
     } else {
         size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity));
-        newAllocBase = vm.heap.tryAllocateAuxiliary(this, newSize);
+        newAllocBase = vm.auxiliarySpace.tryAllocate(newSize);
         if (!newAllocBase)
             return false;
         newStorageCapacity = desiredCapacity;
@@ -391,13 +401,16 @@ bool JSArray::unshiftCountSlowCase(VM& vm, DeferGC&, bool addToFront, unsigned c
     newButterfly->arrayStorage()->setVectorLength(newVectorLength);
     newButterfly->arrayStorage()->m_indexBias = newIndexBias;
     
-    setButterflyWithoutChangingStructure(vm, newButterfly);
+    setButterfly(vm, newButterfly);
 
     return true;
 }
 
 bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, bool throwException, ArrayStorage* storage)
 {
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
     unsigned length = storage->length();
     
     // If the length is read only then we enter sparse mode, so should enter the following 'if'.
@@ -406,7 +419,7 @@ bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, boo
     if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
         // Fail if the length is not writable.
         if (map->lengthIsReadOnly())
-            return reject(exec, throwException, StrictModeReadonlyPropertyWriteError);
+            return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyWriteError));
 
         if (newLength < length) {
             // Copy any keys we might be interested in into a vector.
@@ -431,7 +444,7 @@ bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, boo
                     ASSERT(it != map->notFound());
                     if (it->value.attributes & DontDelete) {
                         storage->setLength(index + 1);
-                        return reject(exec, throwException, "Unable to delete property.");
+                        return typeError(exec, scope, throwException, ASCIILiteral(UnableToDeletePropertyError));
                     }
                     map->remove(it);
                 }
@@ -484,7 +497,15 @@ bool JSArray::appendMemcpy(ExecState* exec, VM& vm, unsigned startIndex, JSC::JS
         return false;
 
     unsigned otherLength = otherArray->length();
-    unsigned newLength = startIndex + otherLength;
+    Checked<unsigned, RecordOverflow> checkedNewLength = startIndex;
+    checkedNewLength += otherLength;
+
+    unsigned newLength;
+    if (checkedNewLength.safeGet(newLength) == CheckedState::DidOverflow) {
+        throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
+        return false;
+    }
+
     if (newLength >= MIN_SPARSE_ARRAY_INDEX)
         return false;
 
@@ -513,6 +534,7 @@ bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException
         if (!newLength)
             return true;
         if (newLength >= MIN_SPARSE_ARRAY_INDEX) {
+            scope.release();
             return setLengthWithArrayStorage(
                 exec, newLength, throwException,
                 ensureArrayStorage(vm));
@@ -529,6 +551,7 @@ bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException
         if (newLength >= MAX_ARRAY_INDEX // This case ensures that we can do fast push.
             || (newLength >= MIN_SPARSE_ARRAY_INDEX
                 && !isDenseEnoughForVector(newLength, countElements()))) {
+            scope.release();
             return setLengthWithArrayStorage(
                 exec, newLength, throwException,
                 ensureArrayStorage(vm));
@@ -561,6 +584,7 @@ bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException
         
     case ArrayWithArrayStorage:
     case ArrayWithSlowPutArrayStorage:
+        scope.release();
         return setLengthWithArrayStorage(exec, newLength, throwException, arrayStorage());
         
     default:
@@ -625,7 +649,7 @@ JSValue JSArray::pop(ExecState* exec)
         unsigned length = storage->length();
         if (!length) {
             if (!isLengthWritable())
-                throwTypeError(exec, scope, StrictModeReadonlyPropertyWriteError);
+                throwTypeError(exec, scope, ASCIILiteral(ReadonlyPropertyWriteError));
             return jsUndefined();
         }
 
@@ -655,11 +679,14 @@ JSValue JSArray::pop(ExecState* exec)
     JSValue element = get(exec, index);
     RETURN_IF_EXCEPTION(scope, JSValue());
     // Call the [[Delete]] internal method of O with arguments indx and true.
-    if (!deletePropertyByIndex(this, exec, index)) {
-        throwTypeError(exec, scope, ASCIILiteral("Unable to delete property."));
+    bool success = deletePropertyByIndex(this, exec, index);
+    RETURN_IF_EXCEPTION(scope, JSValue());
+    if (!success) {
+        throwTypeError(exec, scope, ASCIILiteral(UnableToDeletePropertyError));
         return jsUndefined();
     }
     // Call the [[Put]] internal method of O with arguments "length", indx, and true.
+    scope.release();
     setLength(exec, index, true);
     // Return element.
     return element;
@@ -683,6 +710,7 @@ void JSArray::push(ExecState* exec, JSValue value)
         
     case ArrayWithUndecided: {
         convertUndecidedForValue(vm, value);
+        scope.release();
         push(exec, value);
         return;
     }
@@ -690,6 +718,7 @@ void JSArray::push(ExecState* exec, JSValue value)
     case ArrayWithInt32: {
         if (!value.isInt32()) {
             convertInt32ForValue(vm, value);
+            scope.release();
             push(exec, value);
             return;
         }
@@ -702,13 +731,14 @@ void JSArray::push(ExecState* exec, JSValue value)
             return;
         }
         
-        if (length > MAX_ARRAY_INDEX) {
+        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
             methodTable(vm)->putByIndex(this, exec, length, value, true);
             if (!scope.exception())
-                throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
+                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
             return;
         }
-        
+
+        scope.release();
         putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, length, value);
         return;
     }
@@ -722,13 +752,14 @@ void JSArray::push(ExecState* exec, JSValue value)
             return;
         }
         
-        if (length > MAX_ARRAY_INDEX) {
+        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
             methodTable(vm)->putByIndex(this, exec, length, value, true);
             if (!scope.exception())
-                throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
+                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
             return;
         }
-        
+
+        scope.release();
         putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value);
         return;
     }
@@ -736,12 +767,14 @@ void JSArray::push(ExecState* exec, JSValue value)
     case ArrayWithDouble: {
         if (!value.isNumber()) {
             convertDoubleToContiguous(vm);
+            scope.release();
             push(exec, value);
             return;
         }
         double valueAsDouble = value.asNumber();
         if (valueAsDouble != valueAsDouble) {
             convertDoubleToContiguous(vm);
+            scope.release();
             push(exec, value);
             return;
         }
@@ -754,23 +787,26 @@ void JSArray::push(ExecState* exec, JSValue value)
             return;
         }
         
-        if (length > MAX_ARRAY_INDEX) {
+        if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
             methodTable(vm)->putByIndex(this, exec, length, value, true);
             if (!scope.exception())
-                throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
+                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
             return;
         }
-        
+
+        scope.release();
         putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, length, value);
-        break;
+        return;
     }
         
     case ArrayWithSlowPutArrayStorage: {
         unsigned oldLength = length();
         bool putResult = false;
         if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) {
-            if (!scope.exception() && oldLength < 0xFFFFFFFFu)
+            if (!scope.exception() && oldLength < 0xFFFFFFFFu) {
+                scope.release();
                 setLength(exec, oldLength + 1, true);
+            }
             return;
         }
         FALLTHROUGH;
@@ -789,17 +825,18 @@ void JSArray::push(ExecState* exec, JSValue value)
         }
 
         // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
-        if (storage->length() > MAX_ARRAY_INDEX) {
+        if (UNLIKELY(storage->length() > MAX_ARRAY_INDEX)) {
             methodTable(vm)->putByIndex(this, exec, storage->length(), value, true);
             // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
             if (!scope.exception())
-                throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
+                throwException(exec, scope, createRangeError(exec, ASCIILiteral(LengthExceededTheMaximumArrayLengthError)));
             return;
         }
 
         // Handled the same as putIndex.
+        scope.release();
         putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage);
-        break;
+        return;
     }
         
     default:
@@ -819,8 +856,8 @@ JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count
             return nullptr;
 
         Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType);
-        JSArray* resultArray = JSArray::tryCreateUninitialized(vm, resultStructure, count);
-        if (!resultArray)
+        JSArray* resultArray = JSArray::tryCreateForInitializationPrivate(vm, resultStructure, count);
+        if (UNLIKELY(!resultArray))
             return nullptr;
 
         auto& resultButterfly = *resultArray->butterfly();
@@ -865,6 +902,9 @@ bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned c
     if (startIndex >= vectorLength)
         return true;
     
+    DisallowGC disallowGC;
+    auto locker = holdLock(*this);
+    
     if (startIndex + count > vectorLength)
         count = vectorLength - startIndex;
     
@@ -903,7 +943,7 @@ bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned c
         // the start of the Butterfly, which needs to point at the first indexed property in the used
         // portion of the vector.
         Butterfly* butterfly = m_butterfly.get()->shift(structure(), count);
-        m_butterfly.setWithoutBarrier(butterfly);
+        setButterfly(vm, butterfly);
         storage = butterfly->arrayStorage();
         storage->m_indexBias += count;
 
@@ -991,6 +1031,11 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startInde
             butterfly->contiguous()[i].clear();
         
         butterfly->setPublicLength(oldLength - count);
+
+        // Our memmoving of values around in the array could have concealed some of them from
+        // the collector. Let's make sure that the collector scans this object again.
+        vm.heap.writeBarrier(this);
+        
         return true;
     }
         
@@ -1061,16 +1106,17 @@ bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex,
     // Need to have GC deferred around the unshiftCountSlowCase(), since that leaves the butterfly in
     // a weird state: some parts of it will be left uninitialized, which we will fill in here.
     DeferGC deferGC(vm.heap);
+    auto locker = holdLock(*this);
     
     if (moveFront && storage->m_indexBias >= count) {
         Butterfly* newButterfly = storage->butterfly()->unshift(structure(), count);
         storage = newButterfly->arrayStorage();
         storage->m_indexBias -= count;
         storage->setVectorLength(vectorLength + count);
-        setButterflyWithoutChangingStructure(vm, newButterfly);
+        setButterfly(vm, newButterfly);
     } else if (!moveFront && vectorLength - length >= count)
         storage = storage->butterfly()->arrayStorage();
-    else if (unshiftCountSlowCase(vm, deferGC, moveFront, count))
+    else if (unshiftCountSlowCase(locker, vm, deferGC, moveFront, count))
         storage = arrayStorage();
     else {
         throwOutOfMemoryError(exec, scope);
@@ -1088,6 +1134,7 @@ bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex,
 
     for (unsigned i = 0; i < count; i++)
         vector[i + startIndex].clear();
+    
     return true;
 }
 
@@ -1110,8 +1157,10 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
         
         // We may have to walk the entire array to do the unshift. We're willing to do so
         // only if it's not horribly slow.
-        if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
+        if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) {
+            scope.release();
             return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
+        }
         
         if (!ensureLength(vm, oldLength + count)) {
             throwOutOfMemoryError(exec, scope);
@@ -1123,8 +1172,10 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
         // through shifting and then realize we should have been in ArrayStorage mode.
         for (unsigned i = oldLength; i-- > startIndex;) {
             JSValue v = butterfly->contiguous()[i].get();
-            if (UNLIKELY(!v))
+            if (UNLIKELY(!v)) {
+                scope.release();
                 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
+            }
         }
 
         for (unsigned i = oldLength; i-- > startIndex;) {
@@ -1133,6 +1184,10 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
             butterfly->contiguous()[i + count].setWithoutWriteBarrier(v);
         }
         
+        // Our memmoving of values around in the array could have concealed some of them from
+        // the collector. Let's make sure that the collector scans this object again.
+        vm.heap.writeBarrier(this);
+        
         // NOTE: we're leaving being garbage in the part of the array that we shifted out
         // of. This is fine because the caller is required to store over that area, and
         // in contiguous mode storing into a hole is guaranteed to behave exactly the same
@@ -1146,8 +1201,10 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
         
         // We may have to walk the entire array to do the unshift. We're willing to do so
         // only if it's not horribly slow.
-        if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
+        if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) {
+            scope.release();
             return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
+        }
         
         if (!ensureLength(vm, oldLength + count)) {
             throwOutOfMemoryError(exec, scope);
@@ -1159,8 +1216,10 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
         // through shifting and then realize we should have been in ArrayStorage mode.
         for (unsigned i = oldLength; i-- > startIndex;) {
             double v = butterfly->contiguousDouble()[i];
-            if (UNLIKELY(v != v))
+            if (UNLIKELY(v != v)) {
+                scope.release();
                 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
+            }
         }
 
         for (unsigned i = oldLength; i-- > startIndex;) {
@@ -1179,6 +1238,7 @@ bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startInd
         
     case ArrayWithArrayStorage:
     case ArrayWithSlowPutArrayStorage:
+        scope.release();
         return unshiftCountWithArrayStorage(exec, startIndex, count, arrayStorage());
         
     default: