| /* |
| * 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 |