| /* 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/Class.h" |
| #include "jsapi-tests/tests.h" |
| |
| using namespace JS; |
| using mozilla::UniquePtr; |
| |
| struct BarkWhenTracedClass { |
| static int finalizeCount; |
| static int traceCount; |
| |
| static const JSClass class_; |
| static void finalize(JSFreeOp* fop, JSObject* obj) { finalizeCount++; } |
| static void trace(JSTracer* trc, JSObject* obj) { traceCount++; } |
| static void reset() { finalizeCount = 0; traceCount = 0; } |
| }; |
| |
| int BarkWhenTracedClass::finalizeCount; |
| int BarkWhenTracedClass::traceCount; |
| |
| const JSClass BarkWhenTracedClass::class_ = { |
| "BarkWhenTracedClass", |
| 0, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| finalize, |
| nullptr, |
| nullptr, |
| nullptr, |
| trace |
| }; |
| |
| struct Kennel { |
| PersistentRootedObject obj; |
| Kennel() { } |
| explicit Kennel(JSContext* cx) : obj(cx) { } |
| Kennel(JSContext* cx, const HandleObject& woof) : obj(cx, woof) { } |
| void init(JSContext* cx, const HandleObject& woof) { |
| obj.init(cx, woof); |
| } |
| void clear() { |
| obj = nullptr; |
| } |
| }; |
| |
| // A function for allocating a Kennel and a barker. Only allocating |
| // PersistentRooteds on the heap, and in this function, helps ensure that the |
| // conservative GC doesn't find stray references to the barker. Ugh. |
| MOZ_NEVER_INLINE static Kennel* |
| Allocate(JSContext* cx) |
| { |
| RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_)); |
| if (!barker) |
| return nullptr; |
| |
| return new Kennel(cx, barker); |
| } |
| |
| // Do a GC, expecting |n| barkers to be finalized. |
| static bool |
| GCFinalizesNBarkers(JSContext* cx, int n) |
| { |
| int preGCTrace = BarkWhenTracedClass::traceCount; |
| int preGCFinalize = BarkWhenTracedClass::finalizeCount; |
| |
| JS_GC(JS_GetRuntime(cx)); |
| |
| return (BarkWhenTracedClass::finalizeCount == preGCFinalize + n && |
| BarkWhenTracedClass::traceCount > preGCTrace); |
| } |
| |
| // PersistentRooted instances protect their contents from being recycled. |
| BEGIN_TEST(test_PersistentRooted) |
| { |
| BarkWhenTracedClass::reset(); |
| |
| UniquePtr<Kennel> kennel(Allocate(cx)); |
| CHECK(kennel.get()); |
| |
| // GC should be able to find our barker. |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| kennel = nullptr; |
| |
| // Now GC should not be able to find the barker. |
| JS_GC(JS_GetRuntime(cx)); |
| CHECK(BarkWhenTracedClass::finalizeCount == 1); |
| |
| return true; |
| } |
| END_TEST(test_PersistentRooted) |
| |
| // GC should not be upset by null PersistentRooteds. |
| BEGIN_TEST(test_PersistentRootedNull) |
| { |
| BarkWhenTracedClass::reset(); |
| |
| Kennel kennel(cx); |
| CHECK(!kennel.obj); |
| |
| JS_GC(JS_GetRuntime(cx)); |
| CHECK(BarkWhenTracedClass::finalizeCount == 0); |
| |
| return true; |
| } |
| END_TEST(test_PersistentRootedNull) |
| |
| // Copy construction works. |
| BEGIN_TEST(test_PersistentRootedCopy) |
| { |
| BarkWhenTracedClass::reset(); |
| |
| UniquePtr<Kennel> kennel(Allocate(cx)); |
| CHECK(kennel.get()); |
| |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| // Copy construction! AMAZING! |
| UniquePtr<Kennel> newKennel(new Kennel(*kennel)); |
| |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| kennel = nullptr; |
| |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| newKennel = nullptr; |
| |
| // Now that kennel and nowKennel are both deallocated, GC should not be |
| // able to find the barker. |
| JS_GC(JS_GetRuntime(cx)); |
| CHECK(BarkWhenTracedClass::finalizeCount == 1); |
| |
| return true; |
| } |
| END_TEST(test_PersistentRootedCopy) |
| |
| // Assignment works. |
| BEGIN_TEST(test_PersistentRootedAssign) |
| { |
| BarkWhenTracedClass::reset(); |
| |
| UniquePtr<Kennel> kennel(Allocate(cx)); |
| CHECK(kennel.get()); |
| |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| // Allocate a new, empty kennel. |
| UniquePtr<Kennel> kennel2(new Kennel(cx)); |
| |
| // Assignment! ASTONISHING! |
| *kennel2 = *kennel; |
| |
| // With both kennels referring to the same barker, it is held alive. |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| kennel2 = nullptr; |
| |
| // The destination of the assignment alone holds the barker alive. |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| // Allocate a second barker. |
| kennel2 = UniquePtr<Kennel>(Allocate(cx)); |
| CHECK(kennel2.get()); |
| |
| *kennel = *kennel2; |
| |
| // Nothing refers to the first kennel any more. |
| CHECK(GCFinalizesNBarkers(cx, 1)); |
| |
| kennel = nullptr; |
| kennel2 = nullptr; |
| |
| // Now that kennel and kennel2 are both deallocated, GC should not be |
| // able to find the barker. |
| JS_GC(JS_GetRuntime(cx)); |
| CHECK(BarkWhenTracedClass::finalizeCount == 2); |
| |
| return true; |
| } |
| END_TEST(test_PersistentRootedAssign) |
| |
| static PersistentRootedObject gGlobalRoot; |
| |
| // PersistentRooted instances can initialized in a separate step to allow for global PersistentRooteds. |
| BEGIN_TEST(test_GlobalPersistentRooted) |
| { |
| BarkWhenTracedClass::reset(); |
| |
| CHECK(!gGlobalRoot.initialized()); |
| |
| { |
| RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_)); |
| CHECK(barker); |
| |
| gGlobalRoot.init(cx, barker); |
| } |
| |
| CHECK(gGlobalRoot.initialized()); |
| |
| // GC should be able to find our barker. |
| CHECK(GCFinalizesNBarkers(cx, 0)); |
| |
| gGlobalRoot.reset(); |
| CHECK(!gGlobalRoot.initialized()); |
| |
| // Now GC should not be able to find the barker. |
| JS_GC(JS_GetRuntime(cx)); |
| CHECK(BarkWhenTracedClass::finalizeCount == 1); |
| |
| return true; |
| } |
| END_TEST(test_GlobalPersistentRooted) |