| /* 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 "jsapi-tests/tests.h" |
| |
| static const unsigned BufferSize = 20; |
| static unsigned FinalizeCalls = 0; |
| static JSFinalizeStatus StatusBuffer[BufferSize]; |
| static bool IsCompartmentGCBuffer[BufferSize]; |
| |
| BEGIN_TEST(testGCFinalizeCallback) |
| { |
| JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); |
| |
| /* Full GC, non-incremental. */ |
| FinalizeCalls = 0; |
| JS_GC(rt); |
| CHECK(rt->gc.isFullGc()); |
| CHECK(checkSingleGroup()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(false)); |
| |
| /* Full GC, incremental. */ |
| FinalizeCalls = 0; |
| JS::PrepareForFullGC(rt); |
| JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); |
| CHECK(!rt->gc.isIncrementalGCInProgress()); |
| CHECK(rt->gc.isFullGc()); |
| CHECK(checkMultipleGroups()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(false)); |
| |
| JS::RootedObject global1(cx, createTestGlobal()); |
| JS::RootedObject global2(cx, createTestGlobal()); |
| JS::RootedObject global3(cx, createTestGlobal()); |
| CHECK(global1); |
| CHECK(global2); |
| CHECK(global3); |
| |
| /* Compartment GC, non-incremental, single compartment. */ |
| FinalizeCalls = 0; |
| JS::PrepareZoneForGC(global1->zone()); |
| JS::GCForReason(rt, GC_NORMAL, JS::gcreason::API); |
| CHECK(!rt->gc.isFullGc()); |
| CHECK(checkSingleGroup()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(true)); |
| |
| /* Compartment GC, non-incremental, multiple compartments. */ |
| FinalizeCalls = 0; |
| JS::PrepareZoneForGC(global1->zone()); |
| JS::PrepareZoneForGC(global2->zone()); |
| JS::PrepareZoneForGC(global3->zone()); |
| JS::GCForReason(rt, GC_NORMAL, JS::gcreason::API); |
| CHECK(!rt->gc.isFullGc()); |
| CHECK(checkSingleGroup()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(true)); |
| |
| /* Compartment GC, incremental, single compartment. */ |
| FinalizeCalls = 0; |
| JS::PrepareZoneForGC(global1->zone()); |
| JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); |
| CHECK(!rt->gc.isIncrementalGCInProgress()); |
| CHECK(!rt->gc.isFullGc()); |
| CHECK(checkSingleGroup()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(true)); |
| |
| /* Compartment GC, incremental, multiple compartments. */ |
| FinalizeCalls = 0; |
| JS::PrepareZoneForGC(global1->zone()); |
| JS::PrepareZoneForGC(global2->zone()); |
| JS::PrepareZoneForGC(global3->zone()); |
| JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000); |
| CHECK(!rt->gc.isIncrementalGCInProgress()); |
| CHECK(!rt->gc.isFullGc()); |
| CHECK(checkMultipleGroups()); |
| CHECK(checkFinalizeStatus()); |
| CHECK(checkFinalizeIsCompartmentGC(true)); |
| |
| #ifdef JS_GC_ZEAL |
| |
| /* Full GC with reset due to new compartment, becoming compartment GC. */ |
| |
| FinalizeCalls = 0; |
| JS_SetGCZeal(cx, 9, 1000000); |
| JS::PrepareForFullGC(rt); |
| js::SliceBudget budget(js::WorkBudget(1)); |
| rt->gc.startDebugGC(GC_NORMAL, budget); |
| CHECK(rt->gc.state() == js::gc::MARK); |
| CHECK(rt->gc.isFullGc()); |
| |
| JS::RootedObject global4(cx, createTestGlobal()); |
| budget = js::SliceBudget(js::WorkBudget(1)); |
| rt->gc.debugGCSlice(budget); |
| CHECK(!rt->gc.isIncrementalGCInProgress()); |
| CHECK(!rt->gc.isFullGc()); |
| CHECK(checkMultipleGroups()); |
| CHECK(checkFinalizeStatus()); |
| |
| for (unsigned i = 0; i < FinalizeCalls - 1; ++i) |
| CHECK(!IsCompartmentGCBuffer[i]); |
| CHECK(IsCompartmentGCBuffer[FinalizeCalls - 1]); |
| |
| 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 compartments 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::CompartmentOptions options; |
| options.setVersion(JSVERSION_LATEST); |
| return JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, options); |
| } |
| |
| virtual bool init() override |
| { |
| if (!JSAPITest::init()) |
| return false; |
| |
| JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr); |
| return true; |
| } |
| |
| virtual void uninit() override |
| { |
| JS_RemoveFinalizeCallback(rt, FinalizeCallback); |
| JSAPITest::uninit(); |
| } |
| |
| bool checkSingleGroup() |
| { |
| CHECK(FinalizeCalls < BufferSize); |
| CHECK(FinalizeCalls == 3); |
| return true; |
| } |
| |
| bool checkMultipleGroups() |
| { |
| CHECK(FinalizeCalls < BufferSize); |
| CHECK(FinalizeCalls % 2 == 1); |
| CHECK((FinalizeCalls - 1) / 2 > 1); |
| return true; |
| } |
| |
| bool checkFinalizeStatus() |
| { |
| /* |
| * The finalize callback should be called twice for each compartment 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 += 2) { |
| CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_START); |
| CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_END); |
| } |
| |
| CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END); |
| |
| return true; |
| } |
| |
| bool checkFinalizeIsCompartmentGC(bool isCompartmentGC) |
| { |
| for (unsigned i = 0; i < FinalizeCalls; ++i) |
| CHECK(IsCompartmentGCBuffer[i] == isCompartmentGC); |
| |
| return true; |
| } |
| |
| static void |
| FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status, bool isCompartmentGC, void* data) |
| { |
| if (FinalizeCalls < BufferSize) { |
| StatusBuffer[FinalizeCalls] = status; |
| IsCompartmentGCBuffer[FinalizeCalls] = isCompartmentGC; |
| } |
| ++FinalizeCalls; |
| } |
| END_TEST(testGCFinalizeCallback) |