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_;
finalizeBarkWhenTracedClass15 static void finalize(JSFreeOp* fop, JSObject* obj) { finalizeCount++; }
traceBarkWhenTracedClass16 static void trace(JSTracer* trc, JSObject* obj) { traceCount++; }
resetBarkWhenTracedClass17 static void reset() { finalizeCount = 0; traceCount = 0; }
18 };
19
20 int BarkWhenTracedClass::finalizeCount;
21 int BarkWhenTracedClass::traceCount;
22
23 static const JSClassOps BarkWhenTracedClassClassOps = {
24 nullptr,
25 nullptr,
26 nullptr,
27 nullptr,
28 nullptr,
29 nullptr,
30 nullptr,
31 BarkWhenTracedClass::finalize,
32 nullptr,
33 nullptr,
34 nullptr,
35 BarkWhenTracedClass::trace
36 };
37
38 const JSClass BarkWhenTracedClass::class_ = {
39 "BarkWhenTracedClass",
40 JSCLASS_FOREGROUND_FINALIZE,
41 &BarkWhenTracedClassClassOps
42 };
43
44 struct Kennel {
45 PersistentRootedObject obj;
KennelKennel46 Kennel() { }
KennelKennel47 explicit Kennel(JSContext* cx) : obj(cx) { }
KennelKennel48 Kennel(JSContext* cx, const HandleObject& woof) : obj(cx, woof) { }
initKennel49 void init(JSContext* cx, const HandleObject& woof) {
50 obj.init(cx, woof);
51 }
clearKennel52 void clear() {
53 obj = nullptr;
54 }
55 };
56
57 // A function for allocating a Kennel and a barker. Only allocating
58 // PersistentRooteds on the heap, and in this function, helps ensure that the
59 // conservative GC doesn't find stray references to the barker. Ugh.
60 MOZ_NEVER_INLINE static Kennel*
Allocate(JSContext * cx)61 Allocate(JSContext* cx)
62 {
63 RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_));
64 if (!barker)
65 return nullptr;
66
67 return new Kennel(cx, barker);
68 }
69
70 // Do a GC, expecting |n| barkers to be finalized.
71 static bool
GCFinalizesNBarkers(JSContext * cx,int n)72 GCFinalizesNBarkers(JSContext* cx, int n)
73 {
74 int preGCTrace = BarkWhenTracedClass::traceCount;
75 int preGCFinalize = BarkWhenTracedClass::finalizeCount;
76
77 JS_GC(cx);
78
79 return (BarkWhenTracedClass::finalizeCount == preGCFinalize + n &&
80 BarkWhenTracedClass::traceCount > preGCTrace);
81 }
82
83 // PersistentRooted instances protect their contents from being recycled.
BEGIN_TEST(test_PersistentRooted)84 BEGIN_TEST(test_PersistentRooted)
85 {
86 BarkWhenTracedClass::reset();
87
88 mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
89 CHECK(kennel.get());
90
91 // GC should be able to find our barker.
92 CHECK(GCFinalizesNBarkers(cx, 0));
93
94 kennel = nullptr;
95
96 // Now GC should not be able to find the barker.
97 JS_GC(cx);
98 CHECK(BarkWhenTracedClass::finalizeCount == 1);
99
100 return true;
101 }
102 END_TEST(test_PersistentRooted)
103
104 // GC should not be upset by null PersistentRooteds.
BEGIN_TEST(test_PersistentRootedNull)105 BEGIN_TEST(test_PersistentRootedNull)
106 {
107 BarkWhenTracedClass::reset();
108
109 Kennel kennel(cx);
110 CHECK(!kennel.obj);
111
112 JS_GC(cx);
113 CHECK(BarkWhenTracedClass::finalizeCount == 0);
114
115 return true;
116 }
117 END_TEST(test_PersistentRootedNull)
118
119 // Copy construction works.
BEGIN_TEST(test_PersistentRootedCopy)120 BEGIN_TEST(test_PersistentRootedCopy)
121 {
122 BarkWhenTracedClass::reset();
123
124 mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
125 CHECK(kennel.get());
126
127 CHECK(GCFinalizesNBarkers(cx, 0));
128
129 // Copy construction! AMAZING!
130 mozilla::UniquePtr<Kennel> newKennel(new Kennel(*kennel));
131
132 CHECK(GCFinalizesNBarkers(cx, 0));
133
134 kennel = nullptr;
135
136 CHECK(GCFinalizesNBarkers(cx, 0));
137
138 newKennel = nullptr;
139
140 // Now that kennel and nowKennel are both deallocated, GC should not be
141 // able to find the barker.
142 JS_GC(cx);
143 CHECK(BarkWhenTracedClass::finalizeCount == 1);
144
145 return true;
146 }
147 END_TEST(test_PersistentRootedCopy)
148
149 // Assignment works.
BEGIN_TEST(test_PersistentRootedAssign)150 BEGIN_TEST(test_PersistentRootedAssign)
151 {
152 BarkWhenTracedClass::reset();
153
154 mozilla::UniquePtr<Kennel> kennel(Allocate(cx));
155 CHECK(kennel.get());
156
157 CHECK(GCFinalizesNBarkers(cx, 0));
158
159 // Allocate a new, empty kennel.
160 mozilla::UniquePtr<Kennel> kennel2(new Kennel(cx));
161
162 // Assignment! ASTONISHING!
163 *kennel2 = *kennel;
164
165 // With both kennels referring to the same barker, it is held alive.
166 CHECK(GCFinalizesNBarkers(cx, 0));
167
168 kennel2 = nullptr;
169
170 // The destination of the assignment alone holds the barker alive.
171 CHECK(GCFinalizesNBarkers(cx, 0));
172
173 // Allocate a second barker.
174 kennel2 = mozilla::UniquePtr<Kennel>(Allocate(cx));
175 CHECK(kennel2.get());
176
177 *kennel = *kennel2;
178
179 // Nothing refers to the first kennel any more.
180 CHECK(GCFinalizesNBarkers(cx, 1));
181
182 kennel = nullptr;
183 kennel2 = nullptr;
184
185 // Now that kennel and kennel2 are both deallocated, GC should not be
186 // able to find the barker.
187 JS_GC(cx);
188 CHECK(BarkWhenTracedClass::finalizeCount == 2);
189
190 return true;
191 }
192 END_TEST(test_PersistentRootedAssign)
193
194 static PersistentRootedObject gGlobalRoot;
195
196 // PersistentRooted instances can initialized in a separate step to allow for global PersistentRooteds.
BEGIN_TEST(test_GlobalPersistentRooted)197 BEGIN_TEST(test_GlobalPersistentRooted)
198 {
199 BarkWhenTracedClass::reset();
200
201 CHECK(!gGlobalRoot.initialized());
202
203 {
204 RootedObject barker(cx, JS_NewObject(cx, &BarkWhenTracedClass::class_));
205 CHECK(barker);
206
207 gGlobalRoot.init(cx, barker);
208 }
209
210 CHECK(gGlobalRoot.initialized());
211
212 // GC should be able to find our barker.
213 CHECK(GCFinalizesNBarkers(cx, 0));
214
215 gGlobalRoot.reset();
216 CHECK(!gGlobalRoot.initialized());
217
218 // Now GC should not be able to find the barker.
219 JS_GC(cx);
220 CHECK(BarkWhenTracedClass::finalizeCount == 1);
221
222 return true;
223 }
224 END_TEST(test_GlobalPersistentRooted)
225