#include <gtest/gtest.h>
-namespace WebCore {
+namespace blink {
class IntWrapper : public GarbageCollectedFinalized<IntWrapper> {
public:
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)
+ // 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->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);
+ if (!visitor->isAlive(second))
+ return true;
+ visitor->trace(first);
+ return false;
}
};
namespace WTF {
template<typename T> struct DefaultHash;
-template<> struct DefaultHash<WebCore::ThreadMarker> {
- typedef WebCore::ThreadMarkerHash Hash;
+template<> struct DefaultHash<blink::ThreadMarker> {
+ typedef blink::ThreadMarkerHash Hash;
};
// ThreadMarkerHash is the default hash for ThreadMarker
-template<> struct HashTraits<WebCore::ThreadMarker> : GenericHashTraits<WebCore::ThreadMarker> {
+template<> struct HashTraits<blink::ThreadMarker> : GenericHashTraits<blink::ThreadMarker> {
static const bool emptyValueIsZero = true;
- static void constructDeletedValue(WebCore::ThreadMarker& slot) { new (NotNull, &slot) WebCore::ThreadMarker(HashTableDeletedValue); }
- static bool isDeletedValue(const WebCore::ThreadMarker& slot) { return slot.isHashTableDeletedValue(); }
+ 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<WebCore::PairWithWeakHandling> {
- typedef PairHash<WebCore::Member<WebCore::IntWrapper>, WebCore::WeakMember<WebCore::IntWrapper> > Hash;
+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 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> {
+// 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 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(); }
+ 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 WebCore {
+namespace blink {
class TestGCScope {
public:
m_count++;
}
- virtual void markConservatively(HeapObjectHeader* header) OVERRIDE
- {
- ASSERT_NOT_REACHED();
- }
-
- virtual void markConservatively(FinalizedHeapObjectHeader* header) OVERRIDE
- {
- ASSERT_NOT_REACHED();
- }
-
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; }
int SimpleFinalizedObject::s_destructorCalls = 0;
-class TestTypedHeapClass : public GarbageCollected<TestTypedHeapClass> {
+class Node : public GarbageCollected<Node> {
public:
- static TestTypedHeapClass* create()
+ static Node* create()
{
- return new TestTypedHeapClass();
+ return new Node();
}
void trace(Visitor*) { }
private:
- TestTypedHeapClass() { }
+ Node() { }
};
class Bar : public GarbageCollectedFinalized<Bar> {
bool m_isLast;
};
-} // WebCore namespace
+} // namespace blink
-WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(WebCore::VectorObject);
-WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(WebCore::VectorObjectInheritedTrace);
-WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(WebCore::VectorObjectNoTrace);
+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 WebCore {
+namespace blink {
class OneKiloByteObject : public GarbageCollectedFinalized<OneKiloByteObject> {
public:
{
// We use TraceCounter for allocating an object on the general heap.
Persistent<TraceCounter> generalHeapObject = TraceCounter::create();
- Persistent<TestTypedHeapClass> typedHeapObject = TestTypedHeapClass::create();
- EXPECT_NE(pageHeaderAddress(reinterpret_cast<Address>(generalHeapObject.get())),
- pageHeaderAddress(reinterpret_cast<Address>(typedHeapObject.get())));
+ Persistent<Node> typedHeapObject = Node::create();
+ EXPECT_NE(pageHeaderFromObject(generalHeapObject.get()),
+ pageHeaderFromObject(typedHeapObject.get()));
}
TEST(HeapTest, NoAllocation)
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_TRACING)
+#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));
static OffHeapContainer* create() { return new OffHeapContainer(); }
static const int iterations = 300;
- static const int deadWrappers = 2700;
+ static const int deadWrappers = 1200;
OffHeapContainer()
{
m_vector1.append(ShouldBeTraced(IntWrapper::create(i)));
m_deque2.append(IntWrapper::create(i));
m_vector2.append(IntWrapper::create(i));
- m_hashSet.add(IntWrapper::create(i));
- m_hashMap.add(i + 103, IntWrapper::create(i));
- m_listHashSet.add(IntWrapper::create(i));
- m_linkedHashSet.add(IntWrapper::create(i));
- m_ownedVector.append(adoptPtr(new ShouldBeTraced(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());
- HashSet<Member<IntWrapper> >::iterator setIterator(m_hashSet.begin());
- HashMap<int, Member<IntWrapper> >::iterator mapIterator(m_hashMap.begin());
- ListHashSet<Member<IntWrapper> >::iterator listSetIterator(m_listHashSet.begin());
- LinkedHashSet<Member<IntWrapper> >::iterator linkedSetIterator(m_linkedHashSet.begin());
- Vector<OwnPtr<ShouldBeTraced> >::iterator ownedVectorIterator(m_ownedVector.begin());
for (int i = 0; i < iterations; i++) {
EXPECT_EQ(i, m_vector1[i].m_wrapper->value());
EXPECT_EQ(i, v1Iterator->m_wrapper->value());
EXPECT_EQ(i, d2Iterator->get()->value());
EXPECT_EQ(i, v2Iterator->get()->value());
- EXPECT_EQ(i, linkedSetIterator->get()->value());
- EXPECT_EQ(i, listSetIterator->get()->value());
- EXPECT_EQ(i, ownedVectorIterator->get()->m_wrapper->value());
- int value = setIterator->get()->value();
- EXPECT_LE(0, value);
- EXPECT_GT(iterations, value);
- value = mapIterator->value.get()->value();
- EXPECT_LE(0, value);
- EXPECT_GT(iterations, value);
++d1Iterator;
++v1Iterator;
++d2Iterator;
++v2Iterator;
- ++setIterator;
- ++mapIterator;
- ++listSetIterator;
- ++linkedSetIterator;
- ++ownedVectorIterator;
}
EXPECT_EQ(d1Iterator, m_deque1.end());
EXPECT_EQ(v1Iterator, m_vector1.end());
EXPECT_EQ(d2Iterator, m_deque2.end());
EXPECT_EQ(v2Iterator, m_vector2.end());
- EXPECT_EQ(setIterator, m_hashSet.end());
- EXPECT_EQ(mapIterator, m_hashMap.end());
- EXPECT_EQ(listSetIterator, m_listHashSet.end());
- EXPECT_EQ(linkedSetIterator, m_linkedHashSet.end());
- EXPECT_EQ(ownedVectorIterator, m_ownedVector.end());
}
void trace(Visitor* visitor)
visitor->trace(m_vector1);
visitor->trace(m_deque2);
visitor->trace(m_vector2);
- visitor->trace(m_hashSet);
- visitor->trace(m_hashMap);
- visitor->trace(m_listHashSet);
- visitor->trace(m_linkedHashSet);
- visitor->trace(m_listHashSet);
- visitor->trace(m_ownedVector);
}
Deque<ShouldBeTraced> m_deque1;
Vector<ShouldBeTraced> m_vector1;
Deque<Member<IntWrapper> > m_deque2;
Vector<Member<IntWrapper> > m_vector2;
- HashSet<Member<IntWrapper> > m_hashSet;
- HashMap<int, Member<IntWrapper> > m_hashMap;
- ListHashSet<Member<IntWrapper> > m_listHashSet;
- LinkedHashSet<Member<IntWrapper> > m_linkedHashSet;
- Vector<OwnPtr<ShouldBeTraced> > m_ownedVector;
};
const int OffHeapContainer::iterations;
for (unsigned i = 0; i < 128 + added; i++)
keepNumbersAlive[i] = nullptr;
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
- EXPECT_EQ(added, weakStrong->size());
- EXPECT_EQ(added, strongWeak->size());
- EXPECT_EQ(added, weakWeak->size());
- EXPECT_EQ(added, weakSet->size());
- EXPECT_EQ(added, weakOrderedSet->size());
+ 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());
}
}
}
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);
PListSet pListSet;
PLinkedSet pLinkedSet;
PMap pMap;
+ WeakPMap wpMap;
IntWrapper* one(IntWrapper::create(1));
IntWrapper* two(IntWrapper::create(2));
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);
pListSet.add(eight);
pLinkedSet.add(nine);
pMap.add(five, six);
+ wpMap.add(ten, eleven);
// Collect |vec| and |one|.
vec = 0;
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(9, IntWrapper::s_destructorCalls);
+ EXPECT_EQ(11, IntWrapper::s_destructorCalls);
}
TEST(HeapTest, CollectionNesting)
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)
+ for (typename Set::iterator it = set.begin(); it != set.end(); ++it) {
EXPECT_EQ(42, **it);
+ delete *it;
+ }
}
TEST(HeapTest, RawPtrInHash)
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));
typedef HeapHashSet<WeakMember<IntWrapper> > WeakSet;
// These special traits will remove a set from a map when the set is empty.
-struct EmptyClearingHastSetTraits : HashTraits<WeakSet> {
+struct EmptyClearingHashSetTraits : HashTraits<WeakSet> {
static const WTF::WeakHandlingFlag weakHandlingFlag = WTF::WeakHandlingInCollections;
- static bool shouldRemoveFromCollection(Visitor* visitor, WeakSet& set)
- {
- return set.isEmpty(); // Remove this set from any maps it is in.
- }
- static void traceInCollection(Visitor* visitor, WeakSet& set, WebCore::ShouldWeakPointersBeMarkedStrongly strongify)
- {
- // We just trace normally, which will invoke the normal weak handling
- // of the set, removing individual items.
- set.trace(visitor);
+ 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;
}
};
Persistent<IntWrapper> livingInt(IntWrapper::create(42));
typedef RefPtr<OffHeapInt> Key;
- typedef HeapHashMap<Key, WeakSet, WTF::DefaultHash<Key>::Hash, HashTraits<Key>, EmptyClearingHastSetTraits> Map;
+ typedef HeapHashMap<Key, WeakSet, WTF::DefaultHash<Key>::Hash, HashTraits<Key>, EmptyClearingHashSetTraits> Map;
Persistent<Map> map(new Map());
map->add(OffHeapInt::create(1), WeakSet());
{
livingInt.clear(); // The weak set can no longer keep the '42' alive now.
Heap::collectGarbage(ThreadState::NoHeapPointersOnStack);
- if (map->size() == 1u) {
- // If the weak processing for the set ran after the weak processing for
- // the map, then the set was not empty, and so the entry in the map was
- // not removed yet.
- WeakSet& set = map->begin()->value;
- EXPECT_EQ(0u, set.size());
+ 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());
}
-} // WebCore namespace
+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