blob: 028194b466432e25796879275c1bb11658c5a242 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "platform/heap/Handle.h"
#include "platform/heap/Heap.h"
#include "platform/heap/HeapLinkedStack.h"
#include "platform/heap/HeapTerminatedArrayBuilder.h"
#include "platform/heap/ThreadState.h"
#include "platform/heap/Visitor.h"
#include "public/platform/Platform.h"
#include "wtf/HashTraits.h"
#include "wtf/LinkedHashSet.h"
#include <gtest/gtest.h>
namespace blink {
class IntWrapper : public GarbageCollectedFinalized<IntWrapper> {
public:
static IntWrapper* create(int x)
{
return new IntWrapper(x);
}
virtual ~IntWrapper()
{
++s_destructorCalls;
}
static int s_destructorCalls;
static void trace(Visitor*) { }
int value() const { return m_x; }
bool operator==(const IntWrapper& other) const { return other.value() == value(); }
unsigned hash() { return IntHash<int>::hash(m_x); }
protected:
IntWrapper(int x) : m_x(x) { }
private:
IntWrapper();
int m_x;
};
USED_FROM_MULTIPLE_THREADS(IntWrapper);
class ThreadMarker {
public:
ThreadMarker() : m_creatingThread(reinterpret_cast<ThreadState*>(0)), m_num(0) { }
ThreadMarker(unsigned i) : m_creatingThread(ThreadState::current()), m_num(i) { }
ThreadMarker(WTF::HashTableDeletedValueType deleted) : m_creatingThread(reinterpret_cast<ThreadState*>(-1)), m_num(0) { }
~ThreadMarker()
{
EXPECT_TRUE((m_creatingThread == ThreadState::current())
|| (m_creatingThread == reinterpret_cast<ThreadState*>(0))
|| (m_creatingThread == reinterpret_cast<ThreadState*>(-1)));
}
bool isHashTableDeletedValue() const { return m_creatingThread == reinterpret_cast<ThreadState*>(-1); }
bool operator==(const ThreadMarker& other) const { return other.m_creatingThread == m_creatingThread && other.m_num == m_num; }
ThreadState* m_creatingThread;
unsigned m_num;
};
struct ThreadMarkerHash {
static unsigned hash(const ThreadMarker& key)
{
return static_cast<unsigned>(reinterpret_cast<uintptr_t>(key.m_creatingThread) + key.m_num);
}
static bool equal(const ThreadMarker& a, const ThreadMarker& b)
{
return a == b;
}
static const bool safeToCompareToEmptyOrDeleted = false;
};
typedef std::pair<Member<IntWrapper>, WeakMember<IntWrapper> > StrongWeakPair;
struct PairWithWeakHandling : public StrongWeakPair {
ALLOW_ONLY_INLINE_ALLOCATION();
public:
// Regular constructor.
PairWithWeakHandling(IntWrapper* one, IntWrapper* two)
: StrongWeakPair(one, two)
{
ASSERT(one); // We use null first field to indicate empty slots in the hash table.
}
// The HashTable (via the HashTrait) calls this constructor with a
// placement new to mark slots in the hash table as being deleted. We will
// never call trace or the destructor on these slots. We mark ourselves deleted
// with a pointer to -1 in the first field.
PairWithWeakHandling(WTF::HashTableDeletedValueType)
: StrongWeakPair(reinterpret_cast<IntWrapper*>(-1), nullptr)
{
}
// Used by the HashTable (via the HashTrait) to skip deleted slots in the
// table. Recognizes objects that were 'constructed' using the above
// constructor.
bool isHashTableDeletedValue() const { return first == reinterpret_cast<IntWrapper*>(-1); }
// Since we don't allocate independent objects of this type, we don't need
// a regular trace method. Instead, we use a traceInCollection method. If
// the entry should be deleted from the collection we return true and don't
// trace the strong pointer.
bool traceInCollection(Visitor* visitor, WTF::ShouldWeakPointersBeMarkedStrongly strongify)
{
visitor->traceInCollection(second, strongify);
if (!visitor->isAlive(second))
return true;
visitor->trace(first);
return false;
}
};
}
namespace WTF {
template<typename T> struct DefaultHash;
template<> struct DefaultHash<blink::ThreadMarker> {
typedef blink::ThreadMarkerHash Hash;
};
// ThreadMarkerHash is the default hash for ThreadMarker
template<> struct HashTraits<blink::ThreadMarker> : GenericHashTraits<blink::ThreadMarker> {
static const bool emptyValueIsZero = true;
static void constructDeletedValue(blink::ThreadMarker& slot) { new (NotNull, &slot) blink::ThreadMarker(HashTableDeletedValue); }
static bool isDeletedValue(const blink::ThreadMarker& slot) { return slot.isHashTableDeletedValue(); }
};
// The hash algorithm for our custom pair class is just the standard double
// hash for pairs. Note that this means you can't mutate either of the parts of
// the pair while they are in the hash table, as that would change their hash
// code and thus their preferred placement in the table.
template<> struct DefaultHash<blink::PairWithWeakHandling> {
typedef PairHash<blink::Member<blink::IntWrapper>, blink::WeakMember<blink::IntWrapper> > Hash;
};
// Custom traits for the pair. These are weakness handling traits, which means
// PairWithWeakHandling must implement the traceInCollection method.
// In addition, these traits are concerned with the two magic values for the
// object, that represent empty and deleted slots in the hash table. The
// SimpleClassHashTraits allow empty slots in the table to be initialzed with
// memset to zero, and we use -1 in the first part of the pair to represent
// deleted slots.
template<> struct HashTraits<blink::PairWithWeakHandling> : blink::WeakHandlingHashTraits<blink::PairWithWeakHandling> {
static const bool needsDestruction = false;
static const bool hasIsEmptyValueFunction = true;
static bool isEmptyValue(const blink::PairWithWeakHandling& value) { return !value.first; }
static void constructDeletedValue(blink::PairWithWeakHandling& slot) { new (NotNull, &slot) blink::PairWithWeakHandling(HashTableDeletedValue); }
static bool isDeletedValue(const blink::PairWithWeakHandling& value) { return value.isHashTableDeletedValue(); }
};
}
namespace blink {
class TestGCScope {
public:
explicit TestGCScope(ThreadState::StackState state)
: m_state(ThreadState::current())
, m_safePointScope(state)
, m_parkedAllThreads(false)
{
m_state->checkThread();
EXPECT_FALSE(m_state->isInGC());
if (LIKELY(ThreadState::stopThreads())) {
m_state->enterGC();
m_parkedAllThreads = true;
}
}
bool allThreadsParked() { return m_parkedAllThreads; }
~TestGCScope()
{
// Only cleanup if we parked all threads in which case the GC happened
// and we need to resume the other threads.
if (LIKELY(m_parkedAllThreads)) {
m_state->leaveGC();
EXPECT_FALSE(m_state->isInGC());
ThreadState::resumeThreads();
}
}
private:
ThreadState* m_state;
ThreadState::SafePointScope m_safePointScope;
bool m_parkedAllThreads; // False if we fail to park all threads
};
static void getHeapStats(HeapStats* stats)
{
TestGCScope scope(ThreadState::NoHeapPointersOnStack);
EXPECT_TRUE(scope.allThreadsParked());
Heap::getStats(stats);
}
#define DEFINE_VISITOR_METHODS(Type) \
virtual void mark(const Type* object, TraceCallback callback) OVERRIDE \
{ \
if (object) \
m_count++; \
} \
virtual bool isMarked(const Type*) OVERRIDE { return false; }
class CountingVisitor : public Visitor {
public:
CountingVisitor()
: m_count(0)
{
}
virtual void mark(const void* object, TraceCallback) OVERRIDE
{
if (object)
m_count++;
}
virtual void mark(HeapObjectHeader* header, TraceCallback callback) OVERRIDE
{
ASSERT(header->payload());
m_count++;
}
virtual void mark(FinalizedHeapObjectHeader* header, TraceCallback callback) OVERRIDE
{
ASSERT(header->payload());
m_count++;
}
virtual void registerWeakMembers(const void*, const void*, WeakPointerCallback) OVERRIDE { }
virtual void registerWeakTable(const void*, EphemeronCallback, EphemeronCallback) OVERRIDE { }
#if ENABLE(ASSERT)
virtual bool weakTableRegistered(const void*) OVERRIDE { return false; }
#endif
virtual void registerWeakCell(void**, WeakPointerCallback) OVERRIDE { }
virtual bool isMarked(const void*) OVERRIDE { return false; }
FOR_EACH_TYPED_HEAP(DEFINE_VISITOR_METHODS)
size_t count() { return m_count; }
void reset() { m_count = 0; }
private:
size_t m_count;
};
class SimpleObject : public GarbageCollected<SimpleObject> {
public:
static SimpleObject* create() { return new SimpleObject(); }
void trace(Visitor*) { }
char getPayload(int i) { return payload[i]; }
// This virtual method is unused but it is here to make sure
// that this object has a vtable. This object is used
// as the super class for objects that also have garbage
// collected mixins and having a virtual here makes sure
// that adjustment is needed both for marking and for isAlive
// checks.
virtual void virtualMethod() { }
protected:
SimpleObject() { }
char payload[64];
};
#undef DEFINE_VISITOR_METHODS
class HeapTestSuperClass : public GarbageCollectedFinalized<HeapTestSuperClass> {
public:
static HeapTestSuperClass* create()
{
return new HeapTestSuperClass();
}
virtual ~HeapTestSuperClass()
{
++s_destructorCalls;
}
static int s_destructorCalls;
void trace(Visitor*) { }
protected:
HeapTestSuperClass() { }
};
int HeapTestSuperClass::s_destructorCalls = 0;
class HeapTestOtherSuperClass {
public:
int payload;
};
static const size_t classMagic = 0xABCDDBCA;
class HeapTestSubClass : public HeapTestOtherSuperClass, public HeapTestSuperClass {
public:
static HeapTestSubClass* create()
{
return new HeapTestSubClass();
}
virtual ~HeapTestSubClass()
{
EXPECT_EQ(classMagic, m_magic);
++s_destructorCalls;
}
static int s_destructorCalls;
private:
HeapTestSubClass() : m_magic(classMagic) { }
const size_t m_magic;
};
int HeapTestSubClass::s_destructorCalls = 0;
class HeapAllocatedArray : public GarbageCollected<HeapAllocatedArray> {
public:
HeapAllocatedArray()
{
for (int i = 0; i < s_arraySize; ++i) {
m_array[i] = i % 128;
}
}
int8_t at(size_t i) { return m_array[i]; }
void trace(Visitor*) { }
private:
static const int s_arraySize = 1000;
int8_t m_array[s_arraySize];
};
// Do several GCs to make sure that later GCs don't free up old memory from
// previously run tests in this process.
static void clearOutOldGarbage(HeapStats* heapStats)
{
while (true) {
getHeapStats(heapStats);
size_t used = heapStats->totalObjectSpace();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
getHeapStats(heapStats);
if (heapStats->totalObjectSpace() >= used)
break;
}
}
class OffHeapInt : public RefCounted<OffHeapInt> {
public:
static RefPtr<OffHeapInt> create(int x)
{
return adoptRef(new OffHeapInt(x));
}
virtual ~OffHeapInt()
{
++s_destructorCalls;
}
static int s_destructorCalls;
int value() const { return m_x; }
bool operator==(const OffHeapInt& other) const { return other.value() == value(); }
unsigned hash() { return IntHash<int>::hash(m_x); }
void voidFunction() { }
protected:
OffHeapInt(int x) : m_x(x) { }
private:
OffHeapInt();
int m_x;
};
int IntWrapper::s_destructorCalls = 0;
int OffHeapInt::s_destructorCalls = 0;
class ThreadedTesterBase {
protected:
static void test(ThreadedTesterBase* tester)
{
for (int i = 0; i < numberOfThreads; i++)
createThread(&threadFunc, tester, "testing thread");
while (tester->m_threadsToFinish) {
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
yield();
}
delete tester;
}
virtual void runThread() = 0;
protected:
static const int numberOfThreads = 10;
static const int gcPerThread = 5;
static const int numberOfAllocations = 50;
ThreadedTesterBase() : m_gcCount(0), m_threadsToFinish(numberOfThreads)
{
}
virtual ~ThreadedTesterBase()
{
}
inline bool done() const { return m_gcCount >= numberOfThreads * gcPerThread; }
volatile int m_gcCount;
volatile int m_threadsToFinish;
private:
static void threadFunc(void* data)
{
reinterpret_cast<ThreadedTesterBase*>(data)->runThread();
}
};
class ThreadedHeapTester : public ThreadedTesterBase {
public:
static void test()
{
ThreadedTesterBase::test(new ThreadedHeapTester);
}
protected:
virtual void runThread() OVERRIDE
{
ThreadState::attach();
int gcCount = 0;
while (!done()) {
ThreadState::current()->safePoint(ThreadState::NoHeapPointersOnStack);
{
Persistent<IntWrapper> wrapper;
typedef Persistent<IntWrapper, GlobalPersistents> GlobalIntWrapperPersistent;
OwnPtr<GlobalIntWrapperPersistent> globalPersistent = adoptPtr(new GlobalIntWrapperPersistent(IntWrapper::create(0x0ed0cabb)));
for (int i = 0; i < numberOfAllocations; i++) {
wrapper = IntWrapper::create(0x0bbac0de);
if (!(i % 10)) {
globalPersistent = adoptPtr(new GlobalIntWrapperPersistent(IntWrapper::create(0x0ed0cabb)));
}
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
yield();
}
if (gcCount < gcPerThread) {
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
gcCount++;
atomicIncrement(&m_gcCount);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(wrapper->value(), 0x0bbac0de);
EXPECT_EQ((*globalPersistent)->value(), 0x0ed0cabb);
}
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
yield();
}
ThreadState::detach();
atomicDecrement(&m_threadsToFinish);
}
};
class ThreadedWeaknessTester : public ThreadedTesterBase {
public:
static void test()
{
ThreadedTesterBase::test(new ThreadedWeaknessTester);
}
private:
virtual void runThread() OVERRIDE
{
ThreadState::attach();
int gcCount = 0;
while (!done()) {
ThreadState::current()->safePoint(ThreadState::NoHeapPointersOnStack);
{
Persistent<HeapHashMap<ThreadMarker, WeakMember<IntWrapper> > > weakMap = new HeapHashMap<ThreadMarker, WeakMember<IntWrapper> >;
PersistentHeapHashMap<ThreadMarker, WeakMember<IntWrapper> > weakMap2;
for (int i = 0; i < numberOfAllocations; i++) {
weakMap->add(static_cast<unsigned>(i), IntWrapper::create(0));
weakMap2.add(static_cast<unsigned>(i), IntWrapper::create(0));
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
yield();
}
if (gcCount < gcPerThread) {
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
gcCount++;
atomicIncrement(&m_gcCount);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_TRUE(weakMap->isEmpty());
EXPECT_TRUE(weakMap2.isEmpty());
}
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
yield();
}
ThreadState::detach();
atomicDecrement(&m_threadsToFinish);
}
};
// The accounting for memory includes the memory used by rounding up object
// sizes. This is done in a different way on 32 bit and 64 bit, so we have to
// have some slack in the tests.
template<typename T>
void CheckWithSlack(T expected, T actual, int slack)
{
EXPECT_LE(expected, actual);
EXPECT_GE((intptr_t)expected + slack, (intptr_t)actual);
}
class TraceCounter : public GarbageCollectedFinalized<TraceCounter> {
public:
static TraceCounter* create()
{
return new TraceCounter();
}
void trace(Visitor*) { m_traceCount++; }
int traceCount() { return m_traceCount; }
private:
TraceCounter()
: m_traceCount(0)
{
}
int m_traceCount;
};
class ClassWithMember : public GarbageCollected<ClassWithMember> {
public:
static ClassWithMember* create()
{
return new ClassWithMember();
}
void trace(Visitor* visitor)
{
EXPECT_TRUE(visitor->isMarked(this));
if (!traceCount())
EXPECT_FALSE(visitor->isMarked(m_traceCounter));
else
EXPECT_TRUE(visitor->isMarked(m_traceCounter));
visitor->trace(m_traceCounter);
}
int traceCount() { return m_traceCounter->traceCount(); }
private:
ClassWithMember()
: m_traceCounter(TraceCounter::create())
{ }
Member<TraceCounter> m_traceCounter;
};
class SimpleFinalizedObject : public GarbageCollectedFinalized<SimpleFinalizedObject> {
public:
static SimpleFinalizedObject* create()
{
return new SimpleFinalizedObject();
}
~SimpleFinalizedObject()
{
++s_destructorCalls;
}
static int s_destructorCalls;
void trace(Visitor*) { }
private:
SimpleFinalizedObject() { }
};
int SimpleFinalizedObject::s_destructorCalls = 0;
class Node : public GarbageCollected<Node> {
public:
static Node* create()
{
return new Node();
}
void trace(Visitor*) { }
private:
Node() { }
};
class Bar : public GarbageCollectedFinalized<Bar> {
public:
static Bar* create()
{
return new Bar();
}
void finalizeGarbageCollectedObject()
{
EXPECT_TRUE(m_magic == magic);
m_magic = 0;
s_live--;
}
bool hasBeenFinalized() const { return !m_magic; }
virtual void trace(Visitor* visitor) { }
static unsigned s_live;
protected:
static const int magic = 1337;
int m_magic;
Bar()
: m_magic(magic)
{
s_live++;
}
};
unsigned Bar::s_live = 0;
class Baz : public GarbageCollected<Baz> {
public:
static Baz* create(Bar* bar)
{
return new Baz(bar);
}
void trace(Visitor* visitor)
{
visitor->trace(m_bar);
}
void clear() { m_bar.release(); }
// willFinalize is called by FinalizationObserver.
void willFinalize()
{
EXPECT_TRUE(!m_bar->hasBeenFinalized());
}
private:
explicit Baz(Bar* bar)
: m_bar(bar)
{
}
Member<Bar> m_bar;
};
class Foo : public Bar {
public:
static Foo* create(Bar* bar)
{
return new Foo(bar);
}
static Foo* create(Foo* foo)
{
return new Foo(foo);
}
virtual void trace(Visitor* visitor) OVERRIDE
{
if (m_pointsToFoo)
visitor->mark(static_cast<Foo*>(m_bar));
else
visitor->mark(m_bar);
}
private:
Foo(Bar* bar)
: Bar()
, m_bar(bar)
, m_pointsToFoo(false)
{
}
Foo(Foo* foo)
: Bar()
, m_bar(foo)
, m_pointsToFoo(true)
{
}
Bar* m_bar;
bool m_pointsToFoo;
};
class Bars : public Bar {
public:
static Bars* create()
{
return new Bars();
}
virtual void trace(Visitor* visitor) OVERRIDE
{
for (unsigned i = 0; i < m_width; i++)
visitor->trace(m_bars[i]);
}
unsigned getWidth() const
{
return m_width;
}
static const unsigned width = 7500;
private:
Bars() : m_width(0)
{
for (unsigned i = 0; i < width; i++) {
m_bars[i] = Bar::create();
m_width++;
}
}
unsigned m_width;
Member<Bar> m_bars[width];
};
class ConstructorAllocation : public GarbageCollected<ConstructorAllocation> {
public:
static ConstructorAllocation* create() { return new ConstructorAllocation(); }
void trace(Visitor* visitor) { visitor->trace(m_intWrapper); }
private:
ConstructorAllocation()
{
m_intWrapper = IntWrapper::create(42);
}
Member<IntWrapper> m_intWrapper;
};
class LargeObject : public GarbageCollectedFinalized<LargeObject> {
public:
~LargeObject()
{
s_destructorCalls++;
}
static LargeObject* create() { return new LargeObject(); }
char get(size_t i) { return m_data[i]; }
void set(size_t i, char c) { m_data[i] = c; }
size_t length() { return s_length; }
void trace(Visitor* visitor)
{
visitor->trace(m_intWrapper);
}
static int s_destructorCalls;
private:
static const size_t s_length = 1024*1024;
LargeObject()
{
m_intWrapper = IntWrapper::create(23);
}
Member<IntWrapper> m_intWrapper;
char m_data[s_length];
};
int LargeObject::s_destructorCalls = 0;
class RefCountedAndGarbageCollected : public RefCountedGarbageCollected<RefCountedAndGarbageCollected> {
public:
static PassRefPtr<RefCountedAndGarbageCollected> create()
{
return adoptRef(new RefCountedAndGarbageCollected());
}
~RefCountedAndGarbageCollected()
{
++s_destructorCalls;
}
// These are here with their default implementations so you can break in
// them in the debugger.
void ref() { RefCountedGarbageCollected<RefCountedAndGarbageCollected>::ref(); }
void deref() { RefCountedGarbageCollected<RefCountedAndGarbageCollected>::deref(); }
void trace(Visitor*) { }
static int s_destructorCalls;
private:
RefCountedAndGarbageCollected()
{
}
};
int RefCountedAndGarbageCollected::s_destructorCalls = 0;
class RefCountedAndGarbageCollected2 : public HeapTestOtherSuperClass, public RefCountedGarbageCollected<RefCountedAndGarbageCollected2> {
public:
static RefCountedAndGarbageCollected2* create()
{
return adoptRefCountedGarbageCollected(new RefCountedAndGarbageCollected2());
}
~RefCountedAndGarbageCollected2()
{
++s_destructorCalls;
}
void trace(Visitor*) { }
static int s_destructorCalls;
private:
RefCountedAndGarbageCollected2()
{
}
};
int RefCountedAndGarbageCollected2::s_destructorCalls = 0;
#define DEFINE_VISITOR_METHODS(Type) \
virtual void mark(const Type* object, TraceCallback callback) OVERRIDE \
{ \
mark(object); \
} \
class RefCountedGarbageCollectedVisitor : public CountingVisitor {
public:
RefCountedGarbageCollectedVisitor(int expected, void** objects)
: m_count(0)
, m_expectedCount(expected)
, m_expectedObjects(objects)
{
}
void mark(const void* ptr) { markNoTrace(ptr); }
virtual void markNoTrace(const void* ptr)
{
if (!ptr)
return;
if (m_count < m_expectedCount)
EXPECT_TRUE(expectedObject(ptr));
else
EXPECT_FALSE(expectedObject(ptr));
m_count++;
}
virtual void mark(const void* ptr, TraceCallback) OVERRIDE
{
mark(ptr);
}
virtual void mark(HeapObjectHeader* header, TraceCallback callback) OVERRIDE
{
mark(header->payload());
}
virtual void mark(FinalizedHeapObjectHeader* header, TraceCallback callback) OVERRIDE
{
mark(header->payload());
}
bool validate() { return m_count >= m_expectedCount; }
void reset() { m_count = 0; }
FOR_EACH_TYPED_HEAP(DEFINE_VISITOR_METHODS)
private:
bool expectedObject(const void* ptr)
{
for (int i = 0; i < m_expectedCount; i++) {
if (m_expectedObjects[i] == ptr)
return true;
}
return false;
}
int m_count;
int m_expectedCount;
void** m_expectedObjects;
};
#undef DEFINE_VISITOR_METHODS
class Weak : public Bar {
public:
static Weak* create(Bar* strong, Bar* weak)
{
return new Weak(strong, weak);
}
virtual void trace(Visitor* visitor) OVERRIDE
{
visitor->trace(m_strongBar);
visitor->registerWeakMembers(this, zapWeakMembers);
}
static void zapWeakMembers(Visitor* visitor, void* self)
{
reinterpret_cast<Weak*>(self)->zapWeakMembers(visitor);
}
bool strongIsThere() { return !!m_strongBar; }
bool weakIsThere() { return !!m_weakBar; }
private:
Weak(Bar* strongBar, Bar* weakBar)
: Bar()
, m_strongBar(strongBar)
, m_weakBar(weakBar)
{
}
void zapWeakMembers(Visitor* visitor)
{
if (!visitor->isAlive(m_weakBar))
m_weakBar = 0;
}
Member<Bar> m_strongBar;
Bar* m_weakBar;
};
class WithWeakMember : public Bar {
public:
static WithWeakMember* create(Bar* strong, Bar* weak)
{
return new WithWeakMember(strong, weak);
}
virtual void trace(Visitor* visitor) OVERRIDE
{
visitor->trace(m_strongBar);
visitor->trace(m_weakBar);
}
bool strongIsThere() { return !!m_strongBar; }
bool weakIsThere() { return !!m_weakBar; }
private:
WithWeakMember(Bar* strongBar, Bar* weakBar)
: Bar()
, m_strongBar(strongBar)
, m_weakBar(weakBar)
{
}
Member<Bar> m_strongBar;
WeakMember<Bar> m_weakBar;
};
class Observable : public GarbageCollectedFinalized<Observable> {
public:
static Observable* create(Bar* bar) { return new Observable(bar); }
~Observable() { m_wasDestructed = true; }
void trace(Visitor* visitor) { visitor->trace(m_bar); }
// willFinalize is called by FinalizationObserver. willFinalize can touch
// other on-heap objects.
void willFinalize()
{
EXPECT_FALSE(m_wasDestructed);
EXPECT_FALSE(m_bar->hasBeenFinalized());
}
private:
explicit Observable(Bar* bar)
: m_bar(bar)
, m_wasDestructed(false)
{
}
Member<Bar> m_bar;
bool m_wasDestructed;
};
template <typename T> class FinalizationObserver : public GarbageCollected<FinalizationObserver<T> > {
public:
static FinalizationObserver* create(T* data) { return new FinalizationObserver(data); }
bool didCallWillFinalize() const { return m_didCallWillFinalize; }
void trace(Visitor* visitor)
{
visitor->registerWeakMembers(this, zapWeakMembers);
}
private:
FinalizationObserver(T* data)
: m_data(data)
, m_didCallWillFinalize(false)
{
}
static void zapWeakMembers(Visitor* visitor, void* self)
{
FinalizationObserver* o = reinterpret_cast<FinalizationObserver*>(self);
if (o->m_data && !visitor->isAlive(o->m_data)) {
o->m_data->willFinalize();
o->m_data = nullptr;
o->m_didCallWillFinalize = true;
}
}
WeakMember<T> m_data;
bool m_didCallWillFinalize;
};
class FinalizationObserverWithHashMap {
public:
typedef HeapHashMap<WeakMember<Observable>, OwnPtr<FinalizationObserverWithHashMap> > ObserverMap;
explicit FinalizationObserverWithHashMap(Observable& target) : m_target(target) { }
~FinalizationObserverWithHashMap()
{
m_target.willFinalize();
s_didCallWillFinalize = true;
}
static ObserverMap& observe(Observable& target)
{
ObserverMap& map = observers();
ObserverMap::AddResult result = map.add(&target, nullptr);
if (result.isNewEntry)
result.storedValue->value = adoptPtr(new FinalizationObserverWithHashMap(target));
else
ASSERT(result.storedValue->value);
return map;
}
static bool s_didCallWillFinalize;
private:
static ObserverMap& observers()
{
DEFINE_STATIC_LOCAL(Persistent<ObserverMap>, observerMap, ());
if (!observerMap)
observerMap = new ObserverMap();
return *observerMap;
}
Observable& m_target;
};
bool FinalizationObserverWithHashMap::s_didCallWillFinalize = false;
class SuperClass;
class PointsBack : public RefCountedWillBeGarbageCollectedFinalized<PointsBack> {
public:
static PassRefPtrWillBeRawPtr<PointsBack> create()
{
return adoptRefWillBeNoop(new PointsBack());
}
~PointsBack()
{
--s_aliveCount;
}
void setBackPointer(SuperClass* backPointer)
{
m_backPointer = backPointer;
}
SuperClass* backPointer() const { return m_backPointer; }
void trace(Visitor* visitor)
{
#if ENABLE_OILPAN
visitor->trace(m_backPointer);
#endif
}
static int s_aliveCount;
private:
PointsBack() : m_backPointer(nullptr)
{
++s_aliveCount;
}
RawPtrWillBeWeakMember<SuperClass> m_backPointer;
};
int PointsBack::s_aliveCount = 0;
class SuperClass : public RefCountedWillBeGarbageCollectedFinalized<SuperClass> {
public:
static PassRefPtrWillBeRawPtr<SuperClass> create(PassRefPtrWillBeRawPtr<PointsBack> pointsBack)
{
return adoptRefWillBeNoop(new SuperClass(pointsBack));
}
virtual ~SuperClass()
{
#if !ENABLE_OILPAN
m_pointsBack->setBackPointer(0);
#endif
--s_aliveCount;
}
void doStuff(PassRefPtrWillBeRawPtr<SuperClass> targetPass, PointsBack* pointsBack, int superClassCount)
{
RefPtrWillBeRawPtr<SuperClass> target = targetPass;
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(pointsBack, target->pointsBack());
EXPECT_EQ(superClassCount, SuperClass::s_aliveCount);
}
virtual void trace(Visitor* visitor)
{
#if ENABLE_OILPAN
visitor->trace(m_pointsBack);
#endif
}
PointsBack* pointsBack() const { return m_pointsBack.get(); }
static int s_aliveCount;
protected:
explicit SuperClass(PassRefPtrWillBeRawPtr<PointsBack> pointsBack)
: m_pointsBack(pointsBack)
{
m_pointsBack->setBackPointer(this);
++s_aliveCount;
}
private:
RefPtrWillBeMember<PointsBack> m_pointsBack;
};
int SuperClass::s_aliveCount = 0;
class SubData : public NoBaseWillBeGarbageCollectedFinalized<SubData> {
public:
SubData() { ++s_aliveCount; }
~SubData() { --s_aliveCount; }
void trace(Visitor*) { }
static int s_aliveCount;
};
int SubData::s_aliveCount = 0;
class SubClass : public SuperClass {
public:
static PassRefPtrWillBeRawPtr<SubClass> create(PassRefPtrWillBeRawPtr<PointsBack> pointsBack)
{
return adoptRefWillBeNoop(new SubClass(pointsBack));
}
virtual ~SubClass()
{
--s_aliveCount;
}
virtual void trace(Visitor* visitor)
{
#if ENABLE_OILPAN
SuperClass::trace(visitor);
visitor->trace(m_data);
#endif
}
static int s_aliveCount;
private:
explicit SubClass(PassRefPtrWillBeRawPtr<PointsBack> pointsBack)
: SuperClass(pointsBack)
, m_data(adoptPtrWillBeNoop(new SubData()))
{
++s_aliveCount;
}
private:
OwnPtrWillBeMember<SubData> m_data;
};
int SubClass::s_aliveCount = 0;
class TransitionRefCounted : public RefCountedWillBeRefCountedGarbageCollected<TransitionRefCounted> {
public:
static PassRefPtrWillBeRawPtr<TransitionRefCounted> create()
{
return adoptRefWillBeRefCountedGarbageCollected(new TransitionRefCounted());
}
~TransitionRefCounted()
{
--s_aliveCount;
}
void trace(Visitor* visitor) { }
static int s_aliveCount;
private:
TransitionRefCounted()
{
++s_aliveCount;
}
};
int TransitionRefCounted::s_aliveCount = 0;
class Mixin : public GarbageCollectedMixin {
public:
virtual void trace(Visitor* visitor) { }
virtual char getPayload(int i) { return m_padding[i]; }
protected:
int m_padding[8];
};
class UseMixin : public SimpleObject, public Mixin {
USING_GARBAGE_COLLECTED_MIXIN(UseMixin)
public:
static UseMixin* create()
{
return new UseMixin();
}
static int s_traceCount;
virtual void trace(Visitor* visitor)
{
SimpleObject::trace(visitor);
Mixin::trace(visitor);
++s_traceCount;
}
private:
UseMixin()
{
s_traceCount = 0;
}
};
int UseMixin::s_traceCount = 0;
class VectorObject {
ALLOW_ONLY_INLINE_ALLOCATION();
public:
VectorObject()
{
m_value = SimpleFinalizedObject::create();
}
void trace(Visitor* visitor)
{
visitor->trace(m_value);
}
private:
Member<SimpleFinalizedObject> m_value;
};
class VectorObjectInheritedTrace : public VectorObject { };
class VectorObjectNoTrace {
ALLOW_ONLY_INLINE_ALLOCATION();
public:
VectorObjectNoTrace()
{
m_value = SimpleFinalizedObject::create();
}
private:
Member<SimpleFinalizedObject> m_value;
};
class TerminatedArrayItem {
ALLOW_ONLY_INLINE_ALLOCATION();
public:
TerminatedArrayItem(IntWrapper* payload) : m_payload(payload), m_isLast(false) { }
void trace(Visitor* visitor) { visitor->trace(m_payload); }
bool isLastInArray() const { return m_isLast; }
void setLastInArray(bool value) { m_isLast = value; }
IntWrapper* payload() const { return m_payload; }
private:
Member<IntWrapper> m_payload;
bool m_isLast;
};
} // namespace blink
WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::VectorObject);
WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::VectorObjectInheritedTrace);
WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::VectorObjectNoTrace);
namespace blink {
class OneKiloByteObject : public GarbageCollectedFinalized<OneKiloByteObject> {
public:
~OneKiloByteObject() { s_destructorCalls++; }
char* data() { return m_data; }
void trace(Visitor* visitor) { }
static int s_destructorCalls;
private:
static const size_t s_length = 1024;
char m_data[s_length];
};
int OneKiloByteObject::s_destructorCalls = 0;
class DynamicallySizedObject : public GarbageCollected<DynamicallySizedObject> {
public:
static DynamicallySizedObject* create(size_t size)
{
void* slot = Heap::allocate<DynamicallySizedObject>(size);
return new (slot) DynamicallySizedObject();
}
void* operator new(std::size_t, void* location)
{
return location;
}
uint8_t get(int i)
{
return *(reinterpret_cast<uint8_t*>(this) + i);
}
void trace(Visitor*) { }
private:
DynamicallySizedObject() { }
};
class FinalizationAllocator : public GarbageCollectedFinalized<FinalizationAllocator> {
public:
FinalizationAllocator(Persistent<IntWrapper>* wrapper)
: m_wrapper(wrapper)
{
}
~FinalizationAllocator()
{
for (int i = 0; i < 10; ++i)
*m_wrapper = IntWrapper::create(42);
for (int i = 0; i < 512; ++i)
new OneKiloByteObject();
}
void trace(Visitor*) { }
private:
Persistent<IntWrapper>* m_wrapper;
};
TEST(HeapTest, Transition)
{
{
RefPtr<TransitionRefCounted> refCounted = TransitionRefCounted::create();
EXPECT_EQ(1, TransitionRefCounted::s_aliveCount);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, TransitionRefCounted::s_aliveCount);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, TransitionRefCounted::s_aliveCount);
RefPtrWillBePersistent<PointsBack> pointsBack1 = PointsBack::create();
RefPtrWillBePersistent<PointsBack> pointsBack2 = PointsBack::create();
RefPtrWillBePersistent<SuperClass> superClass = SuperClass::create(pointsBack1);
RefPtrWillBePersistent<SubClass> subClass = SubClass::create(pointsBack2);
EXPECT_EQ(2, PointsBack::s_aliveCount);
EXPECT_EQ(2, SuperClass::s_aliveCount);
EXPECT_EQ(1, SubClass::s_aliveCount);
EXPECT_EQ(1, SubData::s_aliveCount);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, TransitionRefCounted::s_aliveCount);
EXPECT_EQ(2, PointsBack::s_aliveCount);
EXPECT_EQ(2, SuperClass::s_aliveCount);
EXPECT_EQ(1, SubClass::s_aliveCount);
EXPECT_EQ(1, SubData::s_aliveCount);
superClass->doStuff(superClass.release(), pointsBack1.get(), 2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, PointsBack::s_aliveCount);
EXPECT_EQ(1, SuperClass::s_aliveCount);
EXPECT_EQ(1, SubClass::s_aliveCount);
EXPECT_EQ(1, SubData::s_aliveCount);
EXPECT_EQ(0, pointsBack1->backPointer());
pointsBack1.release();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, PointsBack::s_aliveCount);
EXPECT_EQ(1, SuperClass::s_aliveCount);
EXPECT_EQ(1, SubClass::s_aliveCount);
EXPECT_EQ(1, SubData::s_aliveCount);
subClass->doStuff(subClass.release(), pointsBack2.get(), 1);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, PointsBack::s_aliveCount);
EXPECT_EQ(0, SuperClass::s_aliveCount);
EXPECT_EQ(0, SubClass::s_aliveCount);
EXPECT_EQ(0, SubData::s_aliveCount);
EXPECT_EQ(0, pointsBack2->backPointer());
pointsBack2.release();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, PointsBack::s_aliveCount);
EXPECT_EQ(0, SuperClass::s_aliveCount);
EXPECT_EQ(0, SubClass::s_aliveCount);
EXPECT_EQ(0, SubData::s_aliveCount);
EXPECT_TRUE(superClass == subClass);
}
TEST(HeapTest, Threading)
{
ThreadedHeapTester::test();
}
TEST(HeapTest, ThreadedWeakness)
{
ThreadedWeaknessTester::test();
}
TEST(HeapTest, BasicFunctionality)
{
HeapStats heapStats;
clearOutOldGarbage(&heapStats);
{
size_t slack = 0;
// When the test starts there may already have been leaked some memory
// on the heap, so we establish a base line.
size_t baseLevel = heapStats.totalObjectSpace();
bool testPagesAllocated = !baseLevel;
if (testPagesAllocated)
EXPECT_EQ(heapStats.totalAllocatedSpace(), 0ul);
// This allocates objects on the general heap which should add a page of memory.
DynamicallySizedObject* alloc32 = DynamicallySizedObject::create(32);
slack += 4;
memset(alloc32, 40, 32);
DynamicallySizedObject* alloc64 = DynamicallySizedObject::create(64);
slack += 4;
memset(alloc64, 27, 64);
size_t total = 96;
getHeapStats(&heapStats);
CheckWithSlack(baseLevel + total, heapStats.totalObjectSpace(), slack);
if (testPagesAllocated)
EXPECT_EQ(heapStats.totalAllocatedSpace(), blinkPageSize);
CheckWithSlack(alloc32 + 32 + sizeof(HeapObjectHeader), alloc64, slack);
EXPECT_EQ(alloc32->get(0), 40);
EXPECT_EQ(alloc32->get(31), 40);
EXPECT_EQ(alloc64->get(0), 27);
EXPECT_EQ(alloc64->get(63), 27);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(alloc32->get(0), 40);
EXPECT_EQ(alloc32->get(31), 40);
EXPECT_EQ(alloc64->get(0), 27);
EXPECT_EQ(alloc64->get(63), 27);
}
clearOutOldGarbage(&heapStats);
size_t total = 0;
size_t slack = 0;
size_t baseLevel = heapStats.totalObjectSpace();
bool testPagesAllocated = !baseLevel;
if (testPagesAllocated)
EXPECT_EQ(heapStats.totalAllocatedSpace(), 0ul);
size_t big = 1008;
Persistent<DynamicallySizedObject> bigArea = DynamicallySizedObject::create(big);
total += big;
slack += 4;
size_t persistentCount = 0;
const size_t numPersistents = 100000;
Persistent<DynamicallySizedObject>* persistents[numPersistents];
for (int i = 0; i < 1000; i++) {
size_t size = 128 + i * 8;
total += size;
persistents[persistentCount++] = new Persistent<DynamicallySizedObject>(DynamicallySizedObject::create(size));
slack += 4;
getHeapStats(&heapStats);
CheckWithSlack(baseLevel + total, heapStats.totalObjectSpace(), slack);
if (testPagesAllocated)
EXPECT_EQ(0ul, heapStats.totalAllocatedSpace() & (blinkPageSize - 1));
}
{
DynamicallySizedObject* alloc32b(DynamicallySizedObject::create(32));
slack += 4;
memset(alloc32b, 40, 32);
DynamicallySizedObject* alloc64b(DynamicallySizedObject::create(64));
slack += 4;
memset(alloc64b, 27, 64);
EXPECT_TRUE(alloc32b != alloc64b);
total += 96;
getHeapStats(&heapStats);
CheckWithSlack(baseLevel + total, heapStats.totalObjectSpace(), slack);
if (testPagesAllocated)
EXPECT_EQ(0ul, heapStats.totalAllocatedSpace() & (blinkPageSize - 1));
}
clearOutOldGarbage(&heapStats);
total -= 96;
slack -= 8;
if (testPagesAllocated)
EXPECT_EQ(0ul, heapStats.totalAllocatedSpace() & (blinkPageSize - 1));
DynamicallySizedObject* bigAreaRaw = bigArea;
// Clear the persistent, so that the big area will be garbage collected.
bigArea.release();
clearOutOldGarbage(&heapStats);
total -= big;
slack -= 4;
getHeapStats(&heapStats);
CheckWithSlack(baseLevel + total, heapStats.totalObjectSpace(), slack);
if (testPagesAllocated)
EXPECT_EQ(0ul, heapStats.totalAllocatedSpace() & (blinkPageSize - 1));
// Endless loop unless we eventually get the memory back that we just freed.
while (true) {
Persistent<DynamicallySizedObject>* alloc = new Persistent<DynamicallySizedObject>(DynamicallySizedObject::create(big / 2));
slack += 4;
persistents[persistentCount++] = alloc;
EXPECT_LT(persistentCount, numPersistents);
total += big / 2;
if (bigAreaRaw == alloc->get())
break;
}
getHeapStats(&heapStats);
CheckWithSlack(baseLevel + total, heapStats.totalObjectSpace(), slack);
if (testPagesAllocated)
EXPECT_EQ(0ul, heapStats.totalAllocatedSpace() & (blinkPageSize - 1));
for (size_t i = 0; i < persistentCount; i++) {
delete persistents[i];
persistents[i] = 0;
}
uint8_t* address = reinterpret_cast<uint8_t*>(Heap::reallocate<DynamicallySizedObject>(0, 100));
for (int i = 0; i < 100; i++)
address[i] = i;
address = reinterpret_cast<uint8_t*>(Heap::reallocate<DynamicallySizedObject>(address, 100000));
for (int i = 0; i < 100; i++)
EXPECT_EQ(address[i], i);
address = reinterpret_cast<uint8_t*>(Heap::reallocate<DynamicallySizedObject>(address, 50));
for (int i = 0; i < 50; i++)
EXPECT_EQ(address[i], i);
// This should be equivalent to free(address).
EXPECT_EQ(reinterpret_cast<uintptr_t>(Heap::reallocate<DynamicallySizedObject>(address, 0)), 0ul);
// This should be equivalent to malloc(0).
EXPECT_EQ(reinterpret_cast<uintptr_t>(Heap::reallocate<DynamicallySizedObject>(0, 0)), 0ul);
}
TEST(HeapTest, SimpleAllocation)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
EXPECT_EQ(0ul, initialHeapStats.totalObjectSpace());
// Allocate an object in the heap.
HeapAllocatedArray* array = new HeapAllocatedArray();
HeapStats statsAfterAllocation;
getHeapStats(&statsAfterAllocation);
EXPECT_TRUE(statsAfterAllocation.totalObjectSpace() >= sizeof(HeapAllocatedArray));
// Sanity check of the contents in the heap.
EXPECT_EQ(0, array->at(0));
EXPECT_EQ(42, array->at(42));
EXPECT_EQ(0, array->at(128));
EXPECT_EQ(999 % 128, array->at(999));
}
TEST(HeapTest, SimplePersistent)
{
Persistent<TraceCounter> traceCounter = TraceCounter::create();
EXPECT_EQ(0, traceCounter->traceCount());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, traceCounter->traceCount());
Persistent<ClassWithMember> classWithMember = ClassWithMember::create();
EXPECT_EQ(0, classWithMember->traceCount());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, classWithMember->traceCount());
EXPECT_EQ(2, traceCounter->traceCount());
}
TEST(HeapTest, SimpleFinalization)
{
{
Persistent<SimpleFinalizedObject> finalized = SimpleFinalizedObject::create();
EXPECT_EQ(0, SimpleFinalizedObject::s_destructorCalls);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, SimpleFinalizedObject::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, SimpleFinalizedObject::s_destructorCalls);
}
TEST(HeapTest, Finalization)
{
{
HeapTestSubClass* t1 = HeapTestSubClass::create();
HeapTestSubClass* t2 = HeapTestSubClass::create();
HeapTestSuperClass* t3 = HeapTestSuperClass::create();
// FIXME(oilpan): Ignore unused variables.
(void)t1;
(void)t2;
(void)t3;
}
// Nothing is marked so the GC should free everything and call
// the finalizer on all three objects.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, HeapTestSubClass::s_destructorCalls);
EXPECT_EQ(3, HeapTestSuperClass::s_destructorCalls);
// Destructors not called again when GCing again.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, HeapTestSubClass::s_destructorCalls);
EXPECT_EQ(3, HeapTestSuperClass::s_destructorCalls);
}
TEST(HeapTest, TypedHeapSanity)
{
// We use TraceCounter for allocating an object on the general heap.
Persistent<TraceCounter> generalHeapObject = TraceCounter::create();
Persistent<Node> typedHeapObject = Node::create();
EXPECT_NE(pageHeaderFromObject(generalHeapObject.get()),
pageHeaderFromObject(typedHeapObject.get()));
}
TEST(HeapTest, NoAllocation)
{
EXPECT_TRUE(ThreadState::current()->isAllocationAllowed());
{
// Disallow allocation
NoAllocationScope<AnyThread> noAllocationScope;
EXPECT_FALSE(ThreadState::current()->isAllocationAllowed());
}
EXPECT_TRUE(ThreadState::current()->isAllocationAllowed());
}
TEST(HeapTest, Members)
{
Bar::s_live = 0;
{
Persistent<Baz> h1;
Persistent<Baz> h2;
{
h1 = Baz::create(Bar::create());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, Bar::s_live);
h2 = Baz::create(Bar::create());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u, Bar::s_live);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u, Bar::s_live);
h1->clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, Bar::s_live);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
}
TEST(HeapTest, MarkTest)
{
{
Bar::s_live = 0;
Persistent<Bar> bar = Bar::create();
EXPECT_TRUE(ThreadState::current()->contains(bar));
EXPECT_EQ(1u, Bar::s_live);
{
Foo* foo = Foo::create(bar);
EXPECT_TRUE(ThreadState::current()->contains(foo));
EXPECT_EQ(2u, Bar::s_live);
EXPECT_TRUE(reinterpret_cast<Address>(foo) != reinterpret_cast<Address>(bar.get()));
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(foo != bar); // To make sure foo is kept alive.
EXPECT_EQ(2u, Bar::s_live);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, Bar::s_live);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
}
TEST(HeapTest, DeepTest)
{
const unsigned depth = 100000;
Bar::s_live = 0;
{
Bar* bar = Bar::create();
EXPECT_TRUE(ThreadState::current()->contains(bar));
Foo* foo = Foo::create(bar);
EXPECT_TRUE(ThreadState::current()->contains(foo));
EXPECT_EQ(2u, Bar::s_live);
for (unsigned i = 0; i < depth; i++) {
Foo* foo2 = Foo::create(foo);
foo = foo2;
EXPECT_TRUE(ThreadState::current()->contains(foo));
}
EXPECT_EQ(depth + 2, Bar::s_live);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(foo != bar); // To make sure foo and bar are kept alive.
EXPECT_EQ(depth + 2, Bar::s_live);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
}
TEST(HeapTest, WideTest)
{
Bar::s_live = 0;
{
Bars* bars = Bars::create();
unsigned width = Bars::width;
EXPECT_EQ(width + 1, Bar::s_live);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(width + 1, Bar::s_live);
// Use bars here to make sure that it will be on the stack
// for the conservative stack scan to find.
EXPECT_EQ(width, bars->getWidth());
}
EXPECT_EQ(Bars::width + 1, Bar::s_live);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
}
TEST(HeapTest, HashMapOfMembers)
{
HeapStats initialHeapSize;
IntWrapper::s_destructorCalls = 0;
clearOutOldGarbage(&initialHeapSize);
{
typedef HeapHashMap<
Member<IntWrapper>,
Member<IntWrapper>,
DefaultHash<Member<IntWrapper> >::Hash,
HashTraits<Member<IntWrapper> >,
HashTraits<Member<IntWrapper> > > HeapObjectIdentityMap;
Persistent<HeapObjectIdentityMap> map = new HeapObjectIdentityMap();
map->clear();
HeapStats afterSetWasCreated;
getHeapStats(&afterSetWasCreated);
EXPECT_TRUE(afterSetWasCreated.totalObjectSpace() > initialHeapSize.totalObjectSpace());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
HeapStats afterGC;
getHeapStats(&afterGC);
EXPECT_EQ(afterGC.totalObjectSpace(), afterSetWasCreated.totalObjectSpace());
// If the additions below cause garbage collections, these
// pointers should be found by conservative stack scanning.
IntWrapper* one(IntWrapper::create(1));
IntWrapper* anotherOne(IntWrapper::create(1));
map->add(one, one);
HeapStats afterOneAdd;
getHeapStats(&afterOneAdd);
EXPECT_TRUE(afterOneAdd.totalObjectSpace() > afterGC.totalObjectSpace());
HeapObjectIdentityMap::iterator it(map->begin());
HeapObjectIdentityMap::iterator it2(map->begin());
++it;
++it2;
map->add(anotherOne, one);
// The addition above can cause an allocation of a new
// backing store. We therefore garbage collect before
// taking the heap stats in order to get rid of the old
// backing store. We make sure to not use conservative
// stack scanning as that could find a pointer to the
// old backing.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
HeapStats afterAddAndGC;
getHeapStats(&afterAddAndGC);
EXPECT_TRUE(afterAddAndGC.totalObjectSpace() >= afterOneAdd.totalObjectSpace());
EXPECT_EQ(map->size(), 2u); // Two different wrappings of '1' are distinct.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_TRUE(map->contains(one));
EXPECT_TRUE(map->contains(anotherOne));
IntWrapper* gotten(map->get(one));
EXPECT_EQ(gotten->value(), one->value());
EXPECT_EQ(gotten, one);
HeapStats afterGC2;
getHeapStats(&afterGC2);
EXPECT_EQ(afterGC2.totalObjectSpace(), afterAddAndGC.totalObjectSpace());
IntWrapper* dozen = 0;
for (int i = 1; i < 1000; i++) { // 999 iterations.
IntWrapper* iWrapper(IntWrapper::create(i));
IntWrapper* iSquared(IntWrapper::create(i * i));
map->add(iWrapper, iSquared);
if (i == 12)
dozen = iWrapper;
}
HeapStats afterAdding1000;
getHeapStats(&afterAdding1000);
EXPECT_TRUE(afterAdding1000.totalObjectSpace() > afterGC2.totalObjectSpace());
IntWrapper* gross(map->get(dozen));
EXPECT_EQ(gross->value(), 144);
// This should clear out junk created by all the adds.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
HeapStats afterGC3;
getHeapStats(&afterGC3);
EXPECT_TRUE(afterGC3.totalObjectSpace() < afterAdding1000.totalObjectSpace());
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// The objects 'one', anotherOne, and the 999 other pairs.
EXPECT_EQ(IntWrapper::s_destructorCalls, 2000);
HeapStats afterGC4;
getHeapStats(&afterGC4);
EXPECT_EQ(afterGC4.totalObjectSpace(), initialHeapSize.totalObjectSpace());
}
TEST(HeapTest, NestedAllocation)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
{
Persistent<ConstructorAllocation> constructorAllocation = ConstructorAllocation::create();
}
HeapStats afterFree;
clearOutOldGarbage(&afterFree);
EXPECT_TRUE(initialHeapSize == afterFree);
}
TEST(HeapTest, LargeObjects)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
IntWrapper::s_destructorCalls = 0;
LargeObject::s_destructorCalls = 0;
{
int slack = 8; // LargeObject points to an IntWrapper that is also allocated.
Persistent<LargeObject> object = LargeObject::create();
EXPECT_TRUE(ThreadState::current()->contains(object));
EXPECT_TRUE(ThreadState::current()->contains(reinterpret_cast<char*>(object.get()) + sizeof(LargeObject) - 1));
#if ENABLE(GC_PROFILE_MARKING)
const GCInfo* info = ThreadState::current()->findGCInfo(reinterpret_cast<Address>(object.get()));
EXPECT_NE(reinterpret_cast<const GCInfo*>(0), info);
EXPECT_EQ(info, ThreadState::current()->findGCInfo(reinterpret_cast<Address>(object.get()) + sizeof(LargeObject) - 1));
EXPECT_NE(info, ThreadState::current()->findGCInfo(reinterpret_cast<Address>(object.get()) + sizeof(LargeObject)));
EXPECT_NE(info, ThreadState::current()->findGCInfo(reinterpret_cast<Address>(object.get()) - 1));
#endif
HeapStats afterAllocation;
clearOutOldGarbage(&afterAllocation);
{
object->set(0, 'a');
EXPECT_EQ('a', object->get(0));
object->set(object->length() - 1, 'b');
EXPECT_EQ('b', object->get(object->length() - 1));
size_t expectedObjectSpace = sizeof(LargeObject) + sizeof(IntWrapper);
size_t actualObjectSpace =
afterAllocation.totalObjectSpace() - initialHeapSize.totalObjectSpace();
CheckWithSlack(expectedObjectSpace, actualObjectSpace, slack);
// There is probably space for the IntWrapper in a heap page without
// allocating extra pages. However, the IntWrapper allocation might cause
// the addition of a heap page.
size_t largeObjectAllocationSize =
sizeof(LargeObject) + sizeof(LargeHeapObject<FinalizedHeapObjectHeader>) + sizeof(FinalizedHeapObjectHeader);
size_t allocatedSpaceLowerBound =
initialHeapSize.totalAllocatedSpace() + largeObjectAllocationSize;
size_t allocatedSpaceUpperBound = allocatedSpaceLowerBound + slack + blinkPageSize;
EXPECT_LE(allocatedSpaceLowerBound, afterAllocation.totalAllocatedSpace());
EXPECT_LE(afterAllocation.totalAllocatedSpace(), allocatedSpaceUpperBound);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
EXPECT_EQ(0, LargeObject::s_destructorCalls);
for (int i = 0; i < 10; i++)
object = LargeObject::create();
}
HeapStats oneLargeObject;
clearOutOldGarbage(&oneLargeObject);
EXPECT_TRUE(oneLargeObject == afterAllocation);
EXPECT_EQ(10, IntWrapper::s_destructorCalls);
EXPECT_EQ(10, LargeObject::s_destructorCalls);
}
HeapStats backToInitial;
clearOutOldGarbage(&backToInitial);
EXPECT_TRUE(initialHeapSize == backToInitial);
EXPECT_EQ(11, IntWrapper::s_destructorCalls);
EXPECT_EQ(11, LargeObject::s_destructorCalls);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
}
typedef std::pair<Member<IntWrapper>, int> PairWrappedUnwrapped;
typedef std::pair<int, Member<IntWrapper> > PairUnwrappedWrapped;
typedef std::pair<WeakMember<IntWrapper>, Member<IntWrapper> > PairWeakStrong;
typedef std::pair<Member<IntWrapper>, WeakMember<IntWrapper> > PairStrongWeak;
typedef std::pair<WeakMember<IntWrapper>, int> PairWeakUnwrapped;
typedef std::pair<int, WeakMember<IntWrapper> > PairUnwrappedWeak;
class Container : public GarbageCollected<Container> {
public:
static Container* create() { return new Container(); }
HeapHashMap<Member<IntWrapper>, Member<IntWrapper> > map;
HeapHashSet<Member<IntWrapper> > set;
HeapHashSet<Member<IntWrapper> > set2;
HeapHashCountedSet<Member<IntWrapper> > set3;
HeapVector<Member<IntWrapper>, 2> vector;
HeapVector<PairWrappedUnwrapped, 2> vectorWU;
HeapVector<PairUnwrappedWrapped, 2> vectorUW;
HeapDeque<Member<IntWrapper>, 0> deque;
HeapDeque<PairWrappedUnwrapped, 0> dequeWU;
HeapDeque<PairUnwrappedWrapped, 0> dequeUW;
void trace(Visitor* visitor)
{
visitor->trace(map);
visitor->trace(set);
visitor->trace(set2);
visitor->trace(set3);
visitor->trace(vector);
visitor->trace(vectorWU);
visitor->trace(vectorUW);
visitor->trace(deque);
visitor->trace(dequeWU);
visitor->trace(dequeUW);
}
};
struct ShouldBeTraced {
explicit ShouldBeTraced(IntWrapper* wrapper) : m_wrapper(wrapper) { }
void trace(Visitor* visitor) { visitor->trace(m_wrapper); }
Member<IntWrapper> m_wrapper;
};
class OffHeapContainer : public GarbageCollectedFinalized<OffHeapContainer> {
public:
static OffHeapContainer* create() { return new OffHeapContainer(); }
static const int iterations = 300;
static const int deadWrappers = 1200;
OffHeapContainer()
{
for (int i = 0; i < iterations; i++) {
m_deque1.append(ShouldBeTraced(IntWrapper::create(i)));
m_vector1.append(ShouldBeTraced(IntWrapper::create(i)));
m_deque2.append(IntWrapper::create(i));
m_vector2.append(IntWrapper::create(i));
}
Deque<ShouldBeTraced>::iterator d1Iterator(m_deque1.begin());
Vector<ShouldBeTraced>::iterator v1Iterator(m_vector1.begin());
Deque<Member<IntWrapper> >::iterator d2Iterator(m_deque2.begin());
Vector<Member<IntWrapper> >::iterator v2Iterator(m_vector2.begin());
for (int i = 0; i < iterations; i++) {
EXPECT_EQ(i, m_vector1[i].m_wrapper->value());
EXPECT_EQ(i, m_vector2[i]->value());
EXPECT_EQ(i, d1Iterator->m_wrapper->value());
EXPECT_EQ(i, v1Iterator->m_wrapper->value());
EXPECT_EQ(i, d2Iterator->get()->value());
EXPECT_EQ(i, v2Iterator->get()->value());
++d1Iterator;
++v1Iterator;
++d2Iterator;
++v2Iterator;
}
EXPECT_EQ(d1Iterator, m_deque1.end());
EXPECT_EQ(v1Iterator, m_vector1.end());
EXPECT_EQ(d2Iterator, m_deque2.end());
EXPECT_EQ(v2Iterator, m_vector2.end());
}
void trace(Visitor* visitor)
{
visitor->trace(m_deque1);
visitor->trace(m_vector1);
visitor->trace(m_deque2);
visitor->trace(m_vector2);
}
Deque<ShouldBeTraced> m_deque1;
Vector<ShouldBeTraced> m_vector1;
Deque<Member<IntWrapper> > m_deque2;
Vector<Member<IntWrapper> > m_vector2;
};
const int OffHeapContainer::iterations;
const int OffHeapContainer::deadWrappers;
// These class definitions test compile-time asserts with transition
// types. They are therefore unused in test code and just need to
// compile. This is intentional; do not delete the A and B classes below.
class A : public WillBeGarbageCollectedMixin {
};
class B : public NoBaseWillBeGarbageCollected<B>, public A {
WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(B);
public:
void trace(Visitor*) { }
};
TEST(HeapTest, HeapVectorFilledWithValue)
{
IntWrapper* val = IntWrapper::create(1);
HeapVector<Member<IntWrapper> > vector(10, val);
EXPECT_EQ(10u, vector.size());
for (size_t i = 0; i < vector.size(); i++)
EXPECT_EQ(val, vector[i]);
}
TEST(HeapTest, HeapVectorWithInlineCapacity)
{
IntWrapper* one = IntWrapper::create(1);
IntWrapper* two = IntWrapper::create(2);
IntWrapper* three = IntWrapper::create(3);
IntWrapper* four = IntWrapper::create(4);
IntWrapper* five = IntWrapper::create(5);
IntWrapper* six = IntWrapper::create(6);
{
HeapVector<Member<IntWrapper>, 2> vector;
vector.append(one);
vector.append(two);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(vector.contains(one));
EXPECT_TRUE(vector.contains(two));
vector.append(three);
vector.append(four);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(vector.contains(one));
EXPECT_TRUE(vector.contains(two));
EXPECT_TRUE(vector.contains(three));
EXPECT_TRUE(vector.contains(four));
vector.shrink(1);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(vector.contains(one));
EXPECT_FALSE(vector.contains(two));
EXPECT_FALSE(vector.contains(three));
EXPECT_FALSE(vector.contains(four));
}
{
HeapVector<Member<IntWrapper>, 2> vector1;
HeapVector<Member<IntWrapper>, 2> vector2;
vector1.append(one);
vector2.append(two);
vector1.swap(vector2);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(vector1.contains(two));
EXPECT_TRUE(vector2.contains(one));
}
{
HeapVector<Member<IntWrapper>, 2> vector1;
HeapVector<Member<IntWrapper>, 2> vector2;
vector1.append(one);
vector1.append(two);
vector2.append(three);
vector2.append(four);
vector2.append(five);
vector2.append(six);
vector1.swap(vector2);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(vector1.contains(three));
EXPECT_TRUE(vector1.contains(four));
EXPECT_TRUE(vector1.contains(five));
EXPECT_TRUE(vector1.contains(six));
EXPECT_TRUE(vector2.contains(one));
EXPECT_TRUE(vector2.contains(two));
}
}
template<typename T, size_t inlineCapacity, typename U>
bool dequeContains(HeapDeque<T, inlineCapacity>& deque, U u)
{
typedef typename HeapDeque<T, inlineCapacity>::iterator iterator;
for (iterator it = deque.begin(); it != deque.end(); ++it) {
if (*it == u)
return true;
}
return false;
}
TEST(HeapTest, HeapCollectionTypes)
{
HeapStats initialHeapSize;
IntWrapper::s_destructorCalls = 0;
typedef HeapHashMap<Member<IntWrapper>, Member<IntWrapper> > MemberMember;
typedef HeapHashMap<Member<IntWrapper>, int> MemberPrimitive;
typedef HeapHashMap<int, Member<IntWrapper> > PrimitiveMember;
typedef HeapHashSet<Member<IntWrapper> > MemberSet;
typedef HeapHashCountedSet<Member<IntWrapper> > MemberCountedSet;
typedef HeapVector<Member<IntWrapper>, 2> MemberVector;
typedef HeapDeque<Member<IntWrapper>, 0> MemberDeque;
typedef HeapVector<PairWrappedUnwrapped, 2> VectorWU;
typedef HeapVector<PairUnwrappedWrapped, 2> VectorUW;
typedef HeapDeque<PairWrappedUnwrapped, 0> DequeWU;
typedef HeapDeque<PairUnwrappedWrapped, 0> DequeUW;
Persistent<MemberMember> memberMember = new MemberMember();
Persistent<MemberMember> memberMember2 = new MemberMember();
Persistent<MemberMember> memberMember3 = new MemberMember();
Persistent<MemberPrimitive> memberPrimitive = new MemberPrimitive();
Persistent<PrimitiveMember> primitiveMember = new PrimitiveMember();
Persistent<MemberSet> set = new MemberSet();
Persistent<MemberSet> set2 = new MemberSet();
Persistent<MemberCountedSet> set3 = new MemberCountedSet();
Persistent<MemberVector> vector = new MemberVector();
Persistent<MemberVector> vector2 = new MemberVector();
Persistent<VectorWU> vectorWU = new VectorWU();
Persistent<VectorWU> vectorWU2 = new VectorWU();
Persistent<VectorUW> vectorUW = new VectorUW();
Persistent<VectorUW> vectorUW2 = new VectorUW();
Persistent<MemberDeque> deque = new MemberDeque();
Persistent<MemberDeque> deque2 = new MemberDeque();
Persistent<DequeWU> dequeWU = new DequeWU();
Persistent<DequeWU> dequeWU2 = new DequeWU();
Persistent<DequeUW> dequeUW = new DequeUW();
Persistent<DequeUW> dequeUW2 = new DequeUW();
Persistent<Container> container = Container::create();
clearOutOldGarbage(&initialHeapSize);
{
Persistent<IntWrapper> one(IntWrapper::create(1));
Persistent<IntWrapper> two(IntWrapper::create(2));
Persistent<IntWrapper> oneB(IntWrapper::create(1));
Persistent<IntWrapper> twoB(IntWrapper::create(2));
Persistent<IntWrapper> oneC(IntWrapper::create(1));
Persistent<IntWrapper> oneD(IntWrapper::create(1));
Persistent<IntWrapper> oneE(IntWrapper::create(1));
Persistent<IntWrapper> oneF(IntWrapper::create(1));
{
IntWrapper* threeB(IntWrapper::create(3));
IntWrapper* threeC(IntWrapper::create(3));
IntWrapper* threeD(IntWrapper::create(3));
IntWrapper* threeE(IntWrapper::create(3));
IntWrapper* threeF(IntWrapper::create(3));
IntWrapper* three(IntWrapper::create(3));
IntWrapper* fourB(IntWrapper::create(4));
IntWrapper* fourC(IntWrapper::create(4));
IntWrapper* fourD(IntWrapper::create(4));
IntWrapper* fourE(IntWrapper::create(4));
IntWrapper* fourF(IntWrapper::create(4));
IntWrapper* four(IntWrapper::create(4));
IntWrapper* fiveC(IntWrapper::create(5));
IntWrapper* fiveD(IntWrapper::create(5));
IntWrapper* fiveE(IntWrapper::create(5));
IntWrapper* fiveF(IntWrapper::create(5));
// Member Collections.
memberMember2->add(one, two);
memberMember2->add(two, three);
memberMember2->add(three, four);
memberMember2->add(four, one);
primitiveMember->add(1, two);
primitiveMember->add(2, three);
primitiveMember->add(3, four);
primitiveMember->add(4, one);
memberPrimitive->add(one, 2);
memberPrimitive->add(two, 3);
memberPrimitive->add(three, 4);
memberPrimitive->add(four, 1);
set2->add(one);
set2->add(two);
set2->add(three);
set2->add(four);
set->add(oneB);
set3->add(oneB);
set3->add(oneB);
vector->append(oneB);
deque->append(oneB);
vector2->append(threeB);
vector2->append(fourB);
deque2->append(threeE);
deque2->append(fourE);
vectorWU->append(PairWrappedUnwrapped(&*oneC, 42));
dequeWU->append(PairWrappedUnwrapped(&*oneE, 42));
vectorWU2->append(PairWrappedUnwrapped(&*threeC, 43));
vectorWU2->append(PairWrappedUnwrapped(&*fourC, 44));
vectorWU2->append(PairWrappedUnwrapped(&*fiveC, 45));
dequeWU2->append(PairWrappedUnwrapped(&*threeE, 43));
dequeWU2->append(PairWrappedUnwrapped(&*fourE, 44));
dequeWU2->append(PairWrappedUnwrapped(&*fiveE, 45));
vectorUW->append(PairUnwrappedWrapped(1, &*oneD));
vectorUW2->append(PairUnwrappedWrapped(103, &*threeD));
vectorUW2->append(PairUnwrappedWrapped(104, &*fourD));
vectorUW2->append(PairUnwrappedWrapped(105, &*fiveD));
dequeUW->append(PairUnwrappedWrapped(1, &*oneF));
dequeUW2->append(PairUnwrappedWrapped(103, &*threeF));
dequeUW2->append(PairUnwrappedWrapped(104, &*fourF));
dequeUW2->append(PairUnwrappedWrapped(105, &*fiveF));
EXPECT_TRUE(dequeContains(*deque, oneB));
// Collect garbage. This should change nothing since we are keeping
// alive the IntWrapper objects with on-stack pointers.
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(dequeContains(*deque, oneB));
EXPECT_EQ(0u, memberMember->size());
EXPECT_EQ(4u, memberMember2->size());
EXPECT_EQ(4u, primitiveMember->size());
EXPECT_EQ(4u, memberPrimitive->size());
EXPECT_EQ(1u, set->size());
EXPECT_EQ(4u, set2->size());
EXPECT_EQ(1u, set3->size());
EXPECT_EQ(1u, vector->size());
EXPECT_EQ(2u, vector2->size());
EXPECT_EQ(1u, vectorWU->size());
EXPECT_EQ(3u, vectorWU2->size());
EXPECT_EQ(1u, vectorUW->size());
EXPECT_EQ(3u, vectorUW2->size());
EXPECT_EQ(1u, deque->size());
EXPECT_EQ(2u, deque2->size());
EXPECT_EQ(1u, dequeWU->size());
EXPECT_EQ(3u, dequeWU2->size());
EXPECT_EQ(1u, dequeUW->size());
EXPECT_EQ(3u, dequeUW2->size());
MemberVector& cvec = container->vector;
cvec.swap(*vector.get());
vector2->swap(cvec);
vector->swap(cvec);
VectorWU& cvecWU = container->vectorWU;
cvecWU.swap(*vectorWU.get());
vectorWU2->swap(cvecWU);
vectorWU->swap(cvecWU);
VectorUW& cvecUW = container->vectorUW;
cvecUW.swap(*vectorUW.get());
vectorUW2->swap(cvecUW);
vectorUW->swap(cvecUW);
MemberDeque& cDeque = container->deque;
cDeque.swap(*deque.get());
deque2->swap(cDeque);
deque->swap(cDeque);
DequeWU& cDequeWU = container->dequeWU;
cDequeWU.swap(*dequeWU.get());
dequeWU2->swap(cDequeWU);
dequeWU->swap(cDequeWU);
DequeUW& cDequeUW = container->dequeUW;
cDequeUW.swap(*dequeUW.get());
dequeUW2->swap(cDequeUW);
dequeUW->swap(cDequeUW);
// Swap set and set2 in a roundabout way.
MemberSet& cset1 = container->set;
MemberSet& cset2 = container->set2;
set->swap(cset1);
set2->swap(cset2);
set->swap(cset2);
cset1.swap(cset2);
cset2.swap(set2);
MemberCountedSet& cCountedSet = container->set3;
set3->swap(cCountedSet);
EXPECT_EQ(0u, set3->size());
set3->swap(cCountedSet);
// Triple swap.
container->map.swap(memberMember2);
MemberMember& containedMap = container->map;
memberMember3->swap(containedMap);
memberMember3->swap(memberMember);
EXPECT_TRUE(memberMember->get(one) == two);
EXPECT_TRUE(memberMember->get(two) == three);
EXPECT_TRUE(memberMember->get(three) == four);
EXPECT_TRUE(memberMember->get(four) == one);
EXPECT_TRUE(primitiveMember->get(1) == two);
EXPECT_TRUE(primitiveMember->get(2) == three);
EXPECT_TRUE(primitiveMember->get(3) == four);
EXPECT_TRUE(primitiveMember->get(4) == one);
EXPECT_EQ(1, memberPrimitive->get(four));
EXPECT_EQ(2, memberPrimitive->get(one));
EXPECT_EQ(3, memberPrimitive->get(two));
EXPECT_EQ(4, memberPrimitive->get(three));
EXPECT_TRUE(set->contains(one));
EXPECT_TRUE(set->contains(two));
EXPECT_TRUE(set->contains(three));
EXPECT_TRUE(set->contains(four));
EXPECT_TRUE(set2->contains(oneB));
EXPECT_TRUE(set3->contains(oneB));
EXPECT_TRUE(vector->contains(threeB));
EXPECT_TRUE(vector->contains(fourB));
EXPECT_TRUE(dequeContains(*deque, threeE));
EXPECT_TRUE(dequeContains(*deque, fourE));
EXPECT_TRUE(vector2->contains(oneB));
EXPECT_FALSE(vector2->contains(threeB));
EXPECT_TRUE(dequeContains(*deque2, oneB));
EXPECT_FALSE(dequeContains(*deque2, threeE));
EXPECT_TRUE(vectorWU->contains(PairWrappedUnwrapped(&*threeC, 43)));
EXPECT_TRUE(vectorWU->contains(PairWrappedUnwrapped(&*fourC, 44)));
EXPECT_TRUE(vectorWU->contains(PairWrappedUnwrapped(&*fiveC, 45)));
EXPECT_TRUE(vectorWU2->contains(PairWrappedUnwrapped(&*oneC, 42)));
EXPECT_FALSE(vectorWU2->contains(PairWrappedUnwrapped(&*threeC, 43)));
EXPECT_TRUE(vectorUW->contains(PairUnwrappedWrapped(103, &*threeD)));
EXPECT_TRUE(vectorUW->contains(PairUnwrappedWrapped(104, &*fourD)));
EXPECT_TRUE(vectorUW->contains(PairUnwrappedWrapped(105, &*fiveD)));
EXPECT_TRUE(vectorUW2->contains(PairUnwrappedWrapped(1, &*oneD)));
EXPECT_FALSE(vectorUW2->contains(PairUnwrappedWrapped(103, &*threeD)));
EXPECT_TRUE(dequeContains(*dequeWU, PairWrappedUnwrapped(&*threeE, 43)));
EXPECT_TRUE(dequeContains(*dequeWU, PairWrappedUnwrapped(&*fourE, 44)));
EXPECT_TRUE(dequeContains(*dequeWU, PairWrappedUnwrapped(&*fiveE, 45)));
EXPECT_TRUE(dequeContains(*dequeWU2, PairWrappedUnwrapped(&*oneE, 42)));
EXPECT_FALSE(dequeContains(*dequeWU2, PairWrappedUnwrapped(&*threeE, 43)));
EXPECT_TRUE(dequeContains(*dequeUW, PairUnwrappedWrapped(103, &*threeF)));
EXPECT_TRUE(dequeContains(*dequeUW, PairUnwrappedWrapped(104, &*fourF)));
EXPECT_TRUE(dequeContains(*dequeUW, PairUnwrappedWrapped(105, &*fiveF)));
EXPECT_TRUE(dequeContains(*dequeUW2, PairUnwrappedWrapped(1, &*oneF)));
EXPECT_FALSE(dequeContains(*dequeUW2, PairUnwrappedWrapped(103, &*threeF)));
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(4u, memberMember->size());
EXPECT_EQ(0u, memberMember2->size());
EXPECT_EQ(4u, primitiveMember->size());
EXPECT_EQ(4u, memberPrimitive->size());
EXPECT_EQ(4u, set->size());
EXPECT_EQ(1u, set2->size());
EXPECT_EQ(1u, set3->size());
EXPECT_EQ(2u, vector->size());
EXPECT_EQ(1u, vector2->size());
EXPECT_EQ(3u, vectorUW->size());
EXPECT_EQ(1u, vector2->size());
EXPECT_EQ(2u, deque->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_EQ(3u, dequeUW->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_TRUE(memberMember->get(one) == two);
EXPECT_TRUE(primitiveMember->get(1) == two);
EXPECT_TRUE(primitiveMember->get(4) == one);
EXPECT_EQ(2, memberPrimitive->get(one));
EXPECT_EQ(3, memberPrimitive->get(two));
EXPECT_TRUE(set->contains(one));
EXPECT_TRUE(set->contains(two));
EXPECT_FALSE(set->contains(oneB));
EXPECT_TRUE(set2->contains(oneB));
EXPECT_TRUE(set3->contains(oneB));
EXPECT_EQ(2u, set3->find(oneB)->value);
EXPECT_EQ(3, vector->at(0)->value());
EXPECT_EQ(4, vector->at(1)->value());
EXPECT_EQ(3, deque->begin()->get()->value());
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(4u, memberMember->size());
EXPECT_EQ(4u, primitiveMember->size());
EXPECT_EQ(4u, memberPrimitive->size());
EXPECT_EQ(4u, set->size());
EXPECT_EQ(1u, set2->size());
EXPECT_EQ(2u, vector->size());
EXPECT_EQ(1u, vector2->size());
EXPECT_EQ(3u, vectorWU->size());
EXPECT_EQ(1u, vectorWU2->size());
EXPECT_EQ(3u, vectorUW->size());
EXPECT_EQ(1u, vectorUW2->size());
EXPECT_EQ(2u, deque->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_EQ(3u, dequeWU->size());
EXPECT_EQ(1u, dequeWU2->size());
EXPECT_EQ(3u, dequeUW->size());
EXPECT_EQ(1u, dequeUW2->size());
}
template<typename T>
void MapIteratorCheck(T& it, const T& end, int expected)
{
int found = 0;
while (it != end) {
found++;
int key = it->key->value();
int value = it->value->value();
EXPECT_TRUE(key >= 0 && key < 1100);
EXPECT_TRUE(value >= 0 && value < 1100);
++it;
}
EXPECT_EQ(expected, found);
}
template<typename T>
void SetIteratorCheck(T& it, const T& end, int expected)
{
int found = 0;
while (it != end) {
found++;
int value = (*it)->value();
EXPECT_TRUE(value >= 0 && value < 1100);
++it;
}
EXPECT_EQ(expected, found);
}
TEST(HeapTest, HeapWeakCollectionSimple)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
IntWrapper::s_destructorCalls = 0;
PersistentHeapVector<Member<IntWrapper> > keepNumbersAlive;
typedef HeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper> > WeakStrong;
typedef HeapHashMap<Member<IntWrapper>, WeakMember<IntWrapper> > StrongWeak;
typedef HeapHashMap<WeakMember<IntWrapper>, WeakMember<IntWrapper> > WeakWeak;
typedef HeapHashSet<WeakMember<IntWrapper> > WeakSet;
typedef HeapHashCountedSet<WeakMember<IntWrapper> > WeakCountedSet;
Persistent<WeakStrong> weakStrong = new WeakStrong();
Persistent<StrongWeak> strongWeak = new StrongWeak();
Persistent<WeakWeak> weakWeak = new WeakWeak();
Persistent<WeakSet> weakSet = new WeakSet();
Persistent<WeakCountedSet> weakCountedSet = new WeakCountedSet();
Persistent<IntWrapper> two = IntWrapper::create(2);
keepNumbersAlive.append(IntWrapper::create(103));
keepNumbersAlive.append(IntWrapper::create(10));
{
weakStrong->add(IntWrapper::create(1), two);
strongWeak->add(two, IntWrapper::create(1));
weakWeak->add(two, IntWrapper::create(42));
weakWeak->add(IntWrapper::create(42), two);
weakSet->add(IntWrapper::create(0));
weakSet->add(two);
weakSet->add(keepNumbersAlive[0]);
weakSet->add(keepNumbersAlive[1]);
weakCountedSet->add(IntWrapper::create(0));
weakCountedSet->add(two);
weakCountedSet->add(two);
weakCountedSet->add(two);
weakCountedSet->add(keepNumbersAlive[0]);
weakCountedSet->add(keepNumbersAlive[1]);
EXPECT_EQ(1u, weakStrong->size());
EXPECT_EQ(1u, strongWeak->size());
EXPECT_EQ(2u, weakWeak->size());
EXPECT_EQ(4u, weakSet->size());
EXPECT_EQ(4u, weakCountedSet->size());
EXPECT_EQ(3u, weakCountedSet->find(two)->value);
weakCountedSet->remove(two);
EXPECT_EQ(2u, weakCountedSet->find(two)->value);
}
keepNumbersAlive[0] = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, weakStrong->size());
EXPECT_EQ(0u, strongWeak->size());
EXPECT_EQ(0u, weakWeak->size());
EXPECT_EQ(2u, weakSet->size());
EXPECT_EQ(2u, weakCountedSet->size());
}
template<typename Set>
void orderedSetHelper(bool strong)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
IntWrapper::s_destructorCalls = 0;
PersistentHeapVector<Member<IntWrapper> > keepNumbersAlive;
Persistent<Set> set1 = new Set();
Persistent<Set> set2 = new Set();
const Set& constSet = *set1.get();
keepNumbersAlive.append(IntWrapper::create(2));
keepNumbersAlive.append(IntWrapper::create(103));
keepNumbersAlive.append(IntWrapper::create(10));
set1->add(IntWrapper::create(0));
set1->add(keepNumbersAlive[0]);
set1->add(keepNumbersAlive[1]);
set1->add(keepNumbersAlive[2]);
set2->clear();
set2->add(IntWrapper::create(42));
set2->clear();
EXPECT_EQ(4u, set1->size());
typename Set::iterator it(set1->begin());
typename Set::reverse_iterator reverse(set1->rbegin());
typename Set::const_iterator cit(constSet.begin());
typename Set::const_reverse_iterator creverse(constSet.rbegin());
EXPECT_EQ(0, (*it)->value());
EXPECT_EQ(0, (*cit)->value());
++it;
++cit;
EXPECT_EQ(2, (*it)->value());
EXPECT_EQ(2, (*cit)->value());
--it;
--cit;
EXPECT_EQ(0, (*it)->value());
EXPECT_EQ(0, (*cit)->value());
++it;
++cit;
++it;
++cit;
EXPECT_EQ(103, (*it)->value());
EXPECT_EQ(103, (*cit)->value());
++it;
++cit;
EXPECT_EQ(10, (*it)->value());
EXPECT_EQ(10, (*cit)->value());
++it;
++cit;
EXPECT_EQ(10, (*reverse)->value());
EXPECT_EQ(10, (*creverse)->value());
++reverse;
++creverse;
EXPECT_EQ(103, (*reverse)->value());
EXPECT_EQ(103, (*creverse)->value());
--reverse;
--creverse;
EXPECT_EQ(10, (*reverse)->value());
EXPECT_EQ(10, (*creverse)->value());
++reverse;
++creverse;
++reverse;
++creverse;
EXPECT_EQ(2, (*reverse)->value());
EXPECT_EQ(2, (*creverse)->value());
++reverse;
++creverse;
EXPECT_EQ(0, (*reverse)->value());
EXPECT_EQ(0, (*creverse)->value());
++reverse;
++creverse;
EXPECT_EQ(set1->end(), it);
EXPECT_EQ(constSet.end(), cit);
EXPECT_EQ(set1->rend(), reverse);
EXPECT_EQ(constSet.rend(), creverse);
typename Set::iterator iX(set2->begin());
EXPECT_EQ(set2->end(), iX);
if (strong)
set1->remove(keepNumbersAlive[0]);
keepNumbersAlive[0] = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u + (strong ? 1u : 0u), set1->size());
EXPECT_EQ(2 + (strong ? 0 : 1), IntWrapper::s_destructorCalls);
typename Set::iterator i2(set1->begin());
if (strong) {
EXPECT_EQ(0, (*i2)->value());
++i2;
EXPECT_NE(set1->end(), i2);
}
EXPECT_EQ(103, (*i2)->value());
++i2;
EXPECT_NE(set1->end(), i2);
EXPECT_EQ(10, (*i2)->value());
++i2;
EXPECT_EQ(set1->end(), i2);
}
TEST(HeapTest, HeapWeakLinkedHashSet)
{
orderedSetHelper<HeapLinkedHashSet<Member<IntWrapper> > >(true);
orderedSetHelper<HeapLinkedHashSet<WeakMember<IntWrapper> > >(false);
orderedSetHelper<HeapListHashSet<Member<IntWrapper> > >(true);
}
class ThingWithDestructor {
public:
ThingWithDestructor()
: m_x(emptyValue)
{
s_liveThingsWithDestructor++;
}
ThingWithDestructor(int x)
: m_x(x)
{
s_liveThingsWithDestructor++;
}
ThingWithDestructor(const ThingWithDestructor&other)
{
*this = other;
s_liveThingsWithDestructor++;
}
~ThingWithDestructor()
{
s_liveThingsWithDestructor--;
}
int value() { return m_x; }
static int s_liveThingsWithDestructor;
unsigned hash() { return IntHash<int>::hash(m_x); }
private:
static const int emptyValue = 0;
int m_x;
};
int ThingWithDestructor::s_liveThingsWithDestructor;
struct ThingWithDestructorTraits : public HashTraits<ThingWithDestructor> {
static const bool needsDestruction = true;
};
static void heapMapDestructorHelper(bool clearMaps)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
ThingWithDestructor::s_liveThingsWithDestructor = 0;
typedef HeapHashMap<WeakMember<IntWrapper>, RefPtr<RefCountedAndGarbageCollected> > RefMap;
typedef HeapHashMap<
WeakMember<IntWrapper>,
ThingWithDestructor,
DefaultHash<WeakMember<IntWrapper> >::Hash,
HashTraits<WeakMember<IntWrapper> >,
ThingWithDestructorTraits> Map;
Persistent<Map> map(new Map());
Persistent<RefMap> refMap(new RefMap());
Persistent<IntWrapper> luck(IntWrapper::create(103));
int baseLine, refBaseLine;
{
Map stackMap;
RefMap stackRefMap;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
stackMap.add(IntWrapper::create(42), ThingWithDestructor(1729));
stackMap.add(luck, ThingWithDestructor(8128));
stackRefMap.add(IntWrapper::create(42), RefCountedAndGarbageCollected::create());
stackRefMap.add(luck, RefCountedAndGarbageCollected::create());
baseLine = ThingWithDestructor::s_liveThingsWithDestructor;
refBaseLine = RefCountedAndGarbageCollected::s_destructorCalls;
// Although the heap maps are on-stack, we can't expect prompt
// finalization of the elements, so when they go out of scope here we
// will not necessarily have called the relevant destructors.
}
// The RefCountedAndGarbageCollected things need an extra GC to discover
// that they are no longer ref counted.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(baseLine - 2, ThingWithDestructor::s_liveThingsWithDestructor);
EXPECT_EQ(refBaseLine + 2, RefCountedAndGarbageCollected::s_destructorCalls);
// Now use maps kept alive with persistents. Here we don't expect any
// destructors to be called before there have been GCs.
map->add(IntWrapper::create(42), ThingWithDestructor(1729));
map->add(luck, ThingWithDestructor(8128));
refMap->add(IntWrapper::create(42), RefCountedAndGarbageCollected::create());
refMap->add(luck, RefCountedAndGarbageCollected::create());
baseLine = ThingWithDestructor::s_liveThingsWithDestructor;
refBaseLine = RefCountedAndGarbageCollected::s_destructorCalls;
luck.clear();
if (clearMaps) {
map->clear(); // Clear map.
refMap->clear(); // Clear map.
} else {
map.clear(); // Clear Persistent handle, not map.
refMap.clear(); // Clear Persistent handle, not map.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
}
EXPECT_EQ(baseLine - 2, ThingWithDestructor::s_liveThingsWithDestructor);
// Need a GC to make sure that the RefCountedAndGarbageCollected thing
// noticies it's been decremented to zero.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(refBaseLine + 2, RefCountedAndGarbageCollected::s_destructorCalls);
}
TEST(HeapTest, HeapMapDestructor)
{
heapMapDestructorHelper(true);
heapMapDestructorHelper(false);
}
typedef HeapHashSet<PairWeakStrong> WeakStrongSet;
typedef HeapHashSet<PairWeakUnwrapped> WeakUnwrappedSet;
typedef HeapHashSet<PairStrongWeak> StrongWeakSet;
typedef HeapHashSet<PairUnwrappedWeak> UnwrappedWeakSet;
typedef HeapLinkedHashSet<PairWeakStrong> WeakStrongLinkedSet;
typedef HeapLinkedHashSet<PairWeakUnwrapped> WeakUnwrappedLinkedSet;
typedef HeapLinkedHashSet<PairStrongWeak> StrongWeakLinkedSet;
typedef HeapLinkedHashSet<PairUnwrappedWeak> UnwrappedWeakLinkedSet;
typedef HeapHashCountedSet<PairWeakStrong> WeakStrongCountedSet;
typedef HeapHashCountedSet<PairWeakUnwrapped> WeakUnwrappedCountedSet;
typedef HeapHashCountedSet<PairStrongWeak> StrongWeakCountedSet;
typedef HeapHashCountedSet<PairUnwrappedWeak> UnwrappedWeakCountedSet;
template<typename T>
T& iteratorExtractor(WTF::KeyValuePair<T, unsigned>& pair)
{
return pair.key;
}
template<typename T>
T& iteratorExtractor(T& notAPair)
{
return notAPair;
}
template<typename WSSet, typename SWSet, typename WUSet, typename UWSet>
void checkPairSets(
Persistent<WSSet>& weakStrong,
Persistent<SWSet>& strongWeak,
Persistent<WUSet>& weakUnwrapped,
Persistent<UWSet>& unwrappedWeak,
bool ones,
Persistent<IntWrapper>& two)
{
typename WSSet::iterator itWS = weakStrong->begin();
typename SWSet::iterator itSW = strongWeak->begin();
typename WUSet::iterator itWU = weakUnwrapped->begin();
typename UWSet::iterator itUW = unwrappedWeak->begin();
EXPECT_EQ(2u, weakStrong->size());
EXPECT_EQ(2u, strongWeak->size());
EXPECT_EQ(2u, weakUnwrapped->size());
EXPECT_EQ(2u, unwrappedWeak->size());
PairWeakStrong p = iteratorExtractor(*itWS);
PairStrongWeak p2 = iteratorExtractor(*itSW);
PairWeakUnwrapped p3 = iteratorExtractor(*itWU);
PairUnwrappedWeak p4 = iteratorExtractor(*itUW);
if (p.first == two && p.second == two)
++itWS;
if (p2.first == two && p2.second == two)
++itSW;
if (p3.first == two && p3.second == 2)
++itWU;
if (p4.first == 2 && p4.second == two)
++itUW;
p = iteratorExtractor(*itWS);
p2 = iteratorExtractor(*itSW);
p3 = iteratorExtractor(*itWU);
p4 = iteratorExtractor(*itUW);
IntWrapper* nullWrapper = 0;
if (ones) {
EXPECT_EQ(p.first->value(), 1);
EXPECT_EQ(p2.second->value(), 1);
EXPECT_EQ(p3.first->value(), 1);
EXPECT_EQ(p4.second->value(), 1);
} else {
EXPECT_EQ(p.first, nullWrapper);
EXPECT_EQ(p2.second, nullWrapper);
EXPECT_EQ(p3.first, nullWrapper);
EXPECT_EQ(p4.second, nullWrapper);
}
EXPECT_EQ(p.second->value(), 2);
EXPECT_EQ(p2.first->value(), 2);
EXPECT_EQ(p3.second, 2);
EXPECT_EQ(p4.first, 2);
EXPECT_TRUE(weakStrong->contains(PairWeakStrong(&*two, &*two)));
EXPECT_TRUE(strongWeak->contains(PairStrongWeak(&*two, &*two)));
EXPECT_TRUE(weakUnwrapped->contains(PairWeakUnwrapped(&*two, 2)));
EXPECT_TRUE(unwrappedWeak->contains(PairUnwrappedWeak(2, &*two)));
}
template<typename WSSet, typename SWSet, typename WUSet, typename UWSet>
void weakPairsHelper()
{
IntWrapper::s_destructorCalls = 0;
PersistentHeapVector<Member<IntWrapper> > keepNumbersAlive;
Persistent<WSSet> weakStrong = new WSSet();
Persistent<SWSet> strongWeak = new SWSet();
Persistent<WUSet> weakUnwrapped = new WUSet();
Persistent<UWSet> unwrappedWeak = new UWSet();
Persistent<IntWrapper> two = IntWrapper::create(2);
weakStrong->add(PairWeakStrong(IntWrapper::create(1), &*two));
weakStrong->add(PairWeakStrong(&*two, &*two));
strongWeak->add(PairStrongWeak(&*two, IntWrapper::create(1)));
strongWeak->add(PairStrongWeak(&*two, &*two));
weakUnwrapped->add(PairWeakUnwrapped(IntWrapper::create(1), 2));
weakUnwrapped->add(PairWeakUnwrapped(&*two, 2));
unwrappedWeak->add(PairUnwrappedWeak(2, IntWrapper::create(1)));
unwrappedWeak->add(PairUnwrappedWeak(2, &*two));
checkPairSets<WSSet, SWSet, WUSet, UWSet>(weakStrong, strongWeak, weakUnwrapped, unwrappedWeak, true, two);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
checkPairSets<WSSet, SWSet, WUSet, UWSet>(weakStrong, strongWeak, weakUnwrapped, unwrappedWeak, false, two);
}
TEST(HeapTest, HeapWeakPairs)
{
{
typedef HeapHashSet<PairWeakStrong> WeakStrongSet;
typedef HeapHashSet<PairWeakUnwrapped> WeakUnwrappedSet;
typedef HeapHashSet<PairStrongWeak> StrongWeakSet;
typedef HeapHashSet<PairUnwrappedWeak> UnwrappedWeakSet;
weakPairsHelper<WeakStrongSet, StrongWeakSet, WeakUnwrappedSet, UnwrappedWeakSet>();
}
{
typedef HeapListHashSet<PairWeakStrong> WeakStrongSet;
typedef HeapListHashSet<PairWeakUnwrapped> WeakUnwrappedSet;
typedef HeapListHashSet<PairStrongWeak> StrongWeakSet;
typedef HeapListHashSet<PairUnwrappedWeak> UnwrappedWeakSet;
weakPairsHelper<WeakStrongSet, StrongWeakSet, WeakUnwrappedSet, UnwrappedWeakSet>();
}
{
typedef HeapLinkedHashSet<PairWeakStrong> WeakStrongSet;
typedef HeapLinkedHashSet<PairWeakUnwrapped> WeakUnwrappedSet;
typedef HeapLinkedHashSet<PairStrongWeak> StrongWeakSet;
typedef HeapLinkedHashSet<PairUnwrappedWeak> UnwrappedWeakSet;
weakPairsHelper<WeakStrongSet, StrongWeakSet, WeakUnwrappedSet, UnwrappedWeakSet>();
}
}
TEST(HeapTest, HeapWeakCollectionTypes)
{
HeapStats initialHeapSize;
IntWrapper::s_destructorCalls = 0;
typedef HeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper> > WeakStrong;
typedef HeapHashMap<Member<IntWrapper>, WeakMember<IntWrapper> > StrongWeak;
typedef HeapHashMap<WeakMember<IntWrapper>, WeakMember<IntWrapper> > WeakWeak;
typedef HeapHashSet<WeakMember<IntWrapper> > WeakSet;
typedef HeapLinkedHashSet<WeakMember<IntWrapper> > WeakOrderedSet;
clearOutOldGarbage(&initialHeapSize);
const int weakStrongIndex = 0;
const int strongWeakIndex = 1;
const int weakWeakIndex = 2;
const int numberOfMapIndices = 3;
const int weakSetIndex = 3;
const int weakOrderedSetIndex = 4;
const int numberOfCollections = 5;
for (int testRun = 0; testRun < 4; testRun++) {
for (int collectionNumber = 0; collectionNumber < numberOfCollections; collectionNumber++) {
bool deleteAfterwards = (testRun == 1);
bool addAfterwards = (testRun == 2);
bool testThatIteratorsMakeStrong = (testRun == 3);
// The test doesn't work for strongWeak with deleting because we lost
// the key from the keepNumbersAlive array, so we can't do the lookup.
if (deleteAfterwards && collectionNumber == strongWeakIndex)
continue;
unsigned added = addAfterwards ? 100 : 0;
Persistent<WeakStrong> weakStrong = new WeakStrong();
Persistent<StrongWeak> strongWeak = new StrongWeak();
Persistent<WeakWeak> weakWeak = new WeakWeak();
Persistent<WeakSet> weakSet = new WeakSet();
Persistent<WeakOrderedSet> weakOrderedSet = new WeakOrderedSet();
PersistentHeapVector<Member<IntWrapper> > keepNumbersAlive;
for (int i = 0; i < 128; i += 2) {
IntWrapper* wrapped = IntWrapper::create(i);
IntWrapper* wrapped2 = IntWrapper::create(i + 1);
keepNumbersAlive.append(wrapped);
keepNumbersAlive.append(wrapped2);
weakStrong->add(wrapped, wrapped2);
strongWeak->add(wrapped2, wrapped);
weakWeak->add(wrapped, wrapped2);
weakSet->add(wrapped);
weakOrderedSet->add(wrapped);
}
EXPECT_EQ(64u, weakStrong->size());
EXPECT_EQ(64u, strongWeak->size());
EXPECT_EQ(64u, weakWeak->size());
EXPECT_EQ(64u, weakSet->size());
EXPECT_EQ(64u, weakOrderedSet->size());
// Collect garbage. This should change nothing since we are keeping
// alive the IntWrapper objects.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(64u, weakStrong->size());
EXPECT_EQ(64u, strongWeak->size());
EXPECT_EQ(64u, weakWeak->size());
EXPECT_EQ(64u, weakSet->size());
EXPECT_EQ(64u, weakOrderedSet->size());
for (int i = 0; i < 128; i += 2) {
IntWrapper* wrapped = keepNumbersAlive[i];
IntWrapper* wrapped2 = keepNumbersAlive[i + 1];
EXPECT_EQ(wrapped2, weakStrong->get(wrapped));
EXPECT_EQ(wrapped, strongWeak->get(wrapped2));
EXPECT_EQ(wrapped2, weakWeak->get(wrapped));
EXPECT_TRUE(weakSet->contains(wrapped));
EXPECT_TRUE(weakOrderedSet->contains(wrapped));
}
for (int i = 0; i < 128; i += 3)
keepNumbersAlive[i] = nullptr;
if (collectionNumber != weakStrongIndex)
weakStrong->clear();
if (collectionNumber != strongWeakIndex)
strongWeak->clear();
if (collectionNumber != weakWeakIndex)
weakWeak->clear();
if (collectionNumber != weakSetIndex)
weakSet->clear();
if (collectionNumber != weakOrderedSetIndex)
weakOrderedSet->clear();
if (testThatIteratorsMakeStrong) {
WeakStrong::iterator it1 = weakStrong->begin();
StrongWeak::iterator it2 = strongWeak->begin();
WeakWeak::iterator it3 = weakWeak->begin();
WeakSet::iterator it4 = weakSet->begin();
WeakOrderedSet::iterator it5 = weakOrderedSet->begin();
// Collect garbage. This should change nothing since the
// iterators make the collections strong.
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
if (collectionNumber == weakStrongIndex) {
EXPECT_EQ(64u, weakStrong->size());
MapIteratorCheck(it1, weakStrong->end(), 64);
} else if (collectionNumber == strongWeakIndex) {
EXPECT_EQ(64u, strongWeak->size());
MapIteratorCheck(it2, strongWeak->end(), 64);
} else if (collectionNumber == weakWeakIndex) {
EXPECT_EQ(64u, weakWeak->size());
MapIteratorCheck(it3, weakWeak->end(), 64);
} else if (collectionNumber == weakSetIndex) {
EXPECT_EQ(64u, weakSet->size());
SetIteratorCheck(it4, weakSet->end(), 64);
} else if (collectionNumber == weakOrderedSetIndex) {
EXPECT_EQ(64u, weakOrderedSet->size());
SetIteratorCheck(it5, weakOrderedSet->end(), 64);
}
} else {
// Collect garbage. This causes weak processing to remove
// things from the collections.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
unsigned count = 0;
for (int i = 0; i < 128; i += 2) {
bool firstAlive = keepNumbersAlive[i];
bool secondAlive = keepNumbersAlive[i + 1];
if (firstAlive && (collectionNumber == weakStrongIndex || collectionNumber == strongWeakIndex))
secondAlive = true;
if (firstAlive && secondAlive && collectionNumber < numberOfMapIndices) {
if (collectionNumber == weakStrongIndex) {
if (deleteAfterwards)
EXPECT_EQ(i + 1, weakStrong->take(keepNumbersAlive[i])->value());
} else if (collectionNumber == strongWeakIndex) {
if (deleteAfterwards)
EXPECT_EQ(i, strongWeak->take(keepNumbersAlive[i + 1])->value());
} else if (collectionNumber == weakWeakIndex) {
if (deleteAfterwards)
EXPECT_EQ(i + 1, weakWeak->take(keepNumbersAlive[i])->value());
}
if (!deleteAfterwards)
count++;
} else if (collectionNumber == weakSetIndex && firstAlive) {
ASSERT_TRUE(weakSet->contains(keepNumbersAlive[i]));
if (deleteAfterwards)
weakSet->remove(keepNumbersAlive[i]);
else
count++;
} else if (collectionNumber == weakOrderedSetIndex && firstAlive) {
ASSERT_TRUE(weakOrderedSet->contains(keepNumbersAlive[i]));
if (deleteAfterwards)
weakOrderedSet->remove(keepNumbersAlive[i]);
else
count++;
}
}
if (addAfterwards) {
for (int i = 1000; i < 1100; i++) {
IntWrapper* wrapped = IntWrapper::create(i);
keepNumbersAlive.append(wrapped);
weakStrong->add(wrapped, wrapped);
strongWeak->add(wrapped, wrapped);
weakWeak->add(wrapped, wrapped);
weakSet->add(wrapped);
weakOrderedSet->add(wrapped);
}
}
if (collectionNumber == weakStrongIndex)
EXPECT_EQ(count + added, weakStrong->size());
else if (collectionNumber == strongWeakIndex)
EXPECT_EQ(count + added, strongWeak->size());
else if (collectionNumber == weakWeakIndex)
EXPECT_EQ(count + added, weakWeak->size());
else if (collectionNumber == weakSetIndex)
EXPECT_EQ(count + added, weakSet->size());
else if (collectionNumber == weakOrderedSetIndex)
EXPECT_EQ(count + added, weakOrderedSet->size());
WeakStrong::iterator it1 = weakStrong->begin();
StrongWeak::iterator it2 = strongWeak->begin();
WeakWeak::iterator it3 = weakWeak->begin();
WeakSet::iterator it4 = weakSet->begin();
WeakOrderedSet::iterator it5 = weakOrderedSet->begin();
MapIteratorCheck(it1, weakStrong->end(), (collectionNumber == weakStrongIndex ? count : 0) + added);
MapIteratorCheck(it2, strongWeak->end(), (collectionNumber == strongWeakIndex ? count : 0) + added);
MapIteratorCheck(it3, weakWeak->end(), (collectionNumber == weakWeakIndex ? count : 0) + added);
SetIteratorCheck(it4, weakSet->end(), (collectionNumber == weakSetIndex ? count : 0) + added);
SetIteratorCheck(it5, weakOrderedSet->end(), (collectionNumber == weakOrderedSetIndex ? count : 0) + added);
}
for (unsigned i = 0; i < 128 + added; i++)
keepNumbersAlive[i] = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, weakStrong->size());
EXPECT_EQ(0u, strongWeak->size());
EXPECT_EQ(0u, weakWeak->size());
EXPECT_EQ(0u, weakSet->size());
EXPECT_EQ(0u, weakOrderedSet->size());
}
}
}
TEST(HeapTest, RefCountedGarbageCollected)
{
RefCountedAndGarbageCollected::s_destructorCalls = 0;
{
RefPtr<RefCountedAndGarbageCollected> refPtr3;
{
Persistent<RefCountedAndGarbageCollected> persistent;
{
RefPtr<RefCountedAndGarbageCollected> refPtr1 = RefCountedAndGarbageCollected::create();
RefPtr<RefCountedAndGarbageCollected> refPtr2 = RefCountedAndGarbageCollected::create();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, RefCountedAndGarbageCollected::s_destructorCalls);
persistent = refPtr1.get();
}
// Reference count is zero for both objects but one of
// them is kept alive by a persistent handle.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, RefCountedAndGarbageCollected::s_destructorCalls);
refPtr3 = persistent.get();
}
// The persistent handle is gone but the ref count has been
// increased to 1.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, RefCountedAndGarbageCollected::s_destructorCalls);
}
// Both persistent handle is gone and ref count is zero so the
// object can be collected.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, RefCountedAndGarbageCollected::s_destructorCalls);
}
TEST(HeapTest, RefCountedGarbageCollectedWithStackPointers)
{
RefCountedAndGarbageCollected::s_destructorCalls = 0;
RefCountedAndGarbageCollected2::s_destructorCalls = 0;
{
RefCountedAndGarbageCollected* pointer1 = 0;
RefCountedAndGarbageCollected2* pointer2 = 0;
{
RefPtr<RefCountedAndGarbageCollected> object1 = RefCountedAndGarbageCollected::create();
RefPtr<RefCountedAndGarbageCollected2> object2 = RefCountedAndGarbageCollected2::create();
pointer1 = object1.get();
pointer2 = object2.get();
void* objects[2] = { object1.get(), object2.get() };
RefCountedGarbageCollectedVisitor visitor(2, objects);
ThreadState::current()->visitPersistents(&visitor);
EXPECT_TRUE(visitor.validate());
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, RefCountedAndGarbageCollected::s_destructorCalls);
EXPECT_EQ(0, RefCountedAndGarbageCollected2::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, RefCountedAndGarbageCollected::s_destructorCalls);
EXPECT_EQ(0, RefCountedAndGarbageCollected2::s_destructorCalls);
// At this point, the reference counts of object1 and object2 are 0.
// Only pointer1 and pointer2 keep references to object1 and object2.
void* objects[] = { 0 };
RefCountedGarbageCollectedVisitor visitor(0, objects);
ThreadState::current()->visitPersistents(&visitor);
EXPECT_TRUE(visitor.validate());
{
RefPtr<RefCountedAndGarbageCollected> object1(pointer1);
RefPtr<RefCountedAndGarbageCollected2> object2(pointer2);
void* objects[2] = { object1.get(), object2.get() };
RefCountedGarbageCollectedVisitor visitor(2, objects);
ThreadState::current()->visitPersistents(&visitor);
EXPECT_TRUE(visitor.validate());
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, RefCountedAndGarbageCollected::s_destructorCalls);
EXPECT_EQ(0, RefCountedAndGarbageCollected2::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, RefCountedAndGarbageCollected::s_destructorCalls);
EXPECT_EQ(0, RefCountedAndGarbageCollected2::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, RefCountedAndGarbageCollected::s_destructorCalls);
EXPECT_EQ(1, RefCountedAndGarbageCollected2::s_destructorCalls);
}
TEST(HeapTest, WeakMembers)
{
Bar::s_live = 0;
{
Persistent<Bar> h1 = Bar::create();
Persistent<Weak> h4;
Persistent<WithWeakMember> h5;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
ASSERT_EQ(1u, Bar::s_live); // h1 is live.
{
Bar* h2 = Bar::create();
Bar* h3 = Bar::create();
h4 = Weak::create(h2, h3);
h5 = WithWeakMember::create(h2, h3);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(5u, Bar::s_live); // The on-stack pointer keeps h3 alive.
EXPECT_TRUE(h4->strongIsThere());
EXPECT_TRUE(h4->weakIsThere());
EXPECT_TRUE(h5->strongIsThere());
EXPECT_TRUE(h5->weakIsThere());
}
// h3 is collected, weak pointers from h4 and h5 don't keep it alive.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(4u, Bar::s_live);
EXPECT_TRUE(h4->strongIsThere());
EXPECT_FALSE(h4->weakIsThere()); // h3 is gone from weak pointer.
EXPECT_TRUE(h5->strongIsThere());
EXPECT_FALSE(h5->weakIsThere()); // h3 is gone from weak pointer.
h1.release(); // Zero out h1.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(3u, Bar::s_live); // Only h4, h5 and h2 are left.
EXPECT_TRUE(h4->strongIsThere()); // h2 is still pointed to from h4.
EXPECT_TRUE(h5->strongIsThere()); // h2 is still pointed to from h5.
}
// h4 and h5 have gone out of scope now and they were keeping h2 alive.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live); // All gone.
}
TEST(HeapTest, FinalizationObserver)
{
Persistent<FinalizationObserver<Observable> > o;
{
Observable* foo = Observable::create(Bar::create());
// |o| observes |foo|.
o = FinalizationObserver<Observable>::create(foo);
}
// FinalizationObserver doesn't have a strong reference to |foo|. So |foo|
// and its member will be collected.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
EXPECT_TRUE(o->didCallWillFinalize());
FinalizationObserverWithHashMap::s_didCallWillFinalize = false;
Observable* foo = Observable::create(Bar::create());
FinalizationObserverWithHashMap::ObserverMap& map = FinalizationObserverWithHashMap::observe(*foo);
EXPECT_EQ(1u, map.size());
foo = 0;
// FinalizationObserverWithHashMap doesn't have a strong reference to
// |foo|. So |foo| and its member will be collected.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, Bar::s_live);
EXPECT_EQ(0u, map.size());
EXPECT_TRUE(FinalizationObserverWithHashMap::s_didCallWillFinalize);
}
TEST(HeapTest, Comparisons)
{
Persistent<Bar> barPersistent = Bar::create();
Persistent<Foo> fooPersistent = Foo::create(barPersistent);
EXPECT_TRUE(barPersistent != fooPersistent);
barPersistent = fooPersistent;
EXPECT_TRUE(barPersistent == fooPersistent);
}
TEST(HeapTest, CheckAndMarkPointer)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
Vector<Address> objectAddresses;
Vector<Address> endAddresses;
Address largeObjectAddress;
Address largeObjectEndAddress;
CountingVisitor visitor;
for (int i = 0; i < 10; i++) {
SimpleObject* object = SimpleObject::create();
Address objectAddress = reinterpret_cast<Address>(object);
objectAddresses.append(objectAddress);
endAddresses.append(objectAddress + sizeof(SimpleObject) - 1);
}
LargeObject* largeObject = LargeObject::create();
largeObjectAddress = reinterpret_cast<Address>(largeObject);
largeObjectEndAddress = largeObjectAddress + sizeof(LargeObject) - 1;
// This is a low-level test where we call checkAndMarkPointer. This method
// causes the object start bitmap to be computed which requires the heap
// to be in a consistent state (e.g. the free allocation area must be put
// into a free list header). However when we call makeConsistentForGC it
// also clears out the freelists so we have to rebuild those before trying
// to allocate anything again. We do this by forcing a GC after doing the
// checkAndMarkPointer tests.
{
TestGCScope scope(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(scope.allThreadsParked()); // Fail the test if we could not park all threads.
Heap::makeConsistentForGC();
for (size_t i = 0; i < objectAddresses.size(); i++) {
EXPECT_TRUE(Heap::checkAndMarkPointer(&visitor, objectAddresses[i]));
EXPECT_TRUE(Heap::checkAndMarkPointer(&visitor, endAddresses[i]));
}
EXPECT_EQ(objectAddresses.size() * 2, visitor.count());
visitor.reset();
EXPECT_TRUE(Heap::checkAndMarkPointer(&visitor, largeObjectAddress));
EXPECT_TRUE(Heap::checkAndMarkPointer(&visitor, largeObjectEndAddress));
EXPECT_EQ(2ul, visitor.count());
visitor.reset();
}
// This forces a GC without stack scanning which results in the objects
// being collected. This will also rebuild the above mentioned freelists,
// however we don't rely on that below since we don't have any allocations.
clearOutOldGarbage(&initialHeapStats);
{
TestGCScope scope(ThreadState::HeapPointersOnStack);
EXPECT_TRUE(scope.allThreadsParked());
Heap::makeConsistentForGC();
for (size_t i = 0; i < objectAddresses.size(); i++) {
// We would like to assert that checkAndMarkPointer returned false
// here because the pointers no longer point into a valid object
// (it's been freed by the GCs. But checkAndMarkPointer will return
// true for any pointer that points into a heap page, regardless of
// whether it points at a valid object (this ensures the
// correctness of the page-based on-heap address caches), so we
// can't make that assert.
Heap::checkAndMarkPointer(&visitor, objectAddresses[i]);
Heap::checkAndMarkPointer(&visitor, endAddresses[i]);
}
EXPECT_EQ(0ul, visitor.count());
Heap::checkAndMarkPointer(&visitor, largeObjectAddress);
Heap::checkAndMarkPointer(&visitor, largeObjectEndAddress);
EXPECT_EQ(0ul, visitor.count());
}
// This round of GC is important to make sure that the object start
// bitmap are cleared out and that the free lists are rebuild.
clearOutOldGarbage(&initialHeapStats);
}
TEST(HeapTest, VisitOffHeapCollections)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
IntWrapper::s_destructorCalls = 0;
Persistent<OffHeapContainer> container = OffHeapContainer::create();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
container = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(OffHeapContainer::deadWrappers, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, PersistentHeapCollectionTypes)
{
HeapStats initialHeapSize;
IntWrapper::s_destructorCalls = 0;
typedef HeapVector<Member<IntWrapper> > Vec;
typedef PersistentHeapVector<Member<IntWrapper> > PVec;
typedef PersistentHeapHashSet<Member<IntWrapper> > PSet;
typedef PersistentHeapListHashSet<Member<IntWrapper> > PListSet;
typedef PersistentHeapLinkedHashSet<Member<IntWrapper> > PLinkedSet;
typedef PersistentHeapHashMap<Member<IntWrapper>, Member<IntWrapper> > PMap;
typedef PersistentHeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper> > WeakPMap;
typedef PersistentHeapDeque<Member<IntWrapper> > PDeque;
clearOutOldGarbage(&initialHeapSize);
{
PVec pVec;
PDeque pDeque;
PSet pSet;
PListSet pListSet;
PLinkedSet pLinkedSet;
PMap pMap;
WeakPMap wpMap;
IntWrapper* one(IntWrapper::create(1));
IntWrapper* two(IntWrapper::create(2));
IntWrapper* three(IntWrapper::create(3));
IntWrapper* four(IntWrapper::create(4));
IntWrapper* five(IntWrapper::create(5));
IntWrapper* six(IntWrapper::create(6));
IntWrapper* seven(IntWrapper::create(7));
IntWrapper* eight(IntWrapper::create(8));
IntWrapper* nine(IntWrapper::create(9));
Persistent<IntWrapper> ten(IntWrapper::create(10));
IntWrapper* eleven(IntWrapper::create(11));
pVec.append(one);
pVec.append(two);
pDeque.append(seven);
pDeque.append(two);
Vec* vec = new Vec();
vec->swap(pVec);
pVec.append(two);
pVec.append(three);
pSet.add(four);
pListSet.add(eight);
pLinkedSet.add(nine);
pMap.add(five, six);
wpMap.add(ten, eleven);
// Collect |vec| and |one|.
vec = 0;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
EXPECT_EQ(2u, pVec.size());
EXPECT_EQ(two, pVec.at(0));
EXPECT_EQ(three, pVec.at(1));
EXPECT_EQ(2u, pDeque.size());
EXPECT_EQ(seven, pDeque.first());
EXPECT_EQ(seven, pDeque.takeFirst());
EXPECT_EQ(two, pDeque.first());
EXPECT_EQ(1u, pDeque.size());
EXPECT_EQ(1u, pSet.size());
EXPECT_TRUE(pSet.contains(four));
EXPECT_EQ(1u, pListSet.size());
EXPECT_TRUE(pListSet.contains(eight));
EXPECT_EQ(1u, pLinkedSet.size());
EXPECT_TRUE(pLinkedSet.contains(nine));
EXPECT_EQ(1u, pMap.size());
EXPECT_EQ(six, pMap.get(five));
EXPECT_EQ(1u, wpMap.size());
EXPECT_EQ(eleven, wpMap.get(ten));
ten.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, wpMap.size());
}
// Collect previous roots.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(11, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, CollectionNesting)
{
HeapStats initialStats;
clearOutOldGarbage(&initialStats);
int* key = &IntWrapper::s_destructorCalls;
IntWrapper::s_destructorCalls = 0;
typedef HeapVector<Member<IntWrapper> > IntVector;
typedef HeapDeque<Member<IntWrapper> > IntDeque;
HeapHashMap<void*, IntVector>* map = new HeapHashMap<void*, IntVector>();
HeapHashMap<void*, IntDeque>* map2 = new HeapHashMap<void*, IntDeque>();
map->add(key, IntVector());
map2->add(key, IntDeque());
HeapHashMap<void*, IntVector>::iterator it = map->find(key);
EXPECT_EQ(0u, map->get(key).size());
HeapHashMap<void*, IntDeque>::iterator it2 = map2->find(key);
EXPECT_EQ(0u, map2->get(key).size());
it->value.append(IntWrapper::create(42));
EXPECT_EQ(1u, map->get(key).size());
it2->value.append(IntWrapper::create(42));
EXPECT_EQ(1u, map2->get(key).size());
Persistent<HeapHashMap<void*, IntVector> > keepAlive(map);
Persistent<HeapHashMap<void*, IntDeque> > keepAlive2(map2);
for (int i = 0; i < 100; i++) {
map->add(key + 1 + i, IntVector());
map2->add(key + 1 + i, IntDeque());
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, map->get(key).size());
EXPECT_EQ(1u, map2->get(key).size());
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
keepAlive = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, GarbageCollectedMixin)
{
HeapStats initialHeapStats;
clearOutOldGarbage(&initialHeapStats);
Persistent<UseMixin> usemixin = UseMixin::create();
EXPECT_EQ(0, UseMixin::s_traceCount);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, UseMixin::s_traceCount);
Persistent<Mixin> mixin = usemixin;
usemixin = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, UseMixin::s_traceCount);
PersistentHeapHashSet<WeakMember<Mixin> > weakMap;
weakMap.add(UseMixin::create());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, weakMap.size());
}
TEST(HeapTest, CollectionNesting2)
{
HeapStats initialStats;
clearOutOldGarbage(&initialStats);
void* key = &IntWrapper::s_destructorCalls;
IntWrapper::s_destructorCalls = 0;
typedef HeapHashSet<Member<IntWrapper> > IntSet;
HeapHashMap<void*, IntSet>* map = new HeapHashMap<void*, IntSet>();
map->add(key, IntSet());
HeapHashMap<void*, IntSet>::iterator it = map->find(key);
EXPECT_EQ(0u, map->get(key).size());
it->value.add(IntWrapper::create(42));
EXPECT_EQ(1u, map->get(key).size());
Persistent<HeapHashMap<void*, IntSet> > keepAlive(map);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, map->get(key).size());
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, CollectionNesting3)
{
HeapStats initialStats;
clearOutOldGarbage(&initialStats);
IntWrapper::s_destructorCalls = 0;
typedef HeapVector<Member<IntWrapper> > IntVector;
typedef HeapDeque<Member<IntWrapper> > IntDeque;
HeapVector<IntVector>* vector = new HeapVector<IntVector>();
HeapDeque<IntDeque>* deque = new HeapDeque<IntDeque>();
vector->append(IntVector());
deque->append(IntDeque());
HeapVector<IntVector>::iterator it = vector->begin();
HeapDeque<IntDeque>::iterator it2 = deque->begin();
EXPECT_EQ(0u, it->size());
EXPECT_EQ(0u, it2->size());
it->append(IntWrapper::create(42));
it2->append(IntWrapper::create(42));
EXPECT_EQ(1u, it->size());
EXPECT_EQ(1u, it2->size());
Persistent<HeapVector<IntVector> > keepAlive(vector);
Persistent<HeapDeque<IntDeque> > keepAlive2(deque);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, it->size());
EXPECT_EQ(1u, it2->size());
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, EmbeddedInVector)
{
HeapStats initialStats;
clearOutOldGarbage(&initialStats);
SimpleFinalizedObject::s_destructorCalls = 0;
{
PersistentHeapVector<VectorObject, 2> inlineVector;
PersistentHeapVector<VectorObject> outlineVector;
VectorObject i1, i2;
inlineVector.append(i1);
inlineVector.append(i2);
VectorObject o1, o2;
outlineVector.append(o1);
outlineVector.append(o2);
PersistentHeapVector<VectorObjectInheritedTrace> vectorInheritedTrace;
VectorObjectInheritedTrace it1, it2;
vectorInheritedTrace.append(it1);
vectorInheritedTrace.append(it2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, SimpleFinalizedObject::s_destructorCalls);
// Since VectorObjectNoTrace has no trace method it will
// not be traced and hence be collected when doing GC.
// We trace items in a collection braced on the item's
// having a trace method. This is determined via the
// NeedsTracing trait in wtf/TypeTraits.h.
PersistentHeapVector<VectorObjectNoTrace> vectorNoTrace;
VectorObjectNoTrace n1, n2;
vectorNoTrace.append(n1);
vectorNoTrace.append(n2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, SimpleFinalizedObject::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(8, SimpleFinalizedObject::s_destructorCalls);
}
TEST(HeapTest, EmbeddedInDeque)
{
HeapStats initialStats;
clearOutOldGarbage(&initialStats);
SimpleFinalizedObject::s_destructorCalls = 0;
{
PersistentHeapDeque<VectorObject, 2> inlineDeque;
PersistentHeapDeque<VectorObject> outlineDeque;
VectorObject i1, i2;
inlineDeque.append(i1);
inlineDeque.append(i2);
VectorObject o1, o2;
outlineDeque.append(o1);
outlineDeque.append(o2);
PersistentHeapDeque<VectorObjectInheritedTrace> dequeInheritedTrace;
VectorObjectInheritedTrace it1, it2;
dequeInheritedTrace.append(it1);
dequeInheritedTrace.append(it2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, SimpleFinalizedObject::s_destructorCalls);
// Since VectorObjectNoTrace has no trace method it will
// not be traced and hence be collected when doing GC.
// We trace items in a collection braced on the item's
// having a trace method. This is determined via the
// NeedsTracing trait in wtf/TypeTraits.h.
PersistentHeapDeque<VectorObjectNoTrace> dequeNoTrace;
VectorObjectNoTrace n1, n2;
dequeNoTrace.append(n1);
dequeNoTrace.append(n2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, SimpleFinalizedObject::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(8, SimpleFinalizedObject::s_destructorCalls);
}
template<typename Set>
void rawPtrInHashHelper()
{
Set set;
set.add(new int(42));
set.add(new int(42));
EXPECT_EQ(2u, set.size());
for (typename Set::iterator it = set.begin(); it != set.end(); ++it) {
EXPECT_EQ(42, **it);
delete *it;
}
}
TEST(HeapTest, RawPtrInHash)
{
rawPtrInHashHelper<HashSet<RawPtr<int> > >();
rawPtrInHashHelper<ListHashSet<RawPtr<int> > >();
rawPtrInHashHelper<LinkedHashSet<RawPtr<int> > >();
}
TEST(HeapTest, HeapTerminatedArray)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
IntWrapper::s_destructorCalls = 0;
HeapTerminatedArray<TerminatedArrayItem>* arr = 0;
const size_t prefixSize = 4;
const size_t suffixSize = 4;
{
HeapTerminatedArrayBuilder<TerminatedArrayItem> builder(arr);
builder.grow(prefixSize);
for (size_t i = 0; i < prefixSize; i++)
builder.append(TerminatedArrayItem(IntWrapper::create(i)));
arr = builder.release();
}
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
EXPECT_EQ(prefixSize, arr->size());
for (size_t i = 0; i < prefixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).payload()->value()));
{
HeapTerminatedArrayBuilder<TerminatedArrayItem> builder(arr);
builder.grow(suffixSize);
for (size_t i = 0; i < suffixSize; i++)
builder.append(TerminatedArrayItem(IntWrapper::create(prefixSize + i)));
arr = builder.release();
}
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
EXPECT_EQ(prefixSize + suffixSize, arr->size());
for (size_t i = 0; i < prefixSize + suffixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).payload()->value()));
{
Persistent<HeapTerminatedArray<TerminatedArrayItem> > persistentArr = arr;
arr = 0;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
arr = persistentArr.get();
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
EXPECT_EQ(prefixSize + suffixSize, arr->size());
for (size_t i = 0; i < prefixSize + suffixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).payload()->value()));
}
arr = 0;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(8, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, HeapLinkedStack)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
IntWrapper::s_destructorCalls = 0;
HeapLinkedStack<TerminatedArrayItem>* stack = new HeapLinkedStack<TerminatedArrayItem>();
const size_t stackSize = 10;
for (size_t i = 0; i < stackSize; i++)
stack->push(TerminatedArrayItem(IntWrapper::create(i)));
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
EXPECT_EQ(stackSize, stack->size());
while (!stack->isEmpty()) {
EXPECT_EQ(stack->size() - 1, static_cast<size_t>(stack->peek().payload()->value()));
stack->pop();
}
Persistent<HeapLinkedStack<TerminatedArrayItem> > pStack = stack;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(stackSize, static_cast<size_t>(IntWrapper::s_destructorCalls));
EXPECT_EQ(0u, pStack->size());
}
TEST(HeapTest, AllocationDuringFinalization)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
IntWrapper::s_destructorCalls = 0;
OneKiloByteObject::s_destructorCalls = 0;
Persistent<IntWrapper> wrapper;
new FinalizationAllocator(&wrapper);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
// Check that the wrapper allocated during finalization is not
// swept away and zapped later in the same sweeping phase.
EXPECT_EQ(42, wrapper->value());
wrapper.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(10, IntWrapper::s_destructorCalls);
EXPECT_EQ(512, OneKiloByteObject::s_destructorCalls);
}
class SimpleClassWithDestructor {
public:
SimpleClassWithDestructor() { }
~SimpleClassWithDestructor()
{
s_wasDestructed = true;
}
static bool s_wasDestructed;
};
bool SimpleClassWithDestructor::s_wasDestructed;
class RefCountedWithDestructor : public RefCounted<RefCountedWithDestructor> {
public:
RefCountedWithDestructor() { }
~RefCountedWithDestructor()
{
s_wasDestructed = true;
}
static bool s_wasDestructed;
};
bool RefCountedWithDestructor::s_wasDestructed;
template<typename Set>
void destructorsCalledOnGC(bool addLots)
{
RefCountedWithDestructor::s_wasDestructed = false;
{
Set set;
RefCountedWithDestructor* hasDestructor = new RefCountedWithDestructor();
set.add(adoptRef(hasDestructor));
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
if (addLots) {
for (int i = 0; i < 1000; i++) {
set.add(adoptRef(new RefCountedWithDestructor()));
}
}
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
}
// The destructors of the sets don't call the destructors of the elements
// in the heap sets. You have to actually remove the elments, call clear()
// or have a GC to get the destructors called.
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_TRUE(RefCountedWithDestructor::s_wasDestructed);
}
template<typename Set>
void destructorsCalledOnClear(bool addLots)
{
RefCountedWithDestructor::s_wasDestructed = false;
Set set;
RefCountedWithDestructor* hasDestructor = new RefCountedWithDestructor();
set.add(adoptRef(hasDestructor));
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
if (addLots) {
for (int i = 0; i < 1000; i++) {
set.add(adoptRef(new RefCountedWithDestructor()));
}
}
EXPECT_FALSE(RefCountedWithDestructor::s_wasDestructed);
set.clear();
EXPECT_TRUE(RefCountedWithDestructor::s_wasDestructed);
}
TEST(HeapTest, DestructorsCalled)
{
HeapHashMap<SimpleClassWithDestructor*, OwnPtr<SimpleClassWithDestructor> > map;
SimpleClassWithDestructor* hasDestructor = new SimpleClassWithDestructor();
map.add(hasDestructor, adoptPtr(hasDestructor));
SimpleClassWithDestructor::s_wasDestructed = false;
map.clear();
EXPECT_TRUE(SimpleClassWithDestructor::s_wasDestructed);
destructorsCalledOnClear<HeapHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnClear<HeapListHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnClear<HeapLinkedHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnClear<HeapHashSet<RefPtr<RefCountedWithDestructor> > >(true);
destructorsCalledOnClear<HeapListHashSet<RefPtr<RefCountedWithDestructor> > >(true);
destructorsCalledOnClear<HeapLinkedHashSet<RefPtr<RefCountedWithDestructor> > >(true);
destructorsCalledOnGC<HeapHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnGC<HeapListHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnGC<HeapLinkedHashSet<RefPtr<RefCountedWithDestructor> > >(false);
destructorsCalledOnGC<HeapHashSet<RefPtr<RefCountedWithDestructor> > >(true);
destructorsCalledOnGC<HeapListHashSet<RefPtr<RefCountedWithDestructor> > >(true);
destructorsCalledOnGC<HeapLinkedHashSet<RefPtr<RefCountedWithDestructor> > >(true);
}
class MixinA : public GarbageCollectedMixin {
public:
MixinA() : m_obj(IntWrapper::create(100)) { }
virtual void trace(Visitor* visitor)
{
visitor->trace(m_obj);
}
Member<IntWrapper> m_obj;
};
class MixinB : public GarbageCollectedMixin {
public:
MixinB() : m_obj(IntWrapper::create(101)) { }
virtual void trace(Visitor* visitor)
{
visitor->trace(m_obj);
}
Member<IntWrapper> m_obj;
};
class MultipleMixins : public GarbageCollected<MultipleMixins>, public MixinA, public MixinB {
USING_GARBAGE_COLLECTED_MIXIN(MultipleMixins);
public:
MultipleMixins() : m_obj(IntWrapper::create(102)) { }
virtual void trace(Visitor* visitor)
{
visitor->trace(m_obj);
MixinA::trace(visitor);
MixinB::trace(visitor);
}
Member<IntWrapper> m_obj;
};
static const bool s_isMixinTrue = IsGarbageCollectedMixin<MultipleMixins>::value;
static const bool s_isMixinFalse = IsGarbageCollectedMixin<IntWrapper>::value;
TEST(HeapTest, MultipleMixins)
{
EXPECT_TRUE(s_isMixinTrue);
EXPECT_FALSE(s_isMixinFalse);
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
IntWrapper::s_destructorCalls = 0;
MultipleMixins* obj = new MultipleMixins();
{
Persistent<MixinA> a = obj;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
}
{
Persistent<MixinB> b = obj;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(3, IntWrapper::s_destructorCalls);
}
class GCParkingThreadTester {
public:
static void test()
{
createThread(&sleeperMainFunc, 0, "SleepingThread");
// Wait for the sleeper to run.
while (!s_sleeperRunning) {
yield();
}
{
// Expect the first attempt to park the sleeping thread to fail
TestGCScope scope(ThreadState::NoHeapPointersOnStack);
EXPECT_FALSE(scope.allThreadsParked());
}
s_sleeperDone = true;
// Wait for the sleeper to finish.
while (s_sleeperRunning) {
// We enter the safepoint here since the sleeper thread will detach
// causing it to GC.
ThreadState::current()->safePoint(ThreadState::NoHeapPointersOnStack);
yield();
}
{
// Since the sleeper thread has detached this is the only thread.
TestGCScope scope(ThreadState::NoHeapPointersOnStack);
EXPECT_TRUE(scope.allThreadsParked());
}
}
private:
static void sleeperMainFunc(void* data)
{
ThreadState::attach();
s_sleeperRunning = true;
// Simulate a long running op that is not entering a safepoint.
while (!s_sleeperDone) {
yield();
}
ThreadState::detach();
s_sleeperRunning = false;
}
static volatile bool s_sleeperRunning;
static volatile bool s_sleeperDone;
};
volatile bool GCParkingThreadTester::s_sleeperRunning = false;
volatile bool GCParkingThreadTester::s_sleeperDone = false;
TEST(HeapTest, GCParkingTimeout)
{
GCParkingThreadTester::test();
}
TEST(HeapTest, NeedsAdjustAndMark)
{
// class Mixin : public GarbageCollectedMixin {};
EXPECT_TRUE(NeedsAdjustAndMark<Mixin>::value);
EXPECT_TRUE(NeedsAdjustAndMark<const Mixin>::value);
// class SimpleObject : public GarbageCollected<SimpleObject> {};
EXPECT_FALSE(NeedsAdjustAndMark<SimpleObject>::value);
EXPECT_FALSE(NeedsAdjustAndMark<const SimpleObject>::value);
// class UseMixin : public SimpleObject, public Mixin {};
EXPECT_FALSE(NeedsAdjustAndMark<UseMixin>::value);
EXPECT_FALSE(NeedsAdjustAndMark<const UseMixin>::value);
}
template<typename Set>
void setWithCustomWeaknessHandling()
{
typedef typename Set::iterator Iterator;
Persistent<IntWrapper> livingInt(IntWrapper::create(42));
Persistent<Set> set1(new Set());
{
Set set2;
Set* set3 = new Set();
set2.add(PairWithWeakHandling(IntWrapper::create(0), IntWrapper::create(1)));
set3->add(PairWithWeakHandling(IntWrapper::create(2), IntWrapper::create(3)));
set1->add(PairWithWeakHandling(IntWrapper::create(4), IntWrapper::create(5)));
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
// The first set is pointed to from a persistent, so it's referenced, but
// the weak processing may have taken place.
if (set1->size()) {
Iterator i1 = set1->begin();
EXPECT_EQ(4, i1->first->value());
EXPECT_EQ(5, i1->second->value());
}
// The second set is on-stack, so its backing store must be referenced from
// the stack. That makes the weak references strong.
Iterator i2 = set2.begin();
EXPECT_EQ(0, i2->first->value());
EXPECT_EQ(1, i2->second->value());
// The third set is pointed to from the stack, so it's referenced, but the
// weak processing may have taken place.
if (set3->size()) {
Iterator i3 = set3->begin();
EXPECT_EQ(2, i3->first->value());
EXPECT_EQ(3, i3->second->value());
}
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, set1->size());
set1->add(PairWithWeakHandling(IntWrapper::create(103), livingInt));
set1->add(PairWithWeakHandling(livingInt, IntWrapper::create(103))); // This one gets zapped at GC time because nothing holds the 103 alive.
set1->add(PairWithWeakHandling(IntWrapper::create(103), IntWrapper::create(103))); // This one gets zapped too.
set1->add(PairWithWeakHandling(livingInt, livingInt));
set1->add(PairWithWeakHandling(livingInt, livingInt)); // This one is identical to the previous and doesn't add anything.
EXPECT_EQ(4u, set1->size());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u, set1->size());
Iterator i1 = set1->begin();
EXPECT_TRUE(i1->first->value() == 103 || i1->first == livingInt);
EXPECT_EQ(livingInt, i1->second);
++i1;
EXPECT_TRUE(i1->first->value() == 103 || i1->first == livingInt);
EXPECT_EQ(livingInt, i1->second);
}
TEST(HeapTest, SetWithCustomWeaknessHandling)
{
setWithCustomWeaknessHandling<HeapHashSet<PairWithWeakHandling> >();
setWithCustomWeaknessHandling<HeapLinkedHashSet<PairWithWeakHandling> >();
}
TEST(HeapTest, MapWithCustomWeaknessHandling)
{
typedef HeapHashMap<PairWithWeakHandling, RefPtr<OffHeapInt> > Map;
typedef Map::iterator Iterator;
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
OffHeapInt::s_destructorCalls = 0;
Persistent<Map> map1(new Map());
Persistent<IntWrapper> livingInt(IntWrapper::create(42));
{
Map map2;
Map* map3 = new Map();
map2.add(PairWithWeakHandling(IntWrapper::create(0), IntWrapper::create(1)), OffHeapInt::create(1001));
map3->add(PairWithWeakHandling(IntWrapper::create(2), IntWrapper::create(3)), OffHeapInt::create(1002));
map1->add(PairWithWeakHandling(IntWrapper::create(4), IntWrapper::create(5)), OffHeapInt::create(1003));
EXPECT_EQ(0, OffHeapInt::s_destructorCalls);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
// The first map2 is pointed to from a persistent, so it's referenced, but
// the weak processing may have taken place.
if (map1->size()) {
Iterator i1 = map1->begin();
EXPECT_EQ(4, i1->key.first->value());
EXPECT_EQ(5, i1->key.second->value());
EXPECT_EQ(1003, i1->value->value());
}
// The second map2 is on-stack, so its backing store must be referenced from
// the stack. That makes the weak references strong.
Iterator i2 = map2.begin();
EXPECT_EQ(0, i2->key.first->value());
EXPECT_EQ(1, i2->key.second->value());
EXPECT_EQ(1001, i2->value->value());
// The third map2 is pointed to from the stack, so it's referenced, but the
// weak processing may have taken place.
if (map3->size()) {
Iterator i3 = map3->begin();
EXPECT_EQ(2, i3->key.first->value());
EXPECT_EQ(3, i3->key.second->value());
EXPECT_EQ(1002, i3->value->value());
}
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, map1->size());
EXPECT_EQ(3, OffHeapInt::s_destructorCalls);
OffHeapInt::s_destructorCalls = 0;
map1->add(PairWithWeakHandling(IntWrapper::create(103), livingInt), OffHeapInt::create(2000));
map1->add(PairWithWeakHandling(livingInt, IntWrapper::create(103)), OffHeapInt::create(2001)); // This one gets zapped at GC time because nothing holds the 103 alive.
map1->add(PairWithWeakHandling(IntWrapper::create(103), IntWrapper::create(103)), OffHeapInt::create(2002)); // This one gets zapped too.
RefPtr<OffHeapInt> dupeInt(OffHeapInt::create(2003));
map1->add(PairWithWeakHandling(livingInt, livingInt), dupeInt);
map1->add(PairWithWeakHandling(livingInt, livingInt), dupeInt); // This one is identical to the previous and doesn't add anything.
dupeInt.clear();
EXPECT_EQ(0, OffHeapInt::s_destructorCalls);
EXPECT_EQ(4u, map1->size());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, OffHeapInt::s_destructorCalls);
EXPECT_EQ(2u, map1->size());
Iterator i1 = map1->begin();
EXPECT_TRUE(i1->key.first->value() == 103 || i1->key.first == livingInt);
EXPECT_EQ(livingInt, i1->key.second);
++i1;
EXPECT_TRUE(i1->key.first->value() == 103 || i1->key.first == livingInt);
EXPECT_EQ(livingInt, i1->key.second);
}
TEST(HeapTest, MapWithCustomWeaknessHandling2)
{
typedef HeapHashMap<RefPtr<OffHeapInt>, PairWithWeakHandling> Map;
typedef Map::iterator Iterator;
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
OffHeapInt::s_destructorCalls = 0;
Persistent<Map> map1(new Map());
Persistent<IntWrapper> livingInt(IntWrapper::create(42));
{
Map map2;
Map* map3 = new Map();
map2.add(OffHeapInt::create(1001), PairWithWeakHandling(IntWrapper::create(0), IntWrapper::create(1)));
map3->add(OffHeapInt::create(1002), PairWithWeakHandling(IntWrapper::create(2), IntWrapper::create(3)));
map1->add(OffHeapInt::create(1003), PairWithWeakHandling(IntWrapper::create(4), IntWrapper::create(5)));
EXPECT_EQ(0, OffHeapInt::s_destructorCalls);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
// The first map2 is pointed to from a persistent, so it's referenced, but
// the weak processing may have taken place.
if (map1->size()) {
Iterator i1 = map1->begin();
EXPECT_EQ(4, i1->value.first->value());
EXPECT_EQ(5, i1->value.second->value());
EXPECT_EQ(1003, i1->key->value());
}
// The second map2 is on-stack, so its backing store must be referenced from
// the stack. That makes the weak references strong.
Iterator i2 = map2.begin();
EXPECT_EQ(0, i2->value.first->value());
EXPECT_EQ(1, i2->value.second->value());
EXPECT_EQ(1001, i2->key->value());
// The third map2 is pointed to from the stack, so it's referenced, but the
// weak processing may have taken place.
if (map3->size()) {
Iterator i3 = map3->begin();
EXPECT_EQ(2, i3->value.first->value());
EXPECT_EQ(3, i3->value.second->value());
EXPECT_EQ(1002, i3->key->value());
}
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, map1->size());
EXPECT_EQ(3, OffHeapInt::s_destructorCalls);
OffHeapInt::s_destructorCalls = 0;
map1->add(OffHeapInt::create(2000), PairWithWeakHandling(IntWrapper::create(103), livingInt));
map1->add(OffHeapInt::create(2001), PairWithWeakHandling(livingInt, IntWrapper::create(103))); // This one gets zapped at GC time because nothing holds the 103 alive.
map1->add(OffHeapInt::create(2002), PairWithWeakHandling(IntWrapper::create(103), IntWrapper::create(103))); // This one gets zapped too.
RefPtr<OffHeapInt> dupeInt(OffHeapInt::create(2003));
map1->add(dupeInt, PairWithWeakHandling(livingInt, livingInt));
map1->add(dupeInt, PairWithWeakHandling(livingInt, livingInt)); // This one is identical to the previous and doesn't add anything.
dupeInt.clear();
EXPECT_EQ(0, OffHeapInt::s_destructorCalls);
EXPECT_EQ(4u, map1->size());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2, OffHeapInt::s_destructorCalls);
EXPECT_EQ(2u, map1->size());
Iterator i1 = map1->begin();
EXPECT_TRUE(i1->value.first->value() == 103 || i1->value.first == livingInt);
EXPECT_EQ(livingInt, i1->value.second);
++i1;
EXPECT_TRUE(i1->value.first->value() == 103 || i1->value.first == livingInt);
EXPECT_EQ(livingInt, i1->value.second);
}
static void addElementsToWeakMap(HeapHashMap<int, WeakMember<IntWrapper> >* map)
{
// Key cannot be zero in hashmap.
for (int i = 1; i < 11; i++)
map->add(i, IntWrapper::create(i));
}
// crbug.com/402426
// If it doesn't assert a concurrent modification to the map, then it's passing.
TEST(HeapTest, RegressNullIsStrongified)
{
Persistent<HeapHashMap<int, WeakMember<IntWrapper> > > map = new HeapHashMap<int, WeakMember<IntWrapper> >();
addElementsToWeakMap(map);
HeapHashMap<int, WeakMember<IntWrapper> >::AddResult result = map->add(800, nullptr);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
result.storedValue->value = IntWrapper::create(42);
}
TEST(HeapTest, Bind)
{
Closure closure = bind(&Bar::trace, Bar::create(), static_cast<Visitor*>(0));
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// The closure should have a persistent handle to the Bar.
EXPECT_EQ(1u, Bar::s_live);
Closure closure2 = bind(&Bar::trace, RawPtr<Bar>(Bar::create()), static_cast<Visitor*>(0));
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// The closure should have a persistent handle to the Bar.
EXPECT_EQ(2u, Bar::s_live);
// RawPtr<OffHeapInt> should not make Persistent.
Closure closure3 = bind(&OffHeapInt::voidFunction, RawPtr<OffHeapInt>(OffHeapInt::create(1).get()));
UseMixin::s_traceCount = 0;
Mixin* mixin = UseMixin::create();
Closure mixinClosure = bind(&Mixin::trace, mixin, static_cast<Visitor*>(0));
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// The closure should have a persistent handle to the mixin.
EXPECT_EQ(1, UseMixin::s_traceCount);
}
typedef HeapHashSet<WeakMember<IntWrapper> > WeakSet;
// These special traits will remove a set from a map when the set is empty.
struct EmptyClearingHashSetTraits : HashTraits<WeakSet> {
static const WTF::WeakHandlingFlag weakHandlingFlag = WTF::WeakHandlingInCollections;
static bool traceInCollection(Visitor* visitor, WeakSet& set, WTF::ShouldWeakPointersBeMarkedStrongly strongify)
{
bool liveEntriesFound = false;
WeakSet::iterator end = set.end();
for (WeakSet::iterator it = set.begin(); it != end; ++it) {
if (visitor->isAlive(*it)) {
liveEntriesFound = true;
break;
}
}
// If there are live entries in the set then the set cannot be removed
// from the map it is contained in, and we need to mark it (and its
// backing) live. We just trace normally, which will invoke the normal
// weak handling for any entries that are not live.
if (liveEntriesFound)
set.trace(visitor);
return !liveEntriesFound;
}
};
// This is an example to show how you can remove entries from a T->WeakSet map
// when the weak sets become empty. For this example we are using a type that
// is given to use (HeapHashSet) rather than a type of our own. This means:
// 1) We can't just override the HashTrait for the type since this would affect
// all collections that use this kind of weak set. Instead we have our own
// traits and use a map with custom traits for the value type. These traits
// are the 5th template parameter, so we have to supply default values for
// the 3rd and 4th template parameters
// 2) We can't just inherit from WeakHandlingHashTraits, since that trait
// assumes we can add methods to the type, but we can't add methods to
// HeapHashSet.
TEST(HeapTest, RemoveEmptySets)
{
HeapStats initialHeapSize;
clearOutOldGarbage(&initialHeapSize);
OffHeapInt::s_destructorCalls = 0;
Persistent<IntWrapper> livingInt(IntWrapper::create(42));
typedef RefPtr<OffHeapInt> Key;
typedef HeapHashMap<Key, WeakSet, WTF::DefaultHash<Key>::Hash, HashTraits<Key>, EmptyClearingHashSetTraits> Map;
Persistent<Map> map(new Map());
map->add(OffHeapInt::create(1), WeakSet());
{
WeakSet& set = map->begin()->value;
set.add(IntWrapper::create(103)); // Weak set can't hold this long.
set.add(livingInt); // This prevents the set from being emptied.
EXPECT_EQ(2u, set.size());
}
// The set we add here is empty, so the entry will be removed from the map
// at the next GC.
map->add(OffHeapInt::create(2), WeakSet());
EXPECT_EQ(2u, map->size());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, map->size()); // The one with key 2 was removed.
EXPECT_EQ(1, OffHeapInt::s_destructorCalls);
{
WeakSet& set = map->begin()->value;
EXPECT_EQ(1u, set.size());
}
livingInt.clear(); // The weak set can no longer keep the '42' alive now.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, map->size());
}
TEST(HeapTest, EphemeronsInEphemerons)
{
typedef HeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper> > InnerMap;
typedef HeapHashMap<WeakMember<IntWrapper>, InnerMap> OuterMap;
for (int keepOuterAlive = 0; keepOuterAlive <= 1; keepOuterAlive++) {
for (int keepInnerAlive = 0; keepInnerAlive <=1; keepInnerAlive++) {
Persistent<OuterMap> outer = new OuterMap();
Persistent<IntWrapper> one = IntWrapper::create(1);
Persistent<IntWrapper> two = IntWrapper::create(2);
outer->add(one, InnerMap());
outer->begin()->value.add(two, IntWrapper::create(3));
EXPECT_EQ(1u, outer->get(one).size());
if (!keepOuterAlive)
one.clear();
if (!keepInnerAlive)
two.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
if (keepOuterAlive) {
const InnerMap& inner = outer->get(one);
if (keepInnerAlive) {
EXPECT_EQ(1u, inner.size());
IntWrapper* three = inner.get(two);
EXPECT_EQ(3, three->value());
} else {
EXPECT_EQ(0u, inner.size());
}
} else {
EXPECT_EQ(0u, outer->size());
}
outer->clear();
Persistent<IntWrapper> deep = IntWrapper::create(42);
Persistent<IntWrapper> home = IntWrapper::create(103);
Persistent<IntWrapper> composite = IntWrapper::create(91);
Persistent<HeapVector<Member<IntWrapper> > > keepAlive = new HeapVector<Member<IntWrapper> >();
for (int i = 0; i < 10000; i++) {
IntWrapper* value = IntWrapper::create(i);
keepAlive->append(value);
OuterMap::AddResult newEntry = outer->add(value, InnerMap());
newEntry.storedValue->value.add(deep, home);
newEntry.storedValue->value.add(composite, home);
}
composite.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(10000u, outer->size());
for (int i = 0; i < 10000; i++) {
IntWrapper* value = keepAlive->at(i);
EXPECT_EQ(1u, outer->get(value).size()); // Other one was deleted by weak handling.
if (i & 1)
keepAlive->at(i) = nullptr;
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(5000u, outer->size());
}
}
}
class EphemeronWrapper : public GarbageCollected<EphemeronWrapper> {
public:
void trace(Visitor* visitor)
{
visitor->trace(m_map);
}
typedef HeapHashMap<WeakMember<IntWrapper>, Member<EphemeronWrapper> > Map;
Map& map() { return m_map; }
private:
Map m_map;
};
TEST(HeapTest, EphemeronsPointToEphemerons)
{
Persistent<IntWrapper> key = IntWrapper::create(42);
Persistent<IntWrapper> key2 = IntWrapper::create(103);
Persistent<EphemeronWrapper> chain;
for (int i = 0; i < 100; i++) {
EphemeronWrapper* oldHead = chain;
chain = new EphemeronWrapper();
if (i == 50)
chain->map().add(key2, oldHead);
else
chain->map().add(key, oldHead);
chain->map().add(IntWrapper::create(103), new EphemeronWrapper());
}
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EphemeronWrapper* wrapper = chain;
for (int i = 0; i< 100; i++) {
EXPECT_EQ(1u, wrapper->map().size());
if (i == 49)
wrapper = wrapper->map().get(key2);
else
wrapper = wrapper->map().get(key);
}
EXPECT_EQ(nullptr, wrapper);
key2.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
wrapper = chain;
for (int i = 0; i < 50; i++) {
EXPECT_EQ(i == 49 ? 0u : 1u, wrapper->map().size());
wrapper = wrapper->map().get(key);
}
EXPECT_EQ(nullptr, wrapper);
key.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, chain->map().size());
}
TEST(HeapTest, Ephemeron)
{
typedef HeapHashMap<WeakMember<IntWrapper>, PairWithWeakHandling> WeakPairMap;
typedef HeapHashMap<PairWithWeakHandling, WeakMember<IntWrapper> > PairWeakMap;
typedef HeapHashSet<WeakMember<IntWrapper> > Set;
Persistent<WeakPairMap> weakPairMap = new WeakPairMap();
Persistent<WeakPairMap> weakPairMap2 = new WeakPairMap();
Persistent<WeakPairMap> weakPairMap3 = new WeakPairMap();
Persistent<WeakPairMap> weakPairMap4 = new WeakPairMap();
Persistent<PairWeakMap> pairWeakMap = new PairWeakMap();
Persistent<PairWeakMap> pairWeakMap2 = new PairWeakMap();
Persistent<Set> set = new Set();
Persistent<IntWrapper> wp1 = IntWrapper::create(1);
Persistent<IntWrapper> wp2 = IntWrapper::create(2);
Persistent<IntWrapper> pw1 = IntWrapper::create(3);
Persistent<IntWrapper> pw2 = IntWrapper::create(4);
weakPairMap->add(wp1, PairWithWeakHandling(wp1, wp1));
weakPairMap->add(wp2, PairWithWeakHandling(wp1, wp1));
weakPairMap2->add(wp1, PairWithWeakHandling(wp1, wp2));
weakPairMap2->add(wp2, PairWithWeakHandling(wp1, wp2));
// The map from wp1 to (wp2, wp1) would mark wp2 live, so we skip that.
weakPairMap3->add(wp2, PairWithWeakHandling(wp2, wp1));
weakPairMap4->add(wp1, PairWithWeakHandling(wp2, wp2));
weakPairMap4->add(wp2, PairWithWeakHandling(wp2, wp2));
pairWeakMap->add(PairWithWeakHandling(pw1, pw1), pw1);
pairWeakMap->add(PairWithWeakHandling(pw1, pw2), pw1);
// The map from (pw2, pw1) to pw1 would make pw2 live, so we skip that.
pairWeakMap->add(PairWithWeakHandling(pw2, pw2), pw1);
pairWeakMap2->add(PairWithWeakHandling(pw1, pw1), pw2);
pairWeakMap2->add(PairWithWeakHandling(pw1, pw2), pw2);
pairWeakMap2->add(PairWithWeakHandling(pw2, pw1), pw2);
pairWeakMap2->add(PairWithWeakHandling(pw2, pw2), pw2);
set->add(wp1);
set->add(wp2);
set->add(pw1);
set->add(pw2);
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u, weakPairMap->size());
EXPECT_EQ(2u, weakPairMap2->size());
EXPECT_EQ(1u, weakPairMap3->size());
EXPECT_EQ(2u, weakPairMap4->size());
EXPECT_EQ(3u, pairWeakMap->size());
EXPECT_EQ(4u, pairWeakMap2->size());
EXPECT_EQ(4u, set->size());
wp2.clear(); // Kills all entries in the weakPairMaps except the first.
pw2.clear(); // Kills all entries in the pairWeakMaps except the first.
for (int i = 0; i < 2; i++) {
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, weakPairMap->size());
EXPECT_EQ(0u, weakPairMap2->size());
EXPECT_EQ(0u, weakPairMap3->size());
EXPECT_EQ(0u, weakPairMap4->size());
EXPECT_EQ(1u, pairWeakMap->size());
EXPECT_EQ(0u, pairWeakMap2->size());
EXPECT_EQ(2u, set->size()); // wp1 and pw1.
}
wp1.clear();
pw1.clear();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, weakPairMap->size());
EXPECT_EQ(0u, pairWeakMap->size());
EXPECT_EQ(0u, set->size());
}
class Link1 : public GarbageCollected<Link1> {
public:
Link1(IntWrapper* link) : m_link(link) { }
void trace(Visitor* visitor)
{
visitor->trace(m_link);
}
IntWrapper* link() { return m_link; }
private:
Member<IntWrapper> m_link;
};
TEST(HeapTest, IndirectStrongToWeak)
{
typedef HeapHashMap<WeakMember<IntWrapper>, Member<Link1> > Map;
Persistent<Map> map = new Map();
Persistent<IntWrapper> deadObject = IntWrapper::create(100); // Named for "Drowning by Numbers" (1988).
Persistent<IntWrapper> lifeObject = IntWrapper::create(42);
map->add(deadObject, new Link1(deadObject));
map->add(lifeObject, new Link1(lifeObject));
EXPECT_EQ(2u, map->size());
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(2u, map->size());
EXPECT_EQ(deadObject, map->get(deadObject)->link());
EXPECT_EQ(lifeObject, map->get(lifeObject)->link());
deadObject.clear(); // Now it can live up to its name.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1u, map->size());
EXPECT_EQ(lifeObject, map->get(lifeObject)->link());
lifeObject.clear(); // Despite its name.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(0u, map->size());
}
static Mutex& mainThreadMutex()
{
AtomicallyInitializedStatic(Mutex&, mainMutex = *new Mutex);
return mainMutex;
}
static ThreadCondition& mainThreadCondition()
{
AtomicallyInitializedStatic(ThreadCondition&, mainCondition = *new ThreadCondition);
return mainCondition;
}
static void parkMainThread()
{
mainThreadCondition().wait(mainThreadMutex());
}
static void wakeMainThread()
{
MutexLocker locker(mainThreadMutex());
mainThreadCondition().signal();
}
static Mutex& workerThreadMutex()
{
AtomicallyInitializedStatic(Mutex&, workerMutex = *new Mutex);
return workerMutex;
}
static ThreadCondition& workerThreadCondition()
{
AtomicallyInitializedStatic(ThreadCondition&, workerCondition = *new ThreadCondition);
return workerCondition;
}
static void parkWorkerThread()
{
workerThreadCondition().wait(workerThreadMutex());
}
static void wakeWorkerThread()
{
MutexLocker locker(workerThreadMutex());
workerThreadCondition().signal();
}
class CrossThreadObject : public GarbageCollectedFinalized<CrossThreadObject> {
public:
static CrossThreadObject* create(IntWrapper* workerObjectPointer)
{
return new CrossThreadObject(workerObjectPointer);
}
virtual ~CrossThreadObject()
{
++s_destructorCalls;
}
static int s_destructorCalls;
void trace(Visitor* visitor) { visitor->trace(m_workerObject); }
private:
CrossThreadObject(IntWrapper* workerObjectPointer) : m_workerObject(workerObjectPointer) { }
private:
Member<IntWrapper> m_workerObject;
};
int CrossThreadObject::s_destructorCalls = 0;
class CrossThreadPointerTester {
public:
static void test()
{
CrossThreadObject::s_destructorCalls = 0;
IntWrapper::s_destructorCalls = 0;
MutexLocker locker(mainThreadMutex());
createThread(&workerThreadMain, 0, "Worker Thread");
parkMainThread();
uintptr_t stackPtrValue = 0;
{
// Create an object with a pointer to the other heap's IntWrapper.
Persistent<CrossThreadObject> cto = CrossThreadObject::create(const_cast<IntWrapper*>(s_workerObjectPointer));
s_workerObjectPointer = 0;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// Nothing should have been collected/destructed.
EXPECT_EQ(0, CrossThreadObject::s_destructorCalls);
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
// Put cto into a stack value. This is used to check that a conservative
// GC succeeds even though we are tracing the other thread heap after
// shutting it down.
stackPtrValue = reinterpret_cast<uintptr_t>(cto.get());
}
// At this point it is "programatically" okay to shut down the worker thread
// since the cto object should be dead. However out stackPtrValue will cause a
// trace of the object when doing a conservative GC.
// The worker thread's thread local GC's should just add the worker thread's
// pages to the heap after finalizing IntWrapper.
wakeWorkerThread();
// Wait for the worker to shutdown.
parkMainThread();
// After the worker thread has detached it should have finalized the
// IntWrapper object on its heaps. Since there has been no global GC
// the cto object should not have been finalized.
EXPECT_EQ(0, CrossThreadObject::s_destructorCalls);
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
// Now do a conservative GC. The stackPtrValue should keep cto alive
// and will also cause the orphaned page of the other thread to be
// traced. At this point cto should still not be finalized.
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
EXPECT_EQ(0, CrossThreadObject::s_destructorCalls);
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
// This release assert is here to ensure the stackValuePtr is not
// optimized away before doing the above conservative GC. If the
// EXPECT_EQ(0, CrossThreadObject::s_destructorCalls) call above
// starts failing it means we have to find a better way to ensure
// the stackPtrValue is not optimized away.
RELEASE_ASSERT(stackPtrValue);
// Do a GC with no pointers on the stack to see the cto being collected.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
EXPECT_EQ(1, CrossThreadObject::s_destructorCalls);
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
}
private:
static void workerThreadMain(void* data)
{
MutexLocker locker(workerThreadMutex());
ThreadState::attach();
{
// Create a worker object that is only kept alive by a cross thread
// pointer (from CrossThreadObject).
IntWrapper* workerObject = IntWrapper::create(42);
s_workerObjectPointer = workerObject;
}
// Wake up the main thread which is waiting for the worker to do its
// allocation and passing the pointer.
wakeMainThread();
// Wait for main thread to signal the worker to shutdown.
{
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
parkWorkerThread();
}
ThreadState::detach();
// Tell the main thread the worker has done its shutdown.
wakeMainThread();
}
static volatile IntWrapper* s_workerObjectPointer;
};
volatile IntWrapper* CrossThreadPointerTester::s_workerObjectPointer = 0;
TEST(HeapTest, CrossThreadPointerToOrphanedPage)
{
CrossThreadPointerTester::test();
}
class DeadBitTester {
public:
static void test()
{
IntWrapper::s_destructorCalls = 0;
MutexLocker locker(mainThreadMutex());
createThread(&workerThreadMain, 0, "Worker Thread");
// Wait for the worker thread to have done its initialization,
// IE. the worker allocates an object and then throw aways any
// pointers to it.
parkMainThread();
// Now do a GC. This will not find the worker threads object since it
// is not referred from any of the threads. Even a conservative
// GC will not find it.
// Also at this point the worker is waiting for the main thread
// to be parked and will not do any sweep of its heap.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// Since the worker thread is not sweeping the worker object should
// not have been finalized.
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
// Put the worker thread's object address on the stack and do a
// conservative GC. This should find the worker object, but since
// it was dead in the previous GC it should not be traced in this
// GC.
uintptr_t stackPtrValue = s_workerObjectPointer;
s_workerObjectPointer = 0;
ASSERT_UNUSED(stackPtrValue, stackPtrValue);
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
// Since the worker thread is not sweeping the worker object should
// not have been finalized.
EXPECT_EQ(0, IntWrapper::s_destructorCalls);
// Wake up the worker thread so it can continue with its sweeping.
// This should finalized the worker object which we test below.
// The worker thread will go back to sleep once sweeping to ensure
// we don't have thread local GCs until after validating the destructor
// was called.
wakeWorkerThread();
// Wait for the worker thread to sweep its heaps before checking.
parkMainThread();
EXPECT_EQ(1, IntWrapper::s_destructorCalls);
// Wake up the worker to allow it thread to continue with thread
// shutdown.
wakeWorkerThread();
}
private:
static void workerThreadMain(void* data)
{
MutexLocker locker(workerThreadMutex());
ThreadState::attach();
{
// Create a worker object that is not kept alive except the
// main thread will keep it as an integer value on its stack.
IntWrapper* workerObject = IntWrapper::create(42);
s_workerObjectPointer = reinterpret_cast<uintptr_t>(workerObject);
}
// Signal the main thread that the worker is done with its allocation.
wakeMainThread();
{
// Wait for the main thread to do two GCs without sweeping this thread
// heap. The worker waits within a safepoint, but there is no sweeping
// until leaving the safepoint scope.
ThreadState::SafePointScope scope(ThreadState::NoHeapPointersOnStack);
parkWorkerThread();
}
// Wake up the main thread when done sweeping.
wakeMainThread();
// Wait with detach until the main thread says so. This is not strictly
// necessary, but it means the worker thread will not do its thread local
// GCs just yet, making it easier to reason about that no new GC has occurred
// and the above sweep was the one finalizing the worker object.
parkWorkerThread();
ThreadState::detach();
}
static volatile uintptr_t s_workerObjectPointer;
};
volatile uintptr_t DeadBitTester::s_workerObjectPointer = 0;
TEST(HeapTest, ObjectDeadBit)
{
DeadBitTester::test();
}
static bool allocateAndReturnBool()
{
Heap::collectGarbage(ThreadState::HeapPointersOnStack);
return true;
}
class MixinWithGarbageCollectionInConstructor : public GarbageCollectedMixin {
public:
MixinWithGarbageCollectionInConstructor() : m_dummy(allocateAndReturnBool())
{
}
private:
bool m_dummy;
};
class ClassWithGarbageCollectingMixinConstructor
: public GarbageCollected<ClassWithGarbageCollectingMixinConstructor>
, public MixinWithGarbageCollectionInConstructor {
USING_GARBAGE_COLLECTED_MIXIN(ClassWithGarbageCollectingMixinConstructor);
public:
ClassWithGarbageCollectingMixinConstructor() : m_wrapper(IntWrapper::create(32))
{
}
virtual void trace(Visitor* visitor)
{
visitor->trace(m_wrapper);
}
void verify()
{
EXPECT_EQ(32, m_wrapper->value());
}
private:
Member<IntWrapper> m_wrapper;
};
// Regression test for out of bounds call through vtable.
// Passes if it doesn't crash.
TEST(HeapTest, GarbageCollectionDuringMixinConstruction)
{
ClassWithGarbageCollectingMixinConstructor* a =
new ClassWithGarbageCollectingMixinConstructor();
a->verify();
}
static RecursiveMutex& recursiveMutex()
{
AtomicallyInitializedStatic(RecursiveMutex&, recursiveMutex = *new RecursiveMutex);
return recursiveMutex;
}
class DestructorLockingObject : public GarbageCollectedFinalized<DestructorLockingObject> {
public:
static DestructorLockingObject* create()
{
return new DestructorLockingObject();
}
virtual ~DestructorLockingObject()
{
SafePointAwareMutexLocker lock(recursiveMutex());
++s_destructorCalls;
}
static int s_destructorCalls;
void trace(Visitor* visitor) { }
private:
DestructorLockingObject() { }
};
int DestructorLockingObject::s_destructorCalls = 0;
class RecursiveLockingTester {
public:
static void test()
{
DestructorLockingObject::s_destructorCalls = 0;
MutexLocker locker(mainThreadMutex());
createThread(&workerThreadMain, 0, "Worker Thread");
// Park the main thread until the worker thread has initialized.
parkMainThread();
{
SafePointAwareMutexLocker recursiveLocker(recursiveMutex());
// Let the worker try to acquire the above mutex. It won't get it
// until the main thread has done its GC.
wakeWorkerThread();
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
// The worker thread should not have swept yet since it is waiting
// to get the global mutex.
EXPECT_EQ(0, DestructorLockingObject::s_destructorCalls);
}
// At this point the main thread releases the global lock and the worker
// can acquire it and do its sweep of its heaps. Just wait for the worker
// to complete its sweep and check the result.
parkMainThread();
EXPECT_EQ(1, DestructorLockingObject::s_destructorCalls);
}
private:
static void workerThreadMain(void* data)
{
MutexLocker locker(workerThreadMutex());
ThreadState::attach();
DestructorLockingObject* dlo = DestructorLockingObject::create();
ASSERT_UNUSED(dlo, dlo);
// Wake up the main thread which is waiting for the worker to do its
// allocation.
wakeMainThread();
// Wait for the main thread to get the global lock to ensure it has
// it before the worker tries to acquire it. We want the worker to
// block in the SafePointAwareMutexLocker until the main thread
// has done a GC. The GC will not mark the "dlo" object since the worker
// is entering the safepoint with NoHeapPointersOnStack. When the worker
// subsequently gets the global lock and leaves the safepoint it will
// sweep its heap and finalize "dlo". The destructor of "dlo" will try
// to acquire the same global lock that the thread just got and deadlock
// unless the global lock is recursive.
parkWorkerThread();
SafePointAwareMutexLocker recursiveLocker(recursiveMutex(), ThreadState::NoHeapPointersOnStack);
// We won't get here unless the lock is recursive since the sweep done
// in the constructor of SafePointAwareMutexLocker after
// getting the lock will not complete given the "dlo" destructor is
// waiting to get the same lock.
// Tell the main thread the worker has done its sweep.
wakeMainThread();
ThreadState::detach();
}
static volatile IntWrapper* s_workerObjectPointer;
};
TEST(HeapTest, RecursiveMutex)
{
RecursiveLockingTester::test();
}
template<typename T>
class TraceIfNeededTester : public GarbageCollectedFinalized<TraceIfNeededTester<T> > {
public:
static TraceIfNeededTester<T>* create() { return new TraceIfNeededTester<T>(); }
static TraceIfNeededTester<T>* create(const T& obj) { return new TraceIfNeededTester<T>(obj); }
void trace(Visitor* visitor) { TraceIfNeeded<T>::trace(visitor, &m_obj); }
T& obj() { return m_obj; }
~TraceIfNeededTester() { }
private:
TraceIfNeededTester() { }
explicit TraceIfNeededTester(const T& obj) : m_obj(obj) { }
T m_obj;
};
class PartObject {
DISALLOW_ALLOCATION();
public:
PartObject() : m_obj(SimpleObject::create()) { }
void trace(Visitor* visitor) { visitor->trace(m_obj); }
private:
Member<SimpleObject> m_obj;
};
TEST(HeapTest, TraceIfNeeded)
{
CountingVisitor visitor;
{
TraceIfNeededTester<RefPtr<OffHeapInt> >* m_offHeap = TraceIfNeededTester<RefPtr<OffHeapInt> >::create(OffHeapInt::create(42));
visitor.reset();
m_offHeap->trace(&visitor);
EXPECT_EQ(0u, visitor.count());
}
{
TraceIfNeededTester<PartObject>* m_part = TraceIfNeededTester<PartObject>::create();
visitor.reset();
m_part->trace(&visitor);
EXPECT_EQ(1u, visitor.count());
}
{
TraceIfNeededTester<Member<SimpleObject> >* m_obj = TraceIfNeededTester<Member<SimpleObject> >::create(Member<SimpleObject>(SimpleObject::create()));
visitor.reset();
m_obj->trace(&visitor);
EXPECT_EQ(1u, visitor.count());
}
{
TraceIfNeededTester<HeapVector<Member<SimpleObject> > >* m_vec = TraceIfNeededTester<HeapVector<Member<SimpleObject> > >::create();
m_vec->obj().append(SimpleObject::create());
visitor.reset();
m_vec->trace(&visitor);
EXPECT_EQ(2u, visitor.count());
}
}
class PartObjectWithVirtualMethod {
public:
virtual void trace(Visitor*) { }
};
class ObjectWithVirtualPartObject : public GarbageCollected<ObjectWithVirtualPartObject> {
public:
ObjectWithVirtualPartObject() : m_dummy(allocateAndReturnBool()) { }
void trace(Visitor* visitor) { visitor->trace(m_part); }
private:
bool m_dummy;
PartObjectWithVirtualMethod m_part;
};
TEST(HeapTest, PartObjectWithVirtualMethod)
{
ObjectWithVirtualPartObject* object = new ObjectWithVirtualPartObject();
EXPECT_TRUE(object);
}
class AllocInSuperConstructorArgumentSuper : public GarbageCollectedFinalized<AllocInSuperConstructorArgumentSuper> {
public:
AllocInSuperConstructorArgumentSuper(bool value) : m_value(value) { }
virtual void trace(Visitor*) { }
bool value() { return m_value; }
private:
bool m_value;
};
class AllocInSuperConstructorArgument : public AllocInSuperConstructorArgumentSuper {
public:
AllocInSuperConstructorArgument()
: AllocInSuperConstructorArgumentSuper(allocateAndReturnBool())
{
}
};
// Regression test for crbug.com/404511. Tests conservative marking of
// an object with an uninitialized vtable.
TEST(HeapTest, AllocationInSuperConstructorArgument)
{
AllocInSuperConstructorArgument* object = new AllocInSuperConstructorArgument();
EXPECT_TRUE(object);
Heap::collectAllGarbage();
}
} // namespace blink