Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "gc/AllocKind.h"
#include "gc/Cell.h"
#include "gc/GCInternals.h"
#include "gc/GCRuntime.h"
#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
#include "js/experimental/TypedData.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
#include "js/RootingAPI.h"
#include "jsapi-tests/tests.h"
#include "vm/Runtime.h"
#include "vm/TypedArrayObject.h"
#include "gc/Heap-inl.h"
#include "vm/JSContext-inl.h"
using namespace js;
static js::gc::CellColor GetColor(JSObject* obj) { return obj->color(); }
static js::gc::CellColor GetColor(const JS::ArrayBufferOrView& view) {
return view.asObjectUnbarriered()->color();
}
static MOZ_MAYBE_UNUSED bool IsInsideNursery(gc::Cell* cell) {
return !cell->isTenured();
}
static MOZ_MAYBE_UNUSED bool IsInsideNursery(
const JS::ArrayBufferOrView& view) {
return IsInsideNursery(view.asObjectUnbarriered());
}
// A heap-allocated structure containing one of our barriered pointer wrappers
// to test.
template <typename W, typename T>
struct TestStruct {
W wrapper;
void trace(JSTracer* trc) {
TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper");
}
TestStruct() {}
explicit TestStruct(T init) : wrapper(init) {}
};
template <typename T>
static T CreateNurseryGCThing(JSContext* cx) = delete;
template <>
JSObject* CreateNurseryGCThing(JSContext* cx) {
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj) {
return nullptr;
}
JS_DefineProperty(cx, obj, "x", 42, 0);
MOZ_ASSERT(IsInsideNursery(obj));
return obj;
}
template <>
JSFunction* CreateNurseryGCThing(JSContext* cx) {
/*
* We don't actually use the function as a function, so here we cheat and
* cast a JSObject.
*/
return static_cast<JSFunction*>(CreateNurseryGCThing<JSObject*>(cx));
}
template <>
JS::Uint8Array CreateNurseryGCThing(JSContext* cx) {
JS::Rooted<JS::Uint8Array> arr(cx, JS::Uint8Array::create(cx, 100));
JS::RootedObject obj(cx, arr.asObject());
JS_DefineProperty(cx, obj, "x", 42, 0);
MOZ_ASSERT(IsInsideNursery(obj));
return arr;
}
template <typename T>
static T CreateTenuredGCThing(JSContext* cx) = delete;
template <>
JSObject* CreateTenuredGCThing(JSContext* cx) {
// Use ArrayBuffers because they have finalizers, which allows using them in
// TenuredHeap<> without awkward conversations about nursery allocatability.
// Note that at some point ArrayBuffers might become nursery allocated at
// which point this test will have to change.
JSObject* obj = JS::NewArrayBuffer(cx, 20);
MOZ_ASSERT(!IsInsideNursery(obj));
MOZ_ASSERT(obj->getClass()->hasFinalize() &&
!(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
return obj;
}
template <>
JS::ArrayBuffer CreateTenuredGCThing(JSContext* cx) {
return JS::ArrayBuffer::fromObject(CreateTenuredGCThing<JSObject*>(cx));
}
template <>
JS::Uint8Array CreateTenuredGCThing(JSContext* cx) {
// Use internal APIs that lets us specify the InitialHeap so we can ensure
// that this is tenured.
JSObject* obj = js::NewUint8ArrayWithLength(cx, 100, gc::Heap::Tenured);
MOZ_ASSERT(!IsInsideNursery(obj));
return JS::Uint8Array::fromObject(obj);
}
template <typename T>
void* CreateHiddenTenuredGCThing(JSContext* cx) {
return CreateTenuredGCThing<T>(cx);
}
template <>
void* CreateHiddenTenuredGCThing<JS::ArrayBuffer>(JSContext* cx) {
return CreateTenuredGCThing<JS::ArrayBuffer>(cx).asObjectUnbarriered();
}
template <>
void* CreateHiddenTenuredGCThing<JS::Uint8Array>(JSContext* cx) {
return CreateTenuredGCThing<JS::Uint8Array>(cx).asObjectUnbarriered();
}
static uintptr_t UnbarrieredCastToInt(gc::Cell* cell) {
return reinterpret_cast<uintptr_t>(cell);
}
static uintptr_t UnbarrieredCastToInt(const JS::ArrayBufferOrView& view) {
return UnbarrieredCastToInt(view.asObjectUnbarriered());
}
template <typename T>
T RecoverHiddenGCThing(void* ptr) {
return reinterpret_cast<T>(ptr);
}
template <>
JS::ArrayBuffer RecoverHiddenGCThing(void* ptr) {
return JS::ArrayBuffer::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
}
template <>
JS::Uint8Array RecoverHiddenGCThing(void* ptr) {
return JS::Uint8Array::fromObject(RecoverHiddenGCThing<JSObject*>(ptr));
}
static void MakeGray(JSObject* obj) {
gc::TenuredCell* cell = &obj->asTenured();
cell->unmark();
cell->markIfUnmarked(gc::MarkColor::Gray);
MOZ_ASSERT(obj->isMarkedGray());
}
static void MakeGray(const JS::ArrayBufferOrView& view) {
MakeGray(view.asObjectUnbarriered());
}
// Test post-barrier implementation on wrapper types. The following wrapper
// types have post barriers:
// - JS::Heap
// - GCPtr
// - HeapPtr
// - WeakHeapPtr
BEGIN_TEST(testGCHeapPostBarriers) {
AutoLeaveZeal nozeal(cx);
AutoGCParameter disableSemispace(cx, JSGC_SEMISPACE_NURSERY_ENABLED, 0);
/* Sanity check - objects start in the nursery and then become tenured. */
JS_GC(cx);
JS::RootedObject obj(cx, CreateNurseryGCThing<JSObject*>(cx));
CHECK(IsInsideNursery(obj.get()));
JS_GC(cx);
CHECK(!IsInsideNursery(obj.get()));
JS::RootedObject tenuredObject(cx, obj);
/* JSObject and JSFunction objects are nursery allocated. */
CHECK(TestHeapPostBarriersForType<JSObject*>());
CHECK(TestHeapPostBarriersForType<JSFunction*>());
CHECK(TestHeapPostBarriersForType<JS::Uint8Array>());
// Bug 1599378: Add string tests.
return true;
}
bool CanAccessObject(JSObject* obj) {
JS::RootedObject rootedObj(cx, obj);
JS::RootedValue value(cx);
CHECK(JS_GetProperty(cx, rootedObj, "x", &value));
CHECK(value.isInt32());
CHECK(value.toInt32() == 42);
return true;
}
bool CanAccessObject(const JS::ArrayBufferOrView& view) {
return CanAccessObject(view.asObject());
}
template <typename T>
bool TestHeapPostBarriersForType() {
CHECK((TestHeapPostBarriersForWrapper<js::GCPtr, T>()));
CHECK((TestHeapPostBarriersForMovableWrapper<JS::Heap, T>()));
CHECK((TestHeapPostBarriersForMovableWrapper<js::HeapPtr, T>()));
CHECK((TestHeapPostBarriersForMovableWrapper<js::WeakHeapPtr, T>()));
return true;
}
template <template <typename> class W, typename T>
bool TestHeapPostBarriersForMovableWrapper() {
CHECK((TestHeapPostBarriersForWrapper<W, T>()));
CHECK((TestHeapPostBarrierMoveConstruction<W<T>, T>()));
CHECK((TestHeapPostBarrierMoveAssignment<W<T>, T>()));
return true;
}
template <template <typename> class W, typename T>
bool TestHeapPostBarriersForWrapper() {
CHECK((TestHeapPostBarrierConstruction<W<T>, T>()));
CHECK((TestHeapPostBarrierConstruction<const W<T>, T>()));
CHECK((TestHeapPostBarrierUpdate<W<T>, T>()));
if constexpr (!std::is_same_v<W<T>, GCPtr<T>>) {
// It is not allowed to delete heap memory containing GCPtrs on
// initialization failure like this and doing so will cause an assertion to
// fail in the GCPtr destructor (although we disable this in some places in
// this test).
CHECK((TestHeapPostBarrierInitFailure<W<T>, T>()));
CHECK((TestHeapPostBarrierInitFailure<const W<T>, T>()));
}
return true;
}
template <typename W, typename T>
bool TestHeapPostBarrierConstruction() {
T initialObj = CreateNurseryGCThing<T>(cx);
CHECK(initialObj);
CHECK(IsInsideNursery(initialObj));
uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
{
// We don't root our structure because that would end up tracing it on minor
// GC and we're testing that heap post barrier works for things that aren't
// roots.
JS::AutoSuppressGCAnalysis noAnalysis(cx);
auto* testStruct = js_new<TestStruct<W, T>>(initialObj);
CHECK(testStruct);
auto& wrapper = testStruct->wrapper;
CHECK(wrapper == initialObj);
cx->minorGC(JS::GCReason::API);
CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
CHECK(!IsInsideNursery(wrapper.get()));
CHECK(CanAccessObject(wrapper.get()));
// Disable the check that GCPtrs are only destroyed by the GC. What happens
// on destruction isn't relevant to the test.
gc::AutoSetThreadIsFinalizing threadIsFinalizing;
js_delete(testStruct);
}
cx->minorGC(JS::GCReason::API);
return true;
}
template <typename W, typename T>
bool TestHeapPostBarrierUpdate() {
// Normal case - allocate a heap object, write a nursery pointer into it and
// check that it gets updated on minor GC.
T initialObj = CreateNurseryGCThing<T>(cx);
CHECK(initialObj);
CHECK(IsInsideNursery(initialObj));
uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
{
// We don't root our structure because that would end up tracing it on minor
// GC and we're testing that heap post barrier works for things that aren't
// roots.
JS::AutoSuppressGCAnalysis noAnalysis(cx);
auto* testStruct = js_new<TestStruct<W, T>>();
CHECK(testStruct);
auto& wrapper = testStruct->wrapper;
CHECK(!wrapper.get());
wrapper = initialObj;
CHECK(wrapper == initialObj);
cx->minorGC(JS::GCReason::API);
CHECK(UnbarrieredCastToInt(wrapper.get()) != initialObjAsInt);
CHECK(!IsInsideNursery(wrapper.get()));
CHECK(CanAccessObject(wrapper.get()));
// Disable the check that GCPtrs are only destroyed by the GC. What happens
// on destruction isn't relevant to the test.
gc::AutoSetThreadIsFinalizing threadIsFinalizing;
js_delete(testStruct);
}
cx->minorGC(JS::GCReason::API);
return true;
}
template <typename W, typename T>
bool TestHeapPostBarrierInitFailure() {
// Failure case - allocate a heap object, write a nursery pointer into it
// and fail to complete initialization.
T initialObj = CreateNurseryGCThing<T>(cx);
CHECK(initialObj);
CHECK(IsInsideNursery(initialObj));
{
// We don't root our structure because that would end up tracing it on minor
// GC and we're testing that heap post barrier works for things that aren't
// roots.
JS::AutoSuppressGCAnalysis noAnalysis(cx);
auto testStruct = cx->make_unique<TestStruct<W, T>>(initialObj);
CHECK(testStruct);
auto& wrapper = testStruct->wrapper;
CHECK(wrapper == initialObj);
// testStruct deleted here, as if we left this block due to an error.
}
cx->minorGC(JS::GCReason::API);
return true;
}
template <typename W, typename T>
bool TestHeapPostBarrierMoveConstruction() {
T initialObj = CreateNurseryGCThing<T>(cx);
CHECK(initialObj);
CHECK(IsInsideNursery(initialObj));
uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
{
// We don't root our structure because that would end up tracing it on minor
// GC and we're testing that heap post barrier works for things that aren't
// roots.
JS::AutoSuppressGCAnalysis noAnalysis(cx);
W wrapper1(initialObj);
CHECK(wrapper1 == initialObj);
W wrapper2(std::move(wrapper1));
CHECK(wrapper2 == initialObj);
cx->minorGC(JS::GCReason::API);
CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
CHECK(!IsInsideNursery(wrapper2.get()));
CHECK(CanAccessObject(wrapper2.get()));
}
cx->minorGC(JS::GCReason::API);
return true;
}
template <typename W, typename T>
bool TestHeapPostBarrierMoveAssignment() {
T initialObj = CreateNurseryGCThing<T>(cx);
CHECK(initialObj);
CHECK(IsInsideNursery(initialObj));
uintptr_t initialObjAsInt = UnbarrieredCastToInt(initialObj);
{
// We don't root our structure because that would end up tracing it on minor
// GC and we're testing that heap post barrier works for things that aren't
// roots.
JS::AutoSuppressGCAnalysis noAnalysis(cx);
W wrapper1(initialObj);
CHECK(wrapper1 == initialObj);
W wrapper2;
wrapper2 = std::move(wrapper1);
CHECK(wrapper2 == initialObj);
cx->minorGC(JS::GCReason::API);
CHECK(UnbarrieredCastToInt(wrapper1.get()) != initialObjAsInt);
CHECK(UnbarrieredCastToInt(wrapper2.get()) != initialObjAsInt);
CHECK(!IsInsideNursery(wrapper2.get()));
CHECK(CanAccessObject(wrapper2.get()));
}
cx->minorGC(JS::GCReason::API);
return true;
}
END_TEST(testGCHeapPostBarriers)
// Test read barrier implementation on wrapper types. The following wrapper
// types have read barriers:
// - JS::Heap
// - JS::TenuredHeap
// - WeakHeapPtr
//
// Also check that equality comparisons on wrappers do not trigger the read
// barrier.
BEGIN_TEST(testGCHeapReadBarriers) {
AutoLeaveZeal nozeal(cx);
CHECK((TestWrapperType<JS::Heap<JSObject*>, JSObject*>()));
CHECK((TestWrapperType<JS::TenuredHeap<JSObject*>, JSObject*>()));
CHECK((TestWrapperType<WeakHeapPtr<JSObject*>, JSObject*>()));
CHECK((TestWrapperType<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>()));
CHECK((TestWrapperType<JS::Heap<JS::Uint8Array>, JS::Uint8Array>()));
// JS::Heap has an additional barrier on its move and copy constructors.
CHECK((TestConstructorBarrier<JS::Heap<JSObject*>, JSObject*>()));
CHECK((TestConstructorBarrier<JS::Heap<JS::ArrayBuffer>, JS::ArrayBuffer>()));
CHECK((TestConstructorBarrier<JS::Heap<JS::Uint8Array>, JS::Uint8Array>()));
return true;
}
template <typename WrapperT, typename ObjectT>
bool TestWrapperType() {
// Check that the read barrier normally marks gray things black.
CHECK((TestReadBarrierUnmarksGray<WrapperT, ObjectT>()));
// Check that the read barrier marks gray and white things black during an
// incremental GC.
CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(true)));
CHECK((TestReadBarrierMarksBlack<WrapperT, ObjectT>(false)));
// Allocate test objects and make them gray. We will make sure they stay
// gray. (For most reads, the barrier will unmark gray.)
Rooted<ObjectT> obj1(cx, CreateTenuredGCThing<ObjectT>(cx));
Rooted<ObjectT> obj2(cx, CreateTenuredGCThing<ObjectT>(cx));
MakeGray(obj1);
MakeGray(obj2);
WrapperT wrapper1(obj1);
WrapperT wrapper2(obj2);
const ObjectT constobj1 = obj1;
const ObjectT constobj2 = obj2;
CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(obj1, obj2, wrapper1,
wrapper2)));
CHECK((TestUnbarrieredOperations<WrapperT, ObjectT>(constobj1, constobj2,
wrapper1, wrapper2)));
return true;
}
template <typename WrapperT, typename ObjectT>
void Access(const WrapperT& wrapper) {
if constexpr (std::is_base_of_v<JS::ArrayBufferOrView, ObjectT>) {
(void)wrapper.asObject();
} else {
(void)*wrapper;
}
}
template <typename WrapperT, typename ObjectT>
bool TestReadBarrierUnmarksGray() {
Rooted<ObjectT> obj(cx, CreateTenuredGCThing<ObjectT>(cx));
WrapperT wrapper(obj);
CHECK(GetColor(obj) == gc::CellColor::White);
Access<WrapperT, ObjectT>(wrapper);
CHECK(GetColor(obj) == gc::CellColor::White);
MakeGray(obj);
Access<WrapperT, ObjectT>(wrapper);
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
}
// Execute thunk |f| between two slices of an incremental GC controlled by zeal
// mode |mode|.
template <typename F>
bool CallDuringIncrementalGC(uint32_t mode, F&& f) {
#ifndef JS_GC_ZEAL
fprintf(stderr, "This test requires building with --enable-gczeal\n");
#else
AutoGCParameter incremental(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
const int64_t BudgetMS = 10000; // 10S should be long enough for anyone.
JS::SetGCZeal(cx, mode, 0);
JS::PrepareZoneForGC(cx, js::GetContextZone(cx));
JS::SliceBudget budget{JS::TimeBudget(BudgetMS)};
JS::StartIncrementalGC(cx, JS::GCOptions(), JS::GCReason::DEBUG_GC, budget);
CHECK(JS::IsIncrementalGCInProgress(cx));
CHECK(f());
JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
#endif
return true;
}
template <typename WrapperT, typename ObjectT>
bool TestReadBarrierMarksBlack(bool fromWhite) {
AutoLeaveZeal noZeal(cx);
// Create an object and hide it from the hazard analysis.
void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
CHECK(ptr);
CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
CHECK(JS::IsIncrementalBarrierNeeded(cx));
auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
WrapperT wrapper(obj);
CHECK(GetColor(obj) == gc::CellColor::White);
if (!fromWhite) {
MakeGray(obj);
}
Access<WrapperT, ObjectT>(wrapper);
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
});
return true;
}
template <typename WrapperT, typename ObjectT>
bool TestConstructorBarrier() {
AutoLeaveZeal noZeal(cx);
// Create an object and hide it from the hazard analysis.
void* ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
CHECK(ptr);
CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
CHECK(JS::IsIncrementalBarrierNeeded(cx));
auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
WrapperT wrapper(obj);
CHECK(GetColor(obj) == gc::CellColor::White);
WrapperT copiedWrapper(wrapper);
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
});
ptr = CreateHiddenTenuredGCThing<ObjectT>(cx);
CHECK(ptr);
CallDuringIncrementalGC(9 /* YieldBeforeSweeping */, [&]() -> bool {
CHECK(JS::IsIncrementalBarrierNeeded(cx));
auto obj = RecoverHiddenGCThing<ObjectT>(ptr);
WrapperT wrapper(obj);
CHECK(GetColor(obj) == gc::CellColor::White);
WrapperT movedWrapper(std::move(wrapper));
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
});
return true;
}
template <typename WrapperT, typename ObjectT>
bool TestUnbarrieredOperations(ObjectT obj, ObjectT obj2, WrapperT& wrapper,
WrapperT& wrapper2) {
(void)bool(wrapper);
(void)bool(wrapper2);
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
int x = 0;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += obj == obj2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += obj == wrapper2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += wrapper == obj2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += wrapper == wrapper2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
CHECK(x == 0);
x += obj != obj2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += obj != wrapper2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += wrapper != obj2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
x += wrapper != wrapper2;
CHECK(GetColor(obj) == gc::CellColor::Gray);
CHECK(GetColor(obj2) == gc::CellColor::Gray);
CHECK(x == 4);
return true;
}
END_TEST(testGCHeapReadBarriers)
using ObjectVector = Vector<JSObject*, 0, SystemAllocPolicy>;
// Test pre-barrier implementation on wrapper types. The following wrapper types
// have a pre-barrier:
// - GCPtr
// - HeapPtr
// - PreBarriered
BEGIN_TEST(testGCHeapPreBarriers) {
AutoLeaveZeal nozeal(cx);
AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
// Create a bunch of objects. These are unrooted and will be used to test
// whether barriers have fired by checking whether they have been marked
// black.
size_t objectCount = 100; // Increase this if necessary when adding tests.
ObjectVector testObjects;
for (size_t i = 0; i < objectCount; i++) {
JSObject* obj = CreateTenuredGCThing<JSObject*>(cx);
CHECK(obj);
CHECK(testObjects.append(obj));
}
// Start an incremental GC so we can detect if we cause barriers to fire, as
// these will mark objects black.
JS::PrepareForFullGC(cx);
JS::SliceBudget budget(JS::WorkBudget(1));
gc::GCRuntime* gc = &cx->runtime()->gc;
gc->startDebugGC(JS::GCOptions::Normal, budget);
while (gc->state() != gc::State::Mark) {
gc->debugGCSlice(budget);
}
MOZ_ASSERT(cx->zone()->needsIncrementalBarrier());
TestWrapper<HeapPtr<JSObject*>>(testObjects);
TestWrapper<PreBarriered<JSObject*>>(testObjects);
// GCPtr is different because 1) it doesn't support move operations as it's
// supposed to be part of a GC thing and 2) it doesn't perform a pre-barrier
// in its destructor because these are only destroyed as part of a GC where
// the barrier is unnecessary.
TestGCPtr(testObjects);
gc::FinishGC(cx, JS::GCReason::API);
return true;
}
template <typename Wrapper>
bool TestWrapper(ObjectVector& testObjects) {
CHECK(TestCopyConstruction<Wrapper>(testObjects.popCopy()));
CHECK(TestMoveConstruction<Wrapper>(testObjects.popCopy()));
CHECK(TestAssignment<Wrapper>(testObjects.popCopy(), testObjects.popCopy()));
CHECK(TestMoveAssignment<Wrapper>(testObjects.popCopy(),
testObjects.popCopy()));
return true;
}
template <typename Wrapper>
bool TestCopyConstruction(JSObject* obj) {
CHECK(GetColor(obj) == gc::CellColor::White);
{
Wrapper wrapper1(obj);
Wrapper wrapper2(wrapper1);
CHECK(wrapper1 == obj);
CHECK(wrapper2 == obj);
CHECK(GetColor(obj) == gc::CellColor::White);
}
// Check destructor performs pre-barrier.
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
}
template <typename Wrapper>
bool TestMoveConstruction(JSObject* obj) {
CHECK(GetColor(obj) == gc::CellColor::White);
{
Wrapper wrapper1(obj);
MakeGray(obj); // Check that we allow move of gray GC thing.
Wrapper wrapper2(std::move(wrapper1));
CHECK(!wrapper1);
CHECK(wrapper2 == obj);
CHECK(GetColor(obj) == gc::CellColor::Gray);
}
// Check destructor performs pre-barrier.
CHECK(GetColor(obj) == gc::CellColor::Black);
return true;
}
template <typename Wrapper>
bool TestAssignment(JSObject* obj1, JSObject* obj2) {
CHECK(GetColor(obj1) == gc::CellColor::White);
CHECK(GetColor(obj2) == gc::CellColor::White);
{
Wrapper wrapper1(obj1);
Wrapper wrapper2(obj2);
wrapper2 = wrapper1;
CHECK(wrapper1 == obj1);
CHECK(wrapper2 == obj1);
CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired.
CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
}
// Check destructor performs pre-barrier.
CHECK(GetColor(obj1) == gc::CellColor::Black);
return true;
}
template <typename Wrapper>
bool TestMoveAssignment(JSObject* obj1, JSObject* obj2) {
CHECK(GetColor(obj1) == gc::CellColor::White);
CHECK(GetColor(obj2) == gc::CellColor::White);
{
Wrapper wrapper1(obj1);
Wrapper wrapper2(obj2);
MakeGray(obj1); // Check we allow move of gray thing.
wrapper2 = std::move(wrapper1);
CHECK(!wrapper1);
CHECK(wrapper2 == obj1);
CHECK(GetColor(obj1) == gc::CellColor::Gray); // No barrier fired.
CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
}
// Check destructor performs pre-barrier.
CHECK(GetColor(obj1) == gc::CellColor::Black);
return true;
}
bool TestGCPtr(ObjectVector& testObjects) {
CHECK(TestGCPtrCopyConstruction(testObjects.popCopy()));
CHECK(TestGCPtrAssignment(testObjects.popCopy(), testObjects.popCopy()));
return true;
}
bool TestGCPtrCopyConstruction(JSObject* obj) {
CHECK(GetColor(obj) == gc::CellColor::White);
{
// Let us destroy GCPtrs ourselves for testing purposes.
gc::AutoSetThreadIsFinalizing threadIsFinalizing;
GCPtr<JSObject*> wrapper1(obj);
GCPtr<JSObject*> wrapper2(wrapper1);
CHECK(wrapper1 == obj);
CHECK(wrapper2 == obj);
CHECK(GetColor(obj) == gc::CellColor::White);
}
// GCPtr doesn't perform pre-barrier in destructor.
CHECK(GetColor(obj) == gc::CellColor::White);
return true;
}
bool TestGCPtrAssignment(JSObject* obj1, JSObject* obj2) {
CHECK(GetColor(obj1) == gc::CellColor::White);
CHECK(GetColor(obj2) == gc::CellColor::White);
{
// Let us destroy GCPtrs ourselves for testing purposes.
gc::AutoSetThreadIsFinalizing threadIsFinalizing;
GCPtr<JSObject*> wrapper1(obj1);
GCPtr<JSObject*> wrapper2(obj2);
wrapper2 = wrapper1;
CHECK(wrapper1 == obj1);
CHECK(wrapper2 == obj1);
CHECK(GetColor(obj1) == gc::CellColor::White); // No barrier fired.
CHECK(GetColor(obj2) == gc::CellColor::Black); // Pre barrier fired.
}
// GCPtr doesn't perform pre-barrier in destructor.
CHECK(GetColor(obj1) == gc::CellColor::White);
return true;
}
END_TEST(testGCHeapPreBarriers)