diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 9589f581ceacb..6bb9343c185a6 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -4406,7 +4406,7 @@ static bool DumpHeap(JSContext* cx, unsigned argc, Value* vp) { FILE* dumpFile = stdout; auto closeFile = mozilla::MakeScopeExit([&dumpFile] { - if (dumpFile != stdout) { + if (dumpFile && dumpFile != stdout) { fclose(dumpFile); } }); diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index e1ae5436b8d18..70774f7e0cc5d 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -2345,25 +2345,6 @@ bool CompartmentCheckTracer::edgeIsInCrossCompartmentMap(JS::GCCellPtr dst) { InCrossCompartmentMap(runtime(), static_cast(src), dst); } -static bool IsPartiallyInitializedObject(Cell* cell) { - if (!cell->is()) { - return false; - } - - JSObject* obj = cell->as(); - if (!obj->is()) { - return false; - } - - NativeObject* nobj = &obj->as(); - - // Check for failed allocation of dynamic slots in - // NativeObject::allocateInitialSlots. - size_t nDynamicSlots = NativeObject::calculateDynamicSlots( - nobj->numFixedSlots(), nobj->slotSpan(), nobj->getClass()); - return nDynamicSlots != 0 && !nobj->hasDynamicSlots(); -} - void GCRuntime::checkForCompartmentMismatches() { JSContext* cx = rt->mainContextFromOwnThread(); if (cx->disableStrictProxyCheckingCount) { @@ -2377,12 +2358,6 @@ void GCRuntime::checkForCompartmentMismatches() { for (auto thingKind : AllAllocKinds()) { for (auto i = zone->cellIterUnsafe(thingKind, empty); !i.done(); i.next()) { - // We may encounter partially initialized objects. These are unreachable - // and it's safe to ignore them. - if (IsPartiallyInitializedObject(i.getCell())) { - continue; - } - trc.src = i.getCell(); trc.srcKind = MapAllocToTraceKind(thingKind); trc.compartment = MapGCThingTyped( diff --git a/js/src/jit-test/tests/gc/bug-1877406.js b/js/src/jit-test/tests/gc/bug-1877406.js new file mode 100644 index 0000000000000..bcb26ed0621b5 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1877406.js @@ -0,0 +1,7 @@ +// |jit-test| skip-if: !('oomTest' in this); --fuzzing-safe + +oomTest(Debugger); +oomTest(Debugger); +async function* f() {} +f().return(); +dumpHeap(f); diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 02c69e1b824c7..9c9003a2ba0b3 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -666,6 +666,13 @@ GlobalObject* GlobalObject::new_(JSContext* cx, const JSClass* clasp, return nullptr; } + // Create a shape for plain objects with zero slots. This is required to be + // present in case allocating dynamic slots for objects fails, so we can + // leave a valid object in the heap. + if (!createPlainObjectShapeWithDefaultProto(cx, gc::AllocKind::OBJECT0)) { + return nullptr; + } + realm->clearInitializingGlobal(); if (hookOption == JS::FireOnNewGlobalHook) { JS_FireOnNewGlobalObject(cx, global); diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index a2d44a4ff4c66..92dec6698f92f 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -1086,6 +1086,14 @@ class GlobalObject : public NativeObject { static SharedShape* createPlainObjectShapeWithDefaultProto( JSContext* cx, gc::AllocKind kind); + static SharedShape* getEmptyPlainObjectShape(JSContext* cx) { + const PlainObjectSlotsKind kind = PlainObjectSlotsKind::Slots0; + SharedShape* shape = + cx->global()->data().plainObjectShapesWithDefaultProto[kind]; + MOZ_ASSERT(shape); // This is created on initialization. + return shape; + } + static SharedShape* getFunctionShapeWithDefaultProto(JSContext* cx, bool extended) { GlobalObjectData& data = cx->global()->data(); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index 1bb2e26c97e92..ebfcdbe2e54dc 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -391,10 +391,11 @@ bool NativeObject::growSlotsForNewSlot(JSContext* cx, uint32_t numFixed, bool NativeObject::allocateInitialSlots(JSContext* cx, uint32_t capacity) { uint32_t count = ObjectSlots::allocCount(capacity); HeapSlot* allocation = AllocateCellBuffer(cx, this, count); - if (!allocation) { - // The new object will be unreachable, but we still have to make it safe - // for finalization. Also we must check for it during GC compartment - // checks (see IsPartiallyInitializedObject). + if (MOZ_UNLIKELY(!allocation)) { + // The new object will be unreachable, but we have to make it safe for + // finalization. It can also be observed with dumpHeap(). + // Give it a dummy shape that has no dynamic slots. + setShape(GlobalObject::getEmptyPlainObjectShape(cx)); initEmptyDynamicSlots(); return false; }