1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 #include "mozilla/Maybe.h"
9 #include "mozilla/UniquePtr.h"
10
11 #include "gc/GCRuntime.h"
12 #include "js/ArrayBuffer.h" // JS::NewArrayBuffer
13 #include "js/RootingAPI.h"
14 #include "jsapi-tests/tests.h"
15 #include "vm/Runtime.h"
16
17 #include "vm/JSContext-inl.h"
18
19 using namespace js;
20
21 // A heap-allocated structure containing one of our barriered pointer wrappers
22 // to test.
23 template <typename W, typename T>
24 struct TestStruct {
25 W wrapper;
26
traceTestStruct27 void trace(JSTracer* trc) {
28 TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper");
29 }
30
TestStructTestStruct31 TestStruct() {}
TestStructTestStruct32 explicit TestStruct(T init) : wrapper(init) {}
33 };
34
35 template <typename T>
CreateNurseryGCThing(JSContext * cx)36 static T* CreateNurseryGCThing(JSContext* cx) {
37 MOZ_CRASH();
38 return nullptr;
39 }
40
41 template <>
CreateNurseryGCThing(JSContext * cx)42 JSObject* CreateNurseryGCThing(JSContext* cx) {
43 JS::RootedObject obj(cx, JS_NewPlainObject(cx));
44 if (!obj) {
45 return nullptr;
46 }
47 JS_DefineProperty(cx, obj, "x", 42, 0);
48 MOZ_ASSERT(IsInsideNursery(obj));
49 return obj;
50 }
51
52 template <>
CreateNurseryGCThing(JSContext * cx)53 JSFunction* CreateNurseryGCThing(JSContext* cx) {
54 /*
55 * We don't actually use the function as a function, so here we cheat and
56 * cast a JSObject.
57 */
58 return static_cast<JSFunction*>(CreateNurseryGCThing<JSObject>(cx));
59 }
60
61 template <typename T>
CreateTenuredGCThing(JSContext * cx)62 static T* CreateTenuredGCThing(JSContext* cx) {
63 MOZ_CRASH();
64 return nullptr;
65 }
66
67 template <>
CreateTenuredGCThing(JSContext * cx)68 JSObject* CreateTenuredGCThing(JSContext* cx) {
69 // Use ArrayBuffers because they have finalizers, which allows using them in
70 // TenuredHeap<> without awkward conversations about nursery allocatability.
71 // Note that at some point ArrayBuffers might become nursery allocated at
72 // which point this test will have to change.
73 JSObject* obj = JS::NewArrayBuffer(cx, 20);
74 MOZ_ASSERT(!IsInsideNursery(obj));
75 MOZ_ASSERT(obj->getClass()->hasFinalize() &&
76 !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
77 return obj;
78 }
79
MakeGray(JSObject * obj)80 static void MakeGray(JSObject* obj) {
81 gc::TenuredCell* cell = &obj->asTenured();
82 cell->unmark();
83 cell->markIfUnmarked(gc::MarkColor::Gray);
84 MOZ_ASSERT(obj->isMarkedGray());
85 }
86
87 // Test post-barrier implementation on wrapper types. The following wrapper
88 // types have post barriers:
89 // - JS::Heap
90 // - GCPtr
91 // - HeapPtr
92 // - WeakHeapPtr
BEGIN_TEST(testGCHeapPostBarriers)93 BEGIN_TEST(testGCHeapPostBarriers) {
94 AutoLeaveZeal nozeal(cx);
95
96 /* Sanity check - objects start in the nursery and then become tenured. */
97 JS_GC(cx);
98 JS::RootedObject obj(cx, CreateNurseryGCThing<JSObject>(cx));
99 CHECK(js::gc::IsInsideNursery(obj.get()));
100 JS_GC(cx);
101 CHECK(!js::gc::IsInsideNursery(obj.get()));
102 JS::RootedObject tenuredObject(cx, obj);
103
104 /* JSObject and JSFunction objects are nursery allocated. */
105 CHECK(TestHeapPostBarriersForType<JSObject>());
106 CHECK(TestHeapPostBarriersForType<JSFunction>());
107 // Bug 1599378: Add string tests.
108
109 return true;
110 }
111
CanAccessObject(JSObject * obj)112 bool CanAccessObject(JSObject* obj) {
113 JS::RootedObject rootedObj(cx, obj);
114 JS::RootedValue value(cx);
115 CHECK(JS_GetProperty(cx, rootedObj, "x", &value));
116 CHECK(value.isInt32());
117 CHECK(value.toInt32() == 42);
118 return true;
119 }
120
121 template <typename T>
TestHeapPostBarriersForType()122 bool TestHeapPostBarriersForType() {
123 CHECK((TestHeapPostBarriersForWrapper<js::GCPtr, T>()));
124 CHECK((TestHeapPostBarriersForMovableWrapper<JS::Heap, T>()));
125 CHECK((TestHeapPostBarriersForMovableWrapper<js::HeapPtr, T>()));
126 CHECK((TestHeapPostBarriersForMovableWrapper<js::WeakHeapPtr, T>()));
127 return true;
128 }
129
130 template <template <typename> class W, typename T>
TestHeapPostBarriersForMovableWrapper()131 bool TestHeapPostBarriersForMovableWrapper() {
132 CHECK((TestHeapPostBarriersForWrapper<W, T>()));
133 CHECK((TestHeapPostBarrierMoveConstruction<W<T*>, T>()));
134 CHECK((TestHeapPostBarrierMoveAssignment<W<T*>, T>()));
135 return true;
136 }
137
138 template <template <typename> class W, typename T>
TestHeapPostBarriersForWrapper()139 bool TestHeapPostBarriersForWrapper() {
140 CHECK((TestHeapPostBarrierConstruction<W<T*>, T>()));
141 CHECK((TestHeapPostBarrierConstruction<const W<T*>, T>()));
142 CHECK((TestHeapPostBarrierUpdate<W<T*>, T>()));
143 if constexpr (!std::is_same_v<W<T*>, GCPtr<T*>>) {
144 // It is not allowed to delete heap memory containing GCPtrs on
145 // initialization failure like this and doing so will cause an assertion to
146 // fail in the GCPtr destructor (although we disable this in some places in
147 // this test).
148 CHECK((TestHeapPostBarrierInitFailure<W<T*>, T>()));
149 CHECK((TestHeapPostBarrierInitFailure<const W<T*>, T>()));
150 }
151 return true;
152 }
153
154 template <typename W, typename T>
TestHeapPostBarrierConstruction()155 bool TestHeapPostBarrierConstruction() {
156 T* initialObj = CreateNurseryGCThing<T>(cx);
157 CHECK(initialObj != nullptr);
158 CHECK(js::gc::IsInsideNursery(initialObj));
159 uintptr_t initialObjAsInt = uintptr_t(initialObj);
160
161 {
162 // We don't root our structure because that would end up tracing it on minor
163 // GC and we're testing that heap post barrier works for things that aren't
164 // roots.
165 JS::AutoSuppressGCAnalysis noAnalysis(cx);
166
167 auto* testStruct = js_new<TestStruct<W, T*>>(initialObj);
168 CHECK(testStruct);
169
170 auto& wrapper = testStruct->wrapper;
171 CHECK(wrapper == initialObj);
172
173 cx->minorGC(JS::GCReason::API);
174
175 CHECK(uintptr_t(wrapper.get()) != initialObjAsInt);
176 CHECK(!js::gc::IsInsideNursery(wrapper.get()));
177 CHECK(CanAccessObject(wrapper.get()));
178
179 // Disable the check that GCPtrs are only destroyed by the GC. What happens
180 // on destruction isn't relevant to the test.
181 mozilla::Maybe<gc::AutoSetThreadIsFinalizing> threadIsFinalizing;
182 if constexpr (std::is_same_v<std::remove_const_t<W>, GCPtr<T*>>) {
183 threadIsFinalizing.emplace();
184 }
185
186 js_delete(testStruct);
187 }
188
189 cx->minorGC(JS::GCReason::API);
190
191 return true;
192 }
193
194 template <typename W, typename T>
TestHeapPostBarrierUpdate()195 bool TestHeapPostBarrierUpdate() {
196 // Normal case - allocate a heap object, write a nursery pointer into it and
197 // check that it gets updated on minor GC.
198
199 T* initialObj = CreateNurseryGCThing<T>(cx);
200 CHECK(initialObj != nullptr);
201 CHECK(js::gc::IsInsideNursery(initialObj));
202 uintptr_t initialObjAsInt = uintptr_t(initialObj);
203
204 {
205 // We don't root our structure because that would end up tracing it on minor
206 // GC and we're testing that heap post barrier works for things that aren't
207 // roots.
208 JS::AutoSuppressGCAnalysis noAnalysis(cx);
209
210 auto* testStruct = js_new<TestStruct<W, T*>>();
211 CHECK(testStruct);
212
213 auto& wrapper = testStruct->wrapper;
214 CHECK(wrapper.get() == nullptr);
215
216 wrapper = initialObj;
217 CHECK(wrapper == initialObj);
218
219 cx->minorGC(JS::GCReason::API);
220
221 CHECK(uintptr_t(wrapper.get()) != initialObjAsInt);
222 CHECK(!js::gc::IsInsideNursery(wrapper.get()));
223 CHECK(CanAccessObject(wrapper.get()));
224
225 // Disable the check that GCPtrs are only destroyed by the GC. What happens
226 // on destruction isn't relevant to the test.
227 gc::AutoSetThreadIsFinalizing threadIsFinalizing;
228
229 js_delete(testStruct);
230 }
231
232 cx->minorGC(JS::GCReason::API);
233
234 return true;
235 }
236
237 template <typename W, typename T>
TestHeapPostBarrierInitFailure()238 bool TestHeapPostBarrierInitFailure() {
239 // Failure case - allocate a heap object, write a nursery pointer into it
240 // and fail to complete initialization.
241
242 T* initialObj = CreateNurseryGCThing<T>(cx);
243 CHECK(initialObj != nullptr);
244 CHECK(js::gc::IsInsideNursery(initialObj));
245
246 {
247 // We don't root our structure because that would end up tracing it on minor
248 // GC and we're testing that heap post barrier works for things that aren't
249 // roots.
250 JS::AutoSuppressGCAnalysis noAnalysis(cx);
251
252 auto testStruct = cx->make_unique<TestStruct<W, T*>>(initialObj);
253 CHECK(testStruct);
254
255 auto& wrapper = testStruct->wrapper;
256 CHECK(wrapper == initialObj);
257
258 // testStruct deleted here, as if we left this block due to an error.
259 }
260
261 cx->minorGC(JS::GCReason::API);
262
263 return true;
264 }
265
266 template <typename W, typename T>
TestHeapPostBarrierMoveConstruction()267 bool TestHeapPostBarrierMoveConstruction() {
268 T* initialObj = CreateNurseryGCThing<T>(cx);
269 CHECK(initialObj != nullptr);
270 CHECK(js::gc::IsInsideNursery(initialObj));
271 uintptr_t initialObjAsInt = uintptr_t(initialObj);
272
273 {
274 // We don't root our structure because that would end up tracing it on minor
275 // GC and we're testing that heap post barrier works for things that aren't
276 // roots.
277 JS::AutoSuppressGCAnalysis noAnalysis(cx);
278
279 W wrapper1(initialObj);
280 CHECK(wrapper1 == initialObj);
281
282 W wrapper2(std::move(wrapper1));
283 CHECK(wrapper2 == initialObj);
284
285 cx->minorGC(JS::GCReason::API);
286
287 CHECK(uintptr_t(wrapper1.get()) != initialObjAsInt);
288 CHECK(uintptr_t(wrapper2.get()) != initialObjAsInt);
289 CHECK(!js::gc::IsInsideNursery(wrapper2.get()));
290 CHECK(CanAccessObject(wrapper2.get()));
291 }
292
293 cx->minorGC(JS::GCReason::API);
294
295 return true;
296 }
297
298 template <typename W, typename T>
TestHeapPostBarrierMoveAssignment()299 bool TestHeapPostBarrierMoveAssignment() {
300 T* initialObj = CreateNurseryGCThing<T>(cx);
301 CHECK(initialObj != nullptr);
302 CHECK(js::gc::IsInsideNursery(initialObj));
303 uintptr_t initialObjAsInt = uintptr_t(initialObj);
304
305 {
306 // We don't root our structure because that would end up tracing it on minor
307 // GC and we're testing that heap post barrier works for things that aren't
308 // roots.
309 JS::AutoSuppressGCAnalysis noAnalysis(cx);
310
311 W wrapper1(initialObj);
312 CHECK(wrapper1 == initialObj);
313
314 W wrapper2;
315 wrapper2 = std::move(wrapper1);
316 CHECK(wrapper2 == initialObj);
317
318 cx->minorGC(JS::GCReason::API);
319
320 CHECK(uintptr_t(wrapper1.get()) != initialObjAsInt);
321 CHECK(uintptr_t(wrapper2.get()) != initialObjAsInt);
322 CHECK(!js::gc::IsInsideNursery(wrapper2.get()));
323 CHECK(CanAccessObject(wrapper2.get()));
324 }
325
326 cx->minorGC(JS::GCReason::API);
327
328 return true;
329 }
330
331 END_TEST(testGCHeapPostBarriers)
332
333 // Test read barrier implementation on wrapper types. The following wrapper
334 // types have read barriers:
335 // - JS::Heap
336 // - JS::TenuredHeap
337 // - WeakHeapPtr
338 //
339 // Also check that equality comparisons on wrappers do not trigger the read
340 // barrier.
BEGIN_TEST(testGCHeapReadBarriers)341 BEGIN_TEST(testGCHeapReadBarriers) {
342 AutoLeaveZeal nozeal(cx);
343
344 CHECK((TestWrapperType<JS::Heap<JSObject*>, JSObject*>()));
345 CHECK((TestWrapperType<JS::TenuredHeap<JSObject*>, JSObject*>()));
346 CHECK((TestWrapperType<WeakHeapPtr<JSObject*>, JSObject*>()));
347
348 return true;
349 }
350
351 template <typename WrapperT, typename ObjectT>
TestWrapperType()352 bool TestWrapperType() {
353 // Check that the read barrier normally marks gray things black.
354 {
355 Rooted<ObjectT> obj0(cx, CreateTenuredGCThing<JSObject>(cx));
356 WrapperT wrapper0(obj0);
357 MakeGray(obj0);
358 (void)*wrapper0;
359 CHECK(obj0->isMarkedBlack());
360 }
361
362 // Allocate test objects and make them gray. We will make sure they stay
363 // gray. (For most reads, the barrier will unmark gray.)
364 Rooted<ObjectT> obj1(cx, CreateTenuredGCThing<JSObject>(cx));
365 Rooted<ObjectT> obj2(cx, CreateTenuredGCThing<JSObject>(cx));
366 MakeGray(obj1);
367 MakeGray(obj2);
368
369 WrapperT wrapper1(obj1);
370 WrapperT wrapper2(obj2);
371 const ObjectT constobj1 = obj1;
372 const ObjectT constobj2 = obj2;
373 CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(obj1, obj2, wrapper1,
374 wrapper2)));
375 CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(constobj1, constobj2,
376 wrapper1, wrapper2)));
377
378 return true;
379 }
380
381 template <typename WrapperT, typename ObjectT>
TestUnbarrieredOperations(ObjectT obj,ObjectT obj2,WrapperT & wrapper,WrapperT & wrapper2)382 bool TestUnbarrieredOperations(ObjectT obj, ObjectT obj2, WrapperT& wrapper,
383 WrapperT& wrapper2) {
384 (void)bool(wrapper);
385 (void)bool(wrapper2);
386 CHECK(obj->isMarkedGray());
387 CHECK(obj2->isMarkedGray());
388
389 int x = 0;
390
391 CHECK(obj->isMarkedGray());
392 CHECK(obj2->isMarkedGray());
393 x += obj == obj2;
394 CHECK(obj->isMarkedGray());
395 CHECK(obj2->isMarkedGray());
396 x += obj == wrapper2;
397 CHECK(obj->isMarkedGray());
398 CHECK(obj2->isMarkedGray());
399 x += wrapper == obj2;
400 CHECK(obj->isMarkedGray());
401 CHECK(obj2->isMarkedGray());
402 x += wrapper == wrapper2;
403 CHECK(obj->isMarkedGray());
404 CHECK(obj2->isMarkedGray());
405
406 CHECK(x == 0);
407
408 x += obj != obj2;
409 CHECK(obj->isMarkedGray());
410 CHECK(obj2->isMarkedGray());
411 x += obj != wrapper2;
412 CHECK(obj->isMarkedGray());
413 CHECK(obj2->isMarkedGray());
414 x += wrapper != obj2;
415 CHECK(obj->isMarkedGray());
416 CHECK(obj2->isMarkedGray());
417 x += wrapper != wrapper2;
418 CHECK(obj->isMarkedGray());
419 CHECK(obj2->isMarkedGray());
420
421 CHECK(x == 4);
422
423 return true;
424 }
425
426 END_TEST(testGCHeapReadBarriers)
427
428 using ObjectVector = Vector<JSObject*, 0, SystemAllocPolicy>;
429
430 // Test pre-barrier implementation on wrapper types. The following wrapper types
431 // have a pre-barrier:
432 // - GCPtr
433 // - HeapPtr
434 // - PreBarriered
BEGIN_TEST(testGCHeapPreBarriers)435 BEGIN_TEST(testGCHeapPreBarriers) {
436 AutoLeaveZeal nozeal(cx);
437
438 AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
439
440 // Create a bunch of objects. These are unrooted and will be used to test
441 // whether barriers have fired by checking whether they have been marked
442 // black.
443 size_t objectCount = 100; // Increase this if necessary when adding tests.
444 ObjectVector testObjects;
445 for (size_t i = 0; i < objectCount; i++) {
446 JSObject* obj = CreateTenuredGCThing<JSObject>(cx);
447 CHECK(obj);
448 CHECK(testObjects.append(obj));
449 }
450
451 // Start an incremental GC so we can detect if we cause barriers to fire, as
452 // these will mark objects black.
453 JS::PrepareForFullGC(cx);
454 SliceBudget budget(WorkBudget(1));
455 gc::GCRuntime* gc = &cx->runtime()->gc;
456 gc->startDebugGC(JS::GCOptions::Normal, budget);
457 while (gc->state() != gc::State::Mark) {
458 gc->debugGCSlice(budget);
459 }
460 MOZ_ASSERT(cx->zone()->needsIncrementalBarrier());
461
462 TestWrapper<HeapPtr<JSObject*>>(testObjects);
463 TestWrapper<PreBarriered<JSObject*>>(testObjects);
464
465 // GCPtr is different because 1) it doesn't support move operations as it's
466 // supposed to be part of a GC thing and 2) it doesn't perform a pre-barrier
467 // in its destructor because these are only destroyed as part of a GC where
468 // the barrier is unnecessary.
469 TestGCPtr(testObjects);
470
471 gc::FinishGC(cx, JS::GCReason::API);
472
473 return true;
474 }
475
476 template <typename Wrapper>
TestWrapper(ObjectVector & testObjects)477 bool TestWrapper(ObjectVector& testObjects) {
478 CHECK(TestCopyConstruction<Wrapper>(testObjects.popCopy()));
479 CHECK(TestMoveConstruction<Wrapper>(testObjects.popCopy()));
480 CHECK(TestAssignment<Wrapper>(testObjects.popCopy(), testObjects.popCopy()));
481 CHECK(TestMoveAssignment<Wrapper>(testObjects.popCopy(),
482 testObjects.popCopy()));
483 return true;
484 }
485
486 template <typename Wrapper>
TestCopyConstruction(JSObject * obj)487 bool TestCopyConstruction(JSObject* obj) {
488 CHECK(!obj->isMarkedAny());
489
490 {
491 Wrapper wrapper1(obj);
492 Wrapper wrapper2(wrapper1);
493 CHECK(wrapper1 == obj);
494 CHECK(wrapper2 == obj);
495 CHECK(!obj->isMarkedAny());
496 }
497
498 // Check destructor performs pre-barrier.
499 CHECK(obj->isMarkedBlack());
500
501 return true;
502 }
503
504 template <typename Wrapper>
TestMoveConstruction(JSObject * obj)505 bool TestMoveConstruction(JSObject* obj) {
506 CHECK(!obj->isMarkedAny());
507
508 {
509 Wrapper wrapper1(obj);
510 MakeGray(obj); // Check that we allow move of gray GC thing.
511 Wrapper wrapper2(std::move(wrapper1));
512 CHECK(!wrapper1);
513 CHECK(wrapper2 == obj);
514 CHECK(obj->isMarkedGray());
515 }
516
517 // Check destructor performs pre-barrier.
518 CHECK(obj->isMarkedBlack());
519
520 return true;
521 }
522
523 template <typename Wrapper>
TestAssignment(JSObject * obj1,JSObject * obj2)524 bool TestAssignment(JSObject* obj1, JSObject* obj2) {
525 CHECK(!obj1->isMarkedAny());
526 CHECK(!obj2->isMarkedAny());
527
528 {
529 Wrapper wrapper1(obj1);
530 Wrapper wrapper2(obj2);
531
532 wrapper2 = wrapper1;
533
534 CHECK(wrapper1 == obj1);
535 CHECK(wrapper2 == obj1);
536 CHECK(!obj1->isMarkedAny()); // No barrier fired.
537 CHECK(obj2->isMarkedBlack()); // Pre barrier fired.
538 }
539
540 // Check destructor performs pre-barrier.
541 CHECK(obj1->isMarkedBlack());
542
543 return true;
544 }
545
546 template <typename Wrapper>
TestMoveAssignment(JSObject * obj1,JSObject * obj2)547 bool TestMoveAssignment(JSObject* obj1, JSObject* obj2) {
548 CHECK(!obj1->isMarkedAny());
549 CHECK(!obj2->isMarkedAny());
550
551 {
552 Wrapper wrapper1(obj1);
553 Wrapper wrapper2(obj2);
554
555 MakeGray(obj1); // Check we allow move of gray thing.
556 wrapper2 = std::move(wrapper1);
557
558 CHECK(!wrapper1);
559 CHECK(wrapper2 == obj1);
560 CHECK(obj1->isMarkedGray()); // No barrier fired.
561 CHECK(obj2->isMarkedBlack()); // Pre barrier fired.
562 }
563
564 // Check destructor performs pre-barrier.
565 CHECK(obj1->isMarkedBlack());
566
567 return true;
568 }
569
TestGCPtr(ObjectVector & testObjects)570 bool TestGCPtr(ObjectVector& testObjects) {
571 CHECK(TestGCPtrCopyConstruction(testObjects.popCopy()));
572 CHECK(TestGCPtrAssignment(testObjects.popCopy(), testObjects.popCopy()));
573 return true;
574 }
575
TestGCPtrCopyConstruction(JSObject * obj)576 bool TestGCPtrCopyConstruction(JSObject* obj) {
577 CHECK(!obj->isMarkedAny());
578
579 {
580 // Let us destroy GCPtrs ourselves for testing purposes.
581 gc::AutoSetThreadIsFinalizing threadIsFinalizing;
582
583 GCPtrObject wrapper1(obj);
584 GCPtrObject wrapper2(wrapper1);
585 CHECK(wrapper1 == obj);
586 CHECK(wrapper2 == obj);
587 CHECK(!obj->isMarkedAny());
588 }
589
590 // GCPtr doesn't perform pre-barrier in destructor.
591 CHECK(!obj->isMarkedAny());
592
593 return true;
594 }
595
TestGCPtrAssignment(JSObject * obj1,JSObject * obj2)596 bool TestGCPtrAssignment(JSObject* obj1, JSObject* obj2) {
597 CHECK(!obj1->isMarkedAny());
598 CHECK(!obj2->isMarkedAny());
599
600 {
601 // Let us destroy GCPtrs ourselves for testing purposes.
602 gc::AutoSetThreadIsFinalizing threadIsFinalizing;
603
604 GCPtrObject wrapper1(obj1);
605 GCPtrObject wrapper2(obj2);
606
607 wrapper2 = wrapper1;
608
609 CHECK(wrapper1 == obj1);
610 CHECK(wrapper2 == obj1);
611 CHECK(!obj1->isMarkedAny()); // No barrier fired.
612 CHECK(obj2->isMarkedBlack()); // Pre barrier fired.
613 }
614
615 // GCPtr doesn't perform pre-barrier in destructor.
616 CHECK(!obj1->isMarkedAny());
617
618 return true;
619 }
620
621 END_TEST(testGCHeapPreBarriers)
622