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