/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "js/GlobalObject.h" #include "jsapi-tests/tests.h" using namespace js; static const unsigned BufSize = 20; static unsigned FinalizeCalls = 0; static JSFinalizeStatus StatusBuffer[BufSize]; BEGIN_TEST(testGCFinalizeCallback) { AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true); AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true); /* Full GC, non-incremental. */ FinalizeCalls = 0; JS_GC(cx); CHECK(cx->runtime()->gc.isFullGc()); CHECK(checkSingleGroup()); CHECK(checkFinalizeStatus()); /* Full GC, incremental. */ FinalizeCalls = 0; JS::PrepareForFullGC(cx); SliceBudget startBudget(TimeBudget(1000000)); JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, startBudget); while (cx->runtime()->gc.isIncrementalGCInProgress()) { JS::PrepareForFullGC(cx); SliceBudget budget(TimeBudget(1000000)); JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); } CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); CHECK(cx->runtime()->gc.isFullGc()); CHECK(checkMultipleGroups()); CHECK(checkFinalizeStatus()); #ifdef JS_GC_ZEAL // Bug 1377593 - the below tests want to control how many zones are GC'ing, // and some zeal modes will convert them into all-zones GCs. JS_SetGCZeal(cx, 0, 0); #endif JS::RootedObject global1(cx, createTestGlobal()); JS::RootedObject global2(cx, createTestGlobal()); JS::RootedObject global3(cx, createTestGlobal()); CHECK(global1); CHECK(global2); CHECK(global3); /* Zone GC, non-incremental, single zone. */ FinalizeCalls = 0; JS::PrepareZoneForGC(cx, global1->zone()); JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); CHECK(!cx->runtime()->gc.isFullGc()); CHECK(checkSingleGroup()); CHECK(checkFinalizeStatus()); /* Zone GC, non-incremental, multiple zones. */ FinalizeCalls = 0; JS::PrepareZoneForGC(cx, global1->zone()); JS::PrepareZoneForGC(cx, global2->zone()); JS::PrepareZoneForGC(cx, global3->zone()); JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); CHECK(!cx->runtime()->gc.isFullGc()); CHECK(checkSingleGroup()); CHECK(checkFinalizeStatus()); /* Zone GC, incremental, single zone. */ FinalizeCalls = 0; JS::PrepareZoneForGC(cx, global1->zone()); SliceBudget budget(TimeBudget(1000000)); JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget); while (cx->runtime()->gc.isIncrementalGCInProgress()) { JS::PrepareZoneForGC(cx, global1->zone()); budget = SliceBudget(TimeBudget(1000000)); JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); } CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); CHECK(!cx->runtime()->gc.isFullGc()); CHECK(checkSingleGroup()); CHECK(checkFinalizeStatus()); /* Zone GC, incremental, multiple zones. */ FinalizeCalls = 0; JS::PrepareZoneForGC(cx, global1->zone()); JS::PrepareZoneForGC(cx, global2->zone()); JS::PrepareZoneForGC(cx, global3->zone()); budget = SliceBudget(TimeBudget(1000000)); JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget); while (cx->runtime()->gc.isIncrementalGCInProgress()) { JS::PrepareZoneForGC(cx, global1->zone()); JS::PrepareZoneForGC(cx, global2->zone()); JS::PrepareZoneForGC(cx, global3->zone()); budget = SliceBudget(TimeBudget(1000000)); JS::IncrementalGCSlice(cx, JS::GCReason::API, budget); } CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); CHECK(!cx->runtime()->gc.isFullGc()); CHECK(checkMultipleGroups()); CHECK(checkFinalizeStatus()); #ifdef JS_GC_ZEAL /* Full GC with reset due to new zone, becoming zone GC. */ FinalizeCalls = 0; JS_SetGCZeal(cx, 9, 1000000); JS::PrepareForFullGC(cx); budget = SliceBudget(WorkBudget(1)); cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget); CHECK(cx->runtime()->gc.state() == gc::State::Mark); CHECK(cx->runtime()->gc.isFullGc()); JS::RootedObject global4(cx, createTestGlobal()); budget = SliceBudget(WorkBudget(1)); cx->runtime()->gc.debugGCSlice(budget); while (cx->runtime()->gc.isIncrementalGCInProgress()) { cx->runtime()->gc.debugGCSlice(budget); } CHECK(!cx->runtime()->gc.isIncrementalGCInProgress()); CHECK(checkSingleGroup()); CHECK(checkFinalizeStatus()); JS_SetGCZeal(cx, 0, 0); #endif /* * Make some use of the globals here to ensure the compiler doesn't optimize * them away in release builds, causing the zones to be collected and * the test to fail. */ CHECK(JS_IsGlobalObject(global1)); CHECK(JS_IsGlobalObject(global2)); CHECK(JS_IsGlobalObject(global3)); return true; } JSObject* createTestGlobal() { JS::RealmOptions options; return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, options); } virtual bool init() override { if (!JSAPITest::init()) { return false; } JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr); return true; } virtual void uninit() override { JS_RemoveFinalizeCallback(cx, FinalizeCallback); JSAPITest::uninit(); } bool checkSingleGroup() { CHECK(FinalizeCalls < BufSize); CHECK(FinalizeCalls == 4); return true; } bool checkMultipleGroups() { CHECK(FinalizeCalls < BufSize); CHECK(FinalizeCalls % 3 == 1); CHECK((FinalizeCalls - 1) / 3 > 1); return true; } bool checkFinalizeStatus() { /* * The finalize callback should be called twice for each sweep group * finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END, * and then once more with JSFINALIZE_COLLECTION_END. */ for (unsigned i = 0; i < FinalizeCalls - 1; i += 3) { CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_PREPARE); CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_START); CHECK(StatusBuffer[i + 2] == JSFINALIZE_GROUP_END); } CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END); return true; } static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status, void* data) { if (FinalizeCalls < BufSize) { StatusBuffer[FinalizeCalls] = status; } ++FinalizeCalls; } END_TEST(testGCFinalizeCallback)