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