Index: Source/platform/heap/HeapTest.cpp |
diff --git a/Source/platform/heap/HeapTest.cpp b/Source/platform/heap/HeapTest.cpp |
index 3a2fc82352eb5577309670aef54433018fc3fc6a..ac81c25ee3f081c16336026794fd692dcf50d68d 100644 |
--- a/Source/platform/heap/HeapTest.cpp |
+++ b/Source/platform/heap/HeapTest.cpp |
@@ -44,6 +44,37 @@ |
namespace WebCore { |
+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) { } |
@@ -75,6 +106,49 @@ struct ThreadMarkerHash { |
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. |
+ void traceInCollection(Visitor* visitor, ShouldWeakPointersBeMarkedStrongly strongify) |
+ { |
+ visitor->trace(first); |
+ visitor->traceInCollection(second, strongify); |
+ } |
+ // The traceInCollection may not trace the weak members, so it is vital |
+ // that shouldRemoveFromCollection checks them so the entry can be removed |
+ // from the collection (otherwise it would contain a dangling pointer). |
+ bool shouldRemoveFromCollection(Visitor* visitor) |
+ { |
+ return !visitor->isAlive(second); |
+ } |
+}; |
+ |
} |
namespace WTF { |
@@ -91,6 +165,29 @@ template<> struct HashTraits<WebCore::ThreadMarker> : GenericHashTraits<WebCore: |
static bool isDeletedValue(const WebCore::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<WebCore::PairWithWeakHandling> { |
+ typedef PairHash<WebCore::Member<WebCore::IntWrapper>, WebCore::WeakMember<WebCore::IntWrapper> > Hash; |
+}; |
+ |
+// Custom traits for the pair. These are weakness handling traits, which means |
+// PairWithWeakHandling must implement the traceInCollection and |
+// shouldRemoveFromCollection methods. 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<WebCore::PairWithWeakHandling> : WebCore::WeakHandlingHashTraits<WebCore::PairWithWeakHandling> { |
+ static const bool needsDestruction = false; |
+ static const bool hasIsEmptyValueFunction = true; |
+ static bool isEmptyValue(const WebCore::PairWithWeakHandling& value) { return !value.first; } |
+ static void constructDeletedValue(WebCore::PairWithWeakHandling& slot) { new (NotNull, &slot) WebCore::PairWithWeakHandling(HashTableDeletedValue); } |
+ static bool isDeletedValue(const WebCore::PairWithWeakHandling& value) { return value.isHashTableDeletedValue(); } |
+}; |
+ |
} |
namespace WebCore { |
@@ -293,35 +390,6 @@ static void clearOutOldGarbage(HeapStats* heapStats) |
} |
} |
-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; |
-}; |
- |
class OffHeapInt : public RefCounted<OffHeapInt> { |
public: |
static RefPtr<OffHeapInt> create(int x) |
@@ -350,8 +418,6 @@ private: |
int m_x; |
}; |
-USED_FROM_MULTIPLE_THREADS(IntWrapper); |
- |
int IntWrapper::s_destructorCalls = 0; |
int OffHeapInt::s_destructorCalls = 0; |
@@ -3316,12 +3382,19 @@ TEST(HeapTest, CheckAndMarkPointer) |
EXPECT_TRUE(scope.allThreadsParked()); |
Heap::makeConsistentForGC(); |
for (size_t i = 0; i < objectAddresses.size(); i++) { |
- EXPECT_FALSE(Heap::checkAndMarkPointer(&visitor, objectAddresses[i])); |
- EXPECT_FALSE(Heap::checkAndMarkPointer(&visitor, endAddresses[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()); |
- EXPECT_FALSE(Heap::checkAndMarkPointer(&visitor, largeObjectAddress)); |
- EXPECT_FALSE(Heap::checkAndMarkPointer(&visitor, largeObjectEndAddress)); |
+ 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 |
@@ -3978,6 +4051,204 @@ TEST(HeapTest, NeedsAdjustAndMark) |
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); |
+} |
+ |
TEST(HeapTest, Bind) |
{ |
Closure closure = bind(&Bar::trace, Bar::create(), static_cast<Visitor*>(0)); |