michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsapi-tests/tests.h" michael@0: michael@0: static const unsigned BufferSize = 20; michael@0: static unsigned FinalizeCalls = 0; michael@0: static JSFinalizeStatus StatusBuffer[BufferSize]; michael@0: static bool IsCompartmentGCBuffer[BufferSize]; michael@0: michael@0: BEGIN_TEST(testGCFinalizeCallback) michael@0: { michael@0: JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); michael@0: JS_SetFinalizeCallback(rt, FinalizeCallback); michael@0: michael@0: /* Full GC, non-incremental. */ michael@0: FinalizeCalls = 0; michael@0: JS_GC(rt); michael@0: CHECK(rt->gcIsFull); michael@0: CHECK(checkSingleGroup()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(false)); michael@0: michael@0: /* Full GC, incremental. */ michael@0: FinalizeCalls = 0; michael@0: JS::PrepareForFullGC(rt); michael@0: JS::IncrementalGC(rt, JS::gcreason::API, 1000000); michael@0: CHECK(rt->gcIncrementalState == js::gc::NO_INCREMENTAL); michael@0: CHECK(rt->gcIsFull); michael@0: CHECK(checkMultipleGroups()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(false)); michael@0: michael@0: JS::RootedObject global1(cx, createGlobal()); michael@0: JS::RootedObject global2(cx, createGlobal()); michael@0: JS::RootedObject global3(cx, createGlobal()); michael@0: CHECK(global1); michael@0: CHECK(global2); michael@0: CHECK(global3); michael@0: michael@0: /* Compartment GC, non-incremental, single compartment. */ michael@0: FinalizeCalls = 0; michael@0: JS::PrepareZoneForGC(global1->zone()); michael@0: JS::GCForReason(rt, JS::gcreason::API); michael@0: CHECK(!rt->gcIsFull); michael@0: CHECK(checkSingleGroup()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(true)); michael@0: michael@0: /* Compartment GC, non-incremental, multiple compartments. */ michael@0: FinalizeCalls = 0; michael@0: JS::PrepareZoneForGC(global1->zone()); michael@0: JS::PrepareZoneForGC(global2->zone()); michael@0: JS::PrepareZoneForGC(global3->zone()); michael@0: JS::GCForReason(rt, JS::gcreason::API); michael@0: CHECK(!rt->gcIsFull); michael@0: CHECK(checkSingleGroup()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(true)); michael@0: michael@0: /* Compartment GC, incremental, single compartment. */ michael@0: FinalizeCalls = 0; michael@0: JS::PrepareZoneForGC(global1->zone()); michael@0: JS::IncrementalGC(rt, JS::gcreason::API, 1000000); michael@0: CHECK(rt->gcIncrementalState == js::gc::NO_INCREMENTAL); michael@0: CHECK(!rt->gcIsFull); michael@0: CHECK(checkSingleGroup()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(true)); michael@0: michael@0: /* Compartment GC, incremental, multiple compartments. */ michael@0: FinalizeCalls = 0; michael@0: JS::PrepareZoneForGC(global1->zone()); michael@0: JS::PrepareZoneForGC(global2->zone()); michael@0: JS::PrepareZoneForGC(global3->zone()); michael@0: JS::IncrementalGC(rt, JS::gcreason::API, 1000000); michael@0: CHECK(rt->gcIncrementalState == js::gc::NO_INCREMENTAL); michael@0: CHECK(!rt->gcIsFull); michael@0: CHECK(checkMultipleGroups()); michael@0: CHECK(checkFinalizeStatus()); michael@0: CHECK(checkFinalizeIsCompartmentGC(true)); michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: michael@0: /* Full GC with reset due to new compartment, becoming compartment GC. */ michael@0: michael@0: FinalizeCalls = 0; michael@0: JS_SetGCZeal(cx, 9, 1000000); michael@0: JS::PrepareForFullGC(rt); michael@0: js::GCDebugSlice(rt, true, 1); michael@0: CHECK(rt->gcIncrementalState == js::gc::MARK); michael@0: CHECK(rt->gcIsFull); michael@0: michael@0: JS::RootedObject global4(cx, createGlobal()); michael@0: js::GCDebugSlice(rt, true, 1); michael@0: CHECK(rt->gcIncrementalState == js::gc::NO_INCREMENTAL); michael@0: CHECK(!rt->gcIsFull); michael@0: CHECK(checkMultipleGroups()); michael@0: CHECK(checkFinalizeStatus()); michael@0: michael@0: for (unsigned i = 0; i < FinalizeCalls - 1; ++i) michael@0: CHECK(!IsCompartmentGCBuffer[i]); michael@0: CHECK(IsCompartmentGCBuffer[FinalizeCalls - 1]); michael@0: michael@0: JS_SetGCZeal(cx, 0, 0); michael@0: michael@0: #endif michael@0: michael@0: /* michael@0: * Make some use of the globals here to ensure the compiler doesn't optimize michael@0: * them away in release builds, causing the compartments to be collected and michael@0: * the test to fail. michael@0: */ michael@0: CHECK(JS_IsGlobalObject(global1)); michael@0: CHECK(JS_IsGlobalObject(global2)); michael@0: CHECK(JS_IsGlobalObject(global3)); michael@0: michael@0: JS_SetFinalizeCallback(rt, nullptr); michael@0: return true; michael@0: } michael@0: michael@0: bool checkSingleGroup() michael@0: { michael@0: CHECK(FinalizeCalls < BufferSize); michael@0: CHECK(FinalizeCalls == 3); michael@0: return true; michael@0: } michael@0: michael@0: bool checkMultipleGroups() michael@0: { michael@0: CHECK(FinalizeCalls < BufferSize); michael@0: CHECK(FinalizeCalls % 2 == 1); michael@0: CHECK((FinalizeCalls - 1) / 2 > 1); michael@0: return true; michael@0: } michael@0: michael@0: bool checkFinalizeStatus() michael@0: { michael@0: /* michael@0: * The finalize callback should be called twice for each compartment group michael@0: * finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END, michael@0: * and then once more with JSFINALIZE_COLLECTION_END. michael@0: */ michael@0: michael@0: for (unsigned i = 0; i < FinalizeCalls - 1; i += 2) { michael@0: CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_START); michael@0: CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_END); michael@0: } michael@0: michael@0: CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool checkFinalizeIsCompartmentGC(bool isCompartmentGC) michael@0: { michael@0: for (unsigned i = 0; i < FinalizeCalls; ++i) michael@0: CHECK(IsCompartmentGCBuffer[i] == isCompartmentGC); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC) michael@0: { michael@0: if (FinalizeCalls < BufferSize) { michael@0: StatusBuffer[FinalizeCalls] = status; michael@0: IsCompartmentGCBuffer[FinalizeCalls] = isCompartmentGC; michael@0: } michael@0: ++FinalizeCalls; michael@0: } michael@0: END_TEST(testGCFinalizeCallback)