diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 44b3a7d1b73..7e88e1cd3e5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8767,7 +8767,7 @@ Parser::arrayInitializer(YieldHandling yieldHandling) uint32_t index = 0; TokenStream::Modifier modifier = TokenStream::Operand; for (; ; index++) { - if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + if (index == NativeObject::NELEMENTS_LIMIT) { report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG); return null(); } diff --git a/js/src/jit-test/tests/arrays/dense-from-sparse.js b/js/src/jit-test/tests/arrays/dense-from-sparse.js deleted file mode 100644 index c82ba93b6b6..00000000000 --- a/js/src/jit-test/tests/arrays/dense-from-sparse.js +++ /dev/null @@ -1,39 +0,0 @@ -// Appending elements to a dense array should make the array sparse when the -// length exceeds the limit, instead of throwing OOM error. - -function test() { - const MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1; - const VALUES_PER_HEADER = 2; - const MAX_DENSE_ELEMENTS_COUNT = MAX_DENSE_ELEMENTS_ALLOCATION - VALUES_PER_HEADER; - const SPARSE_DENSITY_RATIO = 8; - const MIN_DENSE = MAX_DENSE_ELEMENTS_COUNT / SPARSE_DENSITY_RATIO; - const MARGIN = 16; - - let a = []; - // Fill the beginning of array to make it keep dense until length exceeds - // MAX_DENSE_ELEMENTS_COUNT. - for (let i = 0; i < MIN_DENSE; i++) - a[i] = i; - - // Skip from MIN_DENSE to MAX_DENSE_ELEMENTS_COUNT - MARGIN, to reduce the - // time taken by test. - - // Fill the ending of array to make it sparse at MAX_DENSE_ELEMENTS_COUNT. - for (let i = MAX_DENSE_ELEMENTS_COUNT - MARGIN; i < MAX_DENSE_ELEMENTS_COUNT + MARGIN; i++) - a[i] = i; - - // Make sure the last element is defined. - assertEq(a.length, MAX_DENSE_ELEMENTS_COUNT + MARGIN); - assertEq(a[a.length - 1], MAX_DENSE_ELEMENTS_COUNT + MARGIN - 1); - - // Make sure elements around MAX_DENSE_ELEMENTS_COUNT are also defined. - assertEq(a[MAX_DENSE_ELEMENTS_COUNT - 1], MAX_DENSE_ELEMENTS_COUNT - 1); - assertEq(a[MAX_DENSE_ELEMENTS_COUNT], MAX_DENSE_ELEMENTS_COUNT); - assertEq(a[MAX_DENSE_ELEMENTS_COUNT + 1], MAX_DENSE_ELEMENTS_COUNT + 1); -} - -var config = getBuildConfiguration(); -// Takes too long time on debug build. -if (!config.debug) { - test(); -} diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index b4e80fd15a3..6ba71b5dbfe 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4303,7 +4303,7 @@ CodeGenerator::visitNewArray(LNewArray* lir) JSObject* templateObject = lir->mir()->templateObject(); DebugOnly length = lir->mir()->length(); - MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT); + MOZ_ASSERT(length < NativeObject::NELEMENTS_LIMIT); if (lir->mir()->shouldUseVM()) { visitNewArrayCallVM(lir); @@ -6946,7 +6946,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) key, &callStub); // Update initialized length. The capacity guard above ensures this won't overflow, - // due to MAX_DENSE_ELEMENTS_COUNT. + // due to NELEMENTS_LIMIT. masm.bumpKey(&key, 1); masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength())); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index d1dd269ea9f..92a2cdc66b4 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -442,9 +442,8 @@ IonBuilder::inlineArray(CallInfo& callInfo) // Negative lengths generate a RangeError, unhandled by the inline path. initLength = arg->constantValue().toInt32(); - if (initLength > NativeObject::MAX_DENSE_ELEMENTS_COUNT) + if (initLength >= NativeObject::NELEMENTS_LIMIT) return InliningStatus_NotInlined; - MOZ_ASSERT(initLength <= INT32_MAX); // Make sure initLength matches the template object's length. This is // not guaranteed to be the case, for instance if we're inlining the diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 19674912b61..877b85325fa 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -4116,7 +4116,7 @@ MNewArray::shouldUseVM() const return !templateObject()->as().hasInlineElements(); } - MOZ_ASSERT(length() <= NativeObject::MAX_DENSE_ELEMENTS_COUNT); + MOZ_ASSERT(length() < NativeObject::NELEMENTS_LIMIT); size_t arraySlots = gc::GetGCKindSlots(templateObject()->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER; diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 66db6f78cc3..25dda5d0de1 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1734,7 +1734,7 @@ MArrayLength::computeRange(TempAllocator& alloc) void MInitializedLength::computeRange(TempAllocator& alloc) { - setRange(Range::NewUInt32Range(alloc, 0, NativeObject::MAX_DENSE_ELEMENTS_COUNT)); + setRange(Range::NewUInt32Range(alloc, 0, NativeObject::NELEMENTS_LIMIT)); } void diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 7115d84bebb..5cf8e9eca2a 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -172,7 +172,7 @@ GetGCObjectKind(size_t numSlots) /* As for GetGCObjectKind, but for dense array allocation. */ static inline AllocKind -GetGCArrayKind(size_t numElements) +GetGCArrayKind(size_t numSlots) { /* * Dense arrays can use their fixed slots to hold their elements array @@ -181,12 +181,9 @@ GetGCArrayKind(size_t numElements) * unused. */ JS_STATIC_ASSERT(ObjectElements::VALUES_PER_HEADER == 2); - if (numElements > NativeObject::MAX_DENSE_ELEMENTS_COUNT || - numElements + ObjectElements::VALUES_PER_HEADER >= SLOTS_TO_THING_KIND_LIMIT) - { + if (numSlots > NativeObject::NELEMENTS_LIMIT || numSlots + 2 >= SLOTS_TO_THING_KIND_LIMIT) return AllocKind::OBJECT2; - } - return slotsToThingKind[numElements + ObjectElements::VALUES_PER_HEADER]; + return slotsToThingKind[numSlots + 2]; } static inline AllocKind diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index f4fba5d4aa1..3dfadaf2cdd 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -791,23 +791,23 @@ NewObjectWithGroup(ExclusiveContext* cx, HandleObjectGroup group, } /* - * As for gc::GetGCObjectKind, where numElements is a guess at the final size of + * As for gc::GetGCObjectKind, where numSlots is a guess at the final size of * the object, zero if the final size is unknown. This should only be used for * objects that do not require any fixed slots. */ static inline gc::AllocKind -GuessObjectGCKind(size_t numElements) +GuessObjectGCKind(size_t numSlots) { - if (numElements) - return gc::GetGCObjectKind(numElements); + if (numSlots) + return gc::GetGCObjectKind(numSlots); return gc::AllocKind::OBJECT4; } static inline gc::AllocKind -GuessArrayGCKind(size_t numElements) +GuessArrayGCKind(size_t numSlots) { - if (numElements) - return gc::GetGCArrayKind(numElements); + if (numSlots) + return gc::GetGCArrayKind(numSlots); return gc::AllocKind::OBJECT8; } diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index 4123b877324..ab68fdc4389 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -6,8 +6,8 @@ #include "vm/NativeObject-inl.h" -#include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" +#include "mozilla/CheckedInt.h" #include "jswatchpoint.h" @@ -398,7 +398,7 @@ NativeObject::growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCou * throttled well before the slot capacity can overflow. */ NativeObject::slotsSizeMustNotOverflow(); - MOZ_ASSERT(newCount <= MAX_SLOTS_COUNT); + MOZ_ASSERT(newCount < NELEMENTS_LIMIT); if (!oldCount) { MOZ_ASSERT(!slots_); @@ -520,7 +520,7 @@ NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElemen uint32_t cap = getDenseCapacity(); MOZ_ASSERT(requiredCapacity >= cap); - if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT) + if (requiredCapacity >= NELEMENTS_LIMIT) return true; uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; @@ -594,7 +594,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) return DenseElementResult::Incomplete; - if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT) + if (newInitializedLength >= NELEMENTS_LIMIT) return DenseElementResult::Incomplete; /* @@ -672,7 +672,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy // in one should generally be matched by the other. /* static */ uint32_t -NativeObject::goodElementsAllocationAmount(uint32_t reqAllocated, uint32_t length = 0) +NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0) { // Handle "small" requests primarily by doubling. const uint32_t Mebi = 1 << 20; @@ -712,19 +712,18 @@ NativeObject::goodElementsAllocationAmount(uint32_t reqAllocated, uint32_t lengt // print('0x' + (n * (1 << 20)).toString(16) + ', '); // n = Math.ceil(n * 1.125); // } - // print('MAX_DENSE_ELEMENTS_ALLOCATION'); + // print('NELEMENTS_LIMIT - 1'); // - // Dense array elements can't exceed |MAX_DENSE_ELEMENTS_ALLOCATION|, so - // |MAX_DENSE_ELEMENTS_ALLOCATION| is the biggest allowed length. + // Dense array elements can't exceed |NELEMENTS_LIMIT|, so + // |NELEMENTS_LIMIT - 1| is the biggest allowed length. static const uint32_t BigBuckets[] = { 0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000, 0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, 0x1100000, 0x1400000, 0x1700000, 0x1a00000, 0x1e00000, 0x2200000, 0x2700000, 0x2c00000, 0x3200000, 0x3900000, 0x4100000, 0x4a00000, 0x5400000, 0x5f00000, 0x6b00000, 0x7900000, 0x8900000, 0x9b00000, 0xaf00000, 0xc500000, - 0xde00000, 0xfa00000, MAX_DENSE_ELEMENTS_ALLOCATION + 0xde00000, 0xfa00000, NELEMENTS_LIMIT - 1 }; - MOZ_ASSERT(BigBuckets[ArrayLength(BigBuckets) - 2] <= MAX_DENSE_ELEMENTS_ALLOCATION); // Pick the first bucket that'll fit |reqAllocated|. for (uint32_t b : BigBuckets) { @@ -733,17 +732,12 @@ NativeObject::goodElementsAllocationAmount(uint32_t reqAllocated, uint32_t lengt } // Otherwise, return the maximum bucket size. - return MAX_DENSE_ELEMENTS_ALLOCATION; + return NELEMENTS_LIMIT - 1; } bool NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity) { - if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) { - ReportOutOfMemory(cx); - return false; - } - MOZ_ASSERT(nonProxyIsExtensible()); MOZ_ASSERT(canHaveNonEmptyElements()); if (denseElementsAreCopyOnWrite()) @@ -752,9 +746,17 @@ NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity) uint32_t oldCapacity = getDenseCapacity(); MOZ_ASSERT(oldCapacity < reqCapacity); - uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER; - uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER; - MOZ_ASSERT(oldAllocated <= MAX_DENSE_ELEMENTS_ALLOCATION); + using mozilla::CheckedInt; + + CheckedInt checkedOldAllocated = + CheckedInt(oldCapacity) + ObjectElements::VALUES_PER_HEADER; + CheckedInt checkedReqAllocated = + CheckedInt(reqCapacity) + ObjectElements::VALUES_PER_HEADER; + if (!checkedOldAllocated.isValid() || !checkedReqAllocated.isValid()) + return false; + + uint32_t reqAllocated = checkedReqAllocated.value(); + uint32_t oldAllocated = checkedOldAllocated.value(); uint32_t newAllocated; if (is() && !as().lengthIsWritable()) { @@ -764,15 +766,14 @@ NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity) // enforces this requirement. newAllocated = reqAllocated; } else { - newAllocated = goodElementsAllocationAmount(reqAllocated, getElementsHeader()->length); + newAllocated = goodAllocated(reqAllocated, getElementsHeader()->length); } uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity); - // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become - // sparse. - MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT); + if (newCapacity >= NELEMENTS_LIMIT) + return false; uint32_t initlen = getDenseInitializedLength(); @@ -813,13 +814,13 @@ NativeObject::shrinkElements(ExclusiveContext* cx, uint32_t reqCapacity) uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER; uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER; - uint32_t newAllocated = goodElementsAllocationAmount(reqAllocated); + uint32_t newAllocated = goodAllocated(reqAllocated); if (newAllocated == oldAllocated) return; // Leave elements at its old size. MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER); uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; - MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT); + MOZ_ASSERT(newCapacity < NELEMENTS_LIMIT); HeapSlot* oldHeaderSlots = reinterpret_cast(getElementsHeader()); HeapSlot* newHeaderSlots = ReallocateObjectBuffer(cx, this, oldHeaderSlots, @@ -844,12 +845,12 @@ NativeObject::CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj) uint32_t initlen = obj->getDenseInitializedLength(); uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER; - uint32_t newAllocated = goodElementsAllocationAmount(allocated); + uint32_t newAllocated = goodAllocated(allocated); uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; - // COPY_ON_WRITE flags is set only if obj is a dense array. - MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT); + if (newCapacity >= NELEMENTS_LIMIT) + return false; JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject()); diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 413eca55631..3eb575ecf1e 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -556,13 +556,8 @@ class NativeObject : public JSObject bool shadowingShapeChange(ExclusiveContext* cx, const Shape& shape); bool clearFlag(ExclusiveContext* cx, BaseShape::Flag flag); - // The maximum number of slots in an object. - // |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow - // int32_t (see slotsSizeMustNotOverflow). - static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1; - static void slotsSizeMustNotOverflow() { - static_assert(NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value), + static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value), "every caller of this method requires that a slot " "number (or slot count) count multiplied by " "sizeof(Value) can't overflow uint32_t (and sometimes " @@ -887,19 +882,11 @@ class NativeObject : public JSObject /* Elements accessors. */ - // The maximum size, in sizeof(Value), of the allocation used for an - // object's dense elements. (This includes space used to store an - // ObjectElements instance.) - // |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow - // int32_t (see elementsSizeMustNotOverflow). - static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1; - - // The maximum number of usable dense elements in an object. - static const uint32_t MAX_DENSE_ELEMENTS_COUNT = - MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER; + /* Upper bound on the number of elements in an object. */ + static const uint32_t NELEMENTS_LIMIT = JS_BIT(28); static void elementsSizeMustNotOverflow() { - static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value), + static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value), "every caller of this method require that an element " "count multiplied by sizeof(Value) can't overflow " "uint32_t (and sometimes int32_t ,too)"); @@ -917,7 +904,7 @@ class NativeObject : public JSObject return true; } - static uint32_t goodElementsAllocationAmount(uint32_t n, uint32_t length); + static uint32_t goodAllocated(uint32_t n, uint32_t length); bool growElements(ExclusiveContext* cx, uint32_t newcap); void shrinkElements(ExclusiveContext* cx, uint32_t cap); void setDynamicElements(ObjectElements* header) {