testPersistentRooted.cpp (5727B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "js/Class.h" 6 #include "jsapi-tests/tests.h" 7 8 using namespace JS; 9 10 struct BarkWhenTracedClass { 11 static int finalizeCount; 12 static int traceCount; 13 14 static const JSClass class_; 15 static void finalize(JS::GCContext* gcx, JSObject* obj) { finalizeCount++; } 16 static void trace(JSTracer* trc, JSObject* obj) { traceCount++; } 17 static void reset() { 18 finalizeCount = 0; 19 traceCount = 0; 20 } 21 }; 22 23 int BarkWhenTracedClass::finalizeCount; 24 int BarkWhenTracedClass::traceCount; 25 26 static const JSClassOps BarkWhenTracedClassClassOps = { 27 nullptr, // addProperty 28 nullptr, // delProperty 29 nullptr, // enumerate 30 nullptr, // newEnumerate 31 nullptr, // resolve 32 nullptr, // mayResolve 33 BarkWhenTracedClass::finalize, // finalize 34 nullptr, // call 35 nullptr, // construct 36 BarkWhenTracedClass::trace, // trace 37 }; 38 39 const JSClass BarkWhenTracedClass::class_ = { 40 "BarkWhenTracedClass", 41 JSCLASS_FOREGROUND_FINALIZE, 42 &BarkWhenTracedClassClassOps, 43 }; 44 45 struct Kennel { 46 PersistentRootedObject obj; 47 Kennel() {} 48 explicit Kennel(JSContext* cx) : obj(cx) {} 49 Kennel(JSContext* cx, const HandleObject& woof) : obj(cx, woof) {} 50 void init(JSContext* cx, const HandleObject& woof) { obj.init(cx, woof); } 51 void clear() { obj = nullptr; } 52 }; 53 54 // A function for allocating a Kennel and a barker. Only allocating 55 // PersistentRooteds on the heap, and in this function, helps ensure that the 56 // conservative GC doesn't find stray references to the barker. Ugh. 57 MOZ_NEVER_INLINE static Kennel* Allocate(JSContext* cx) { 58 RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_)); 59 if (!barker) { 60 return nullptr; 61 } 62 63 return new Kennel(cx, barker); 64 } 65 66 // Do a GC, expecting |n| barkers to be finalized. 67 static bool GCFinalizesNBarkers(JSContext* cx, int n) { 68 int preGCTrace = BarkWhenTracedClass::traceCount; 69 int preGCFinalize = BarkWhenTracedClass::finalizeCount; 70 71 JS_GC(cx); 72 73 return (BarkWhenTracedClass::finalizeCount == preGCFinalize + n && 74 BarkWhenTracedClass::traceCount > preGCTrace); 75 } 76 77 // PersistentRooted instances protect their contents from being recycled. 78 BEGIN_TEST(test_PersistentRooted) { 79 BarkWhenTracedClass::reset(); 80 81 mozilla::UniquePtr<Kennel> kennel(Allocate(cx)); 82 CHECK(kennel.get()); 83 84 // GC should be able to find our barker. 85 CHECK(GCFinalizesNBarkers(cx, 0)); 86 87 kennel = nullptr; 88 89 // Now GC should not be able to find the barker. 90 JS_GC(cx); 91 CHECK(BarkWhenTracedClass::finalizeCount == 1); 92 93 return true; 94 } 95 END_TEST(test_PersistentRooted) 96 97 // GC should not be upset by null PersistentRooteds. 98 BEGIN_TEST(test_PersistentRootedNull) { 99 BarkWhenTracedClass::reset(); 100 101 Kennel kennel(cx); 102 CHECK(!kennel.obj); 103 104 JS_GC(cx); 105 CHECK(BarkWhenTracedClass::finalizeCount == 0); 106 107 return true; 108 } 109 END_TEST(test_PersistentRootedNull) 110 111 // Copy construction works. 112 BEGIN_TEST(test_PersistentRootedCopy) { 113 BarkWhenTracedClass::reset(); 114 115 mozilla::UniquePtr<Kennel> kennel(Allocate(cx)); 116 CHECK(kennel.get()); 117 118 CHECK(GCFinalizesNBarkers(cx, 0)); 119 120 // Copy construction! AMAZING! 121 mozilla::UniquePtr<Kennel> newKennel(new Kennel(*kennel)); 122 123 CHECK(GCFinalizesNBarkers(cx, 0)); 124 125 kennel = nullptr; 126 127 CHECK(GCFinalizesNBarkers(cx, 0)); 128 129 newKennel = nullptr; 130 131 // Now that kennel and nowKennel are both deallocated, GC should not be 132 // able to find the barker. 133 JS_GC(cx); 134 CHECK(BarkWhenTracedClass::finalizeCount == 1); 135 136 return true; 137 } 138 END_TEST(test_PersistentRootedCopy) 139 140 // Assignment works. 141 BEGIN_TEST(test_PersistentRootedAssign) { 142 BarkWhenTracedClass::reset(); 143 144 mozilla::UniquePtr<Kennel> kennel(Allocate(cx)); 145 CHECK(kennel.get()); 146 147 CHECK(GCFinalizesNBarkers(cx, 0)); 148 149 // Allocate a new, empty kennel. 150 mozilla::UniquePtr<Kennel> kennel2(new Kennel(cx)); 151 152 // Assignment! ASTONISHING! 153 *kennel2 = *kennel; 154 155 // With both kennels referring to the same barker, it is held alive. 156 CHECK(GCFinalizesNBarkers(cx, 0)); 157 158 kennel2 = nullptr; 159 160 // The destination of the assignment alone holds the barker alive. 161 CHECK(GCFinalizesNBarkers(cx, 0)); 162 163 // Allocate a second barker. 164 kennel2 = mozilla::UniquePtr<Kennel>(Allocate(cx)); 165 CHECK(kennel2.get()); 166 167 *kennel = *kennel2; 168 169 // Nothing refers to the first kennel any more. 170 CHECK(GCFinalizesNBarkers(cx, 1)); 171 172 kennel = nullptr; 173 kennel2 = nullptr; 174 175 // Now that kennel and kennel2 are both deallocated, GC should not be 176 // able to find the barker. 177 JS_GC(cx); 178 CHECK(BarkWhenTracedClass::finalizeCount == 2); 179 180 return true; 181 } 182 END_TEST(test_PersistentRootedAssign) 183 184 MOZ_RUNINIT static PersistentRootedObject gGlobalRoot; 185 186 // PersistentRooted instances can initialized in a separate step to allow for 187 // global PersistentRooteds. 188 BEGIN_TEST(test_GlobalPersistentRooted) { 189 BarkWhenTracedClass::reset(); 190 191 CHECK(!gGlobalRoot.initialized()); 192 193 { 194 RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_)); 195 CHECK(barker); 196 197 gGlobalRoot.init(cx, barker); 198 } 199 200 CHECK(gGlobalRoot.initialized()); 201 202 // GC should be able to find our barker. 203 CHECK(GCFinalizesNBarkers(cx, 0)); 204 205 gGlobalRoot.reset(); 206 CHECK(!gGlobalRoot.initialized()); 207 208 // Now GC should not be able to find the barker. 209 JS_GC(cx); 210 CHECK(BarkWhenTracedClass::finalizeCount == 1); 211 212 return true; 213 } 214 END_TEST(test_GlobalPersistentRooted)