blob: e2b4b38a7e21c363df5d4ac68b605d07e99d1968 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <algorithm>
#include <memory>
#include <utility>
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_thread.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/heap/address_cache.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_linked_stack.h"
#include "third_party/blink/renderer/platform/heap/heap_stats_collector.h"
#include "third_party/blink/renderer/platform/heap/heap_terminated_array_builder.h"
#include "third_party/blink/renderer/platform/heap/heap_test_utilities.h"
#include "third_party/blink/renderer/platform/heap/marking_visitor.h"
#include "third_party/blink/renderer/platform/heap/safe_point.h"
#include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
#include "third_party/blink/renderer/platform/heap/stack_frame_depth.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/web_task_runner.h"
#include "third_party/blink/renderer/platform/wtf/hash_traits.h"
#include "third_party/blink/renderer/platform/wtf/linked_hash_set.h"
namespace blink {
namespace {
class IntWrapper : public GarbageCollectedFinalized<IntWrapper> {
public:
static IntWrapper* Create(int x) { return new IntWrapper(x); }
virtual ~IntWrapper() { AtomicIncrement(&destructor_calls_); }
static int destructor_calls_;
void Trace(blink::Visitor* visitor) {}
int Value() const { return x_; }
bool operator==(const IntWrapper& other) const {
return other.Value() == Value();
}
unsigned GetHash() { return IntHash<int>::GetHash(x_); }
IntWrapper(int x) : x_(x) {}
private:
IntWrapper() = delete;
int x_;
};
struct IntWrapperHash {
static unsigned GetHash(const IntWrapper& key) {
return WTF::HashInt(static_cast<uint32_t>(key.Value()));
}
static bool Equal(const IntWrapper& a, const IntWrapper& b) { return a == b; }
};
static_assert(WTF::IsTraceable<IntWrapper>::value,
"IsTraceable<> template failed to recognize trace method.");
static_assert(WTF::IsTraceable<HeapVector<IntWrapper>>::value,
"HeapVector<IntWrapper> must be traceable.");
static_assert(WTF::IsTraceable<HeapDeque<IntWrapper>>::value,
"HeapDeque<IntWrapper> must be traceable.");
static_assert(WTF::IsTraceable<HeapHashSet<IntWrapper, IntWrapperHash>>::value,
"HeapHashSet<IntWrapper> must be traceable.");
static_assert(WTF::IsTraceable<HeapHashMap<int, IntWrapper>>::value,
"HeapHashMap<int, IntWrapper> must be traceable.");
class KeyWithCopyingMoveConstructor final {
public:
struct Hash final {
STATIC_ONLY(Hash);
public:
static unsigned GetHash(const KeyWithCopyingMoveConstructor& key) {
return key.hash_;
}
static bool Equal(const KeyWithCopyingMoveConstructor& x,
const KeyWithCopyingMoveConstructor& y) {
return x.hash_ == y.hash_;
}
static constexpr bool safe_to_compare_to_empty_or_deleted = true;
};
KeyWithCopyingMoveConstructor() = default;
KeyWithCopyingMoveConstructor(WTF::HashTableDeletedValueType) : hash_(-1) {}
~KeyWithCopyingMoveConstructor() = default;
KeyWithCopyingMoveConstructor(unsigned hash, const String& string)
: hash_(hash), string_(string) {
DCHECK_NE(hash_, 0);
DCHECK_NE(hash_, -1);
}
KeyWithCopyingMoveConstructor(const KeyWithCopyingMoveConstructor&) = default;
// The move constructor delegates to the copy constructor intentionally.
KeyWithCopyingMoveConstructor(KeyWithCopyingMoveConstructor&& x)
: KeyWithCopyingMoveConstructor(x) {}
KeyWithCopyingMoveConstructor& operator=(
const KeyWithCopyingMoveConstructor&) = default;
bool operator==(const KeyWithCopyingMoveConstructor& x) const {
return hash_ == x.hash_;
}
bool IsHashTableDeletedValue() const { return hash_ == -1; }
private:
int hash_ = 0;
String string_;
};
struct SameSizeAsPersistent {
void* pointer_[4];
};
static_assert(sizeof(Persistent<IntWrapper>) <= sizeof(SameSizeAsPersistent),
"Persistent handle should stay small");
class ThreadMarker {
public:
ThreadMarker()
: creating_thread_(reinterpret_cast<ThreadState*>(0)), num_(0) {}
ThreadMarker(unsigned i)
: creating_thread_(ThreadState::Current()), num_(i) {}
ThreadMarker(WTF::HashTableDeletedValueType deleted)
: creating_thread_(reinterpret_cast<ThreadState*>(-1)), num_(0) {}
~ThreadMarker() {
EXPECT_TRUE((creating_thread_ == ThreadState::Current()) ||
(creating_thread_ == reinterpret_cast<ThreadState*>(0)) ||
(creating_thread_ == reinterpret_cast<ThreadState*>(-1)));
}
bool IsHashTableDeletedValue() const {
return creating_thread_ == reinterpret_cast<ThreadState*>(-1);
}
bool operator==(const ThreadMarker& other) const {
return other.creating_thread_ == creating_thread_ && other.num_ == num_;
}
ThreadState* creating_thread_;
unsigned num_;
};
struct ThreadMarkerHash {
static unsigned GetHash(const ThreadMarker& key) {
return static_cast<unsigned>(
reinterpret_cast<uintptr_t>(key.creating_thread_) + key.num_);
}
static bool Equal(const ThreadMarker& a, const ThreadMarker& b) {
return a == b;
}
static const bool safe_to_compare_to_empty_or_deleted = false;
};
typedef std::pair<Member<IntWrapper>, WeakMember<IntWrapper>> StrongWeakPair;
struct PairWithWeakHandling : public StrongWeakPair {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
// Regular constructor.
PairWithWeakHandling(IntWrapper* one, IntWrapper* two)
: StrongWeakPair(one, two) {
DCHECK(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(WTF::kHashTableDeletedValue, 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.IsHashTableDeletedValue();
}
bool IsAlive() { return ThreadHeap::IsHeapObjectAlive(second); }
// 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.
template <typename VisitorDispatcher>
bool TraceInCollection(VisitorDispatcher visitor,
WTF::WeakHandlingFlag weakness) {
HashTraits<WeakMember<IntWrapper>>::TraceInCollection(visitor, second,
weakness);
if (!ThreadHeap::IsHeapObjectAlive(second))
return true;
visitor->Trace(first);
return false;
}
// Incremental marking requires that these objects have a regular tracing
// method that is used for eagerly tracing through them in case they are
// in-place constructed in a container. In this case, we only care about
// strong fields.
void Trace(blink::Visitor* visitor) { visitor->Trace(first); }
};
template <typename T>
struct WeakHandlingHashTraits : WTF::SimpleClassHashTraits<T> {
// We want to treat the object as a weak object in the sense that it can
// disappear from hash sets and hash maps.
static const WTF::WeakHandlingFlag kWeakHandlingFlag = WTF::kWeakHandling;
// Normally whether or not an object needs tracing is inferred
// automatically from the presence of the trace method, but we don't
// necessarily have a trace method, and we may not need one because T
// can perhaps only be allocated inside collections, never as independent
// objects. Explicitly mark this as needing tracing and it will be traced
// in collections using the traceInCollection method, which it must have.
template <typename U = void>
struct IsTraceableInCollection {
static const bool value = true;
};
// The traceInCollection method traces differently depending on whether we
// are strongifying the trace operation. We strongify the trace operation
// when there are active iterators on the object. In this case all
// WeakMembers are marked like strong members so that elements do not
// suddenly disappear during iteration. Returns true if weak pointers to
// dead objects were found: In this case any strong pointers were not yet
// traced and the entry should be removed from the collection.
template <typename VisitorDispatcher>
static bool TraceInCollection(VisitorDispatcher visitor,
T& t,
WTF::WeakHandlingFlag weakness) {
return t.TraceInCollection(visitor, weakness);
}
static bool IsAlive(T& t) { return t.IsAlive(); }
};
} // namespace
} // namespace blink
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 kEmptyValueIsZero = true;
static void ConstructDeletedValue(blink::ThreadMarker& slot, bool) {
new (NotNull, &slot) blink::ThreadMarker(kHashTableDeletedValue);
}
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 kHasIsEmptyValueFunction = true;
static bool IsEmptyValue(const blink::PairWithWeakHandling& value) {
return !value.first;
}
static void ConstructDeletedValue(blink::PairWithWeakHandling& slot, bool) {
new (NotNull, &slot) blink::PairWithWeakHandling(kHashTableDeletedValue);
}
static bool IsDeletedValue(const blink::PairWithWeakHandling& value) {
return value.IsHashTableDeletedValue();
}
};
template <>
struct IsTraceable<blink::PairWithWeakHandling> {
static const bool value = IsTraceable<blink::StrongWeakPair>::value;
};
template <>
struct DefaultHash<blink::KeyWithCopyingMoveConstructor> {
using Hash = blink::KeyWithCopyingMoveConstructor::Hash;
};
template <>
struct HashTraits<blink::KeyWithCopyingMoveConstructor>
: public SimpleClassHashTraits<blink::KeyWithCopyingMoveConstructor> {};
} // namespace WTF
namespace blink {
class TestGCCollectGarbageScope {
public:
explicit TestGCCollectGarbageScope(BlinkGC::StackState state) {
DCHECK(ThreadState::Current()->CheckThread());
}
~TestGCCollectGarbageScope() { ThreadState::Current()->CompleteSweep(); }
};
class TestGCMarkingScope : public TestGCCollectGarbageScope {
public:
explicit TestGCMarkingScope(BlinkGC::StackState state)
: TestGCCollectGarbageScope(state),
atomic_pause_scope_(ThreadState::Current()) {
ThreadState::Current()->Heap().stats_collector()->NotifyMarkingStarted(
BlinkGC::GCReason::kTesting);
ThreadState::Current()->AtomicPausePrologue(state, BlinkGC::kAtomicMarking,
BlinkGC::GCReason::kPreciseGC);
}
~TestGCMarkingScope() {
ThreadState::Current()->MarkPhaseEpilogue(BlinkGC::kAtomicMarking);
ThreadState::Current()->AtomicPauseEpilogue(BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping);
}
private:
ThreadState::AtomicPauseScope atomic_pause_scope_;
};
class TestGCScope : public TestGCMarkingScope {
public:
explicit TestGCScope(BlinkGC::StackState state)
: TestGCMarkingScope(state), safe_point_scope_(state) {}
~TestGCScope() {}
private:
SafePointScope safe_point_scope_;
};
class SimpleObject : public GarbageCollected<SimpleObject> {
public:
static SimpleObject* Create() { return new SimpleObject(); }
void Trace(blink::Visitor* 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() = default;
char payload[64];
};
class HeapTestSuperClass
: public GarbageCollectedFinalized<HeapTestSuperClass> {
public:
static HeapTestSuperClass* Create() { return new HeapTestSuperClass(); }
virtual ~HeapTestSuperClass() { ++destructor_calls_; }
static int destructor_calls_;
void Trace(blink::Visitor* visitor) {}
protected:
HeapTestSuperClass() = default;
};
int HeapTestSuperClass::destructor_calls_ = 0;
class HeapTestOtherSuperClass {
public:
int payload;
};
static const size_t kClassMagic = 0xABCDDBCA;
class HeapTestSubClass : public HeapTestOtherSuperClass,
public HeapTestSuperClass {
public:
static HeapTestSubClass* Create() { return new HeapTestSubClass(); }
~HeapTestSubClass() override {
EXPECT_EQ(kClassMagic, magic_);
++destructor_calls_;
}
static int destructor_calls_;
private:
HeapTestSubClass() : magic_(kClassMagic) {}
const size_t magic_;
};
int HeapTestSubClass::destructor_calls_ = 0;
class HeapAllocatedArray : public GarbageCollected<HeapAllocatedArray> {
public:
HeapAllocatedArray() {
for (int i = 0; i < kArraySize; ++i) {
array_[i] = i % 128;
}
}
int8_t at(size_t i) { return array_[i]; }
void Trace(blink::Visitor* visitor) {}
private:
static const int kArraySize = 1000;
int8_t array_[kArraySize];
};
class OffHeapInt : public RefCounted<OffHeapInt> {
public:
static scoped_refptr<OffHeapInt> Create(int x) {
return base::AdoptRef(new OffHeapInt(x));
}
virtual ~OffHeapInt() { ++destructor_calls_; }
static int destructor_calls_;
int Value() const { return x_; }
bool operator==(const OffHeapInt& other) const {
return other.Value() == Value();
}
unsigned GetHash() { return IntHash<int>::GetHash(x_); }
void VoidFunction() {}
protected:
OffHeapInt(int x) : x_(x) {}
private:
OffHeapInt() = delete;
int x_;
};
int IntWrapper::destructor_calls_ = 0;
int OffHeapInt::destructor_calls_ = 0;
class ThreadedTesterBase {
protected:
static void Test(ThreadedTesterBase* tester) {
Vector<std::unique_ptr<WebThread>, kNumberOfThreads> threads;
for (int i = 0; i < kNumberOfThreads; i++) {
threads.push_back(Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("blink gc testing thread")));
PostCrossThreadTask(
*threads.back()->GetTaskRunner(), FROM_HERE,
CrossThreadBind(ThreadFunc, CrossThreadUnretained(tester)));
}
while (AcquireLoad(&tester->threads_to_finish_)) {
test::YieldCurrentThread();
}
delete tester;
}
virtual void RunThread() = 0;
protected:
static const int kNumberOfThreads = 10;
static const int kGcPerThread = 5;
static const int kNumberOfAllocations = 50;
ThreadedTesterBase() : gc_count_(0), threads_to_finish_(kNumberOfThreads) {}
virtual ~ThreadedTesterBase() = default;
inline bool Done() const {
return AcquireLoad(&gc_count_) >= kNumberOfThreads * kGcPerThread;
}
volatile int gc_count_;
volatile int threads_to_finish_;
private:
static void ThreadFunc(void* data) {
reinterpret_cast<ThreadedTesterBase*>(data)->RunThread();
}
};
// Needed to give this variable a definition (the initializer above is only a
// declaration), so that subclasses can use it.
const int ThreadedTesterBase::kNumberOfThreads;
class ThreadedHeapTester : public ThreadedTesterBase {
public:
static void Test() { ThreadedTesterBase::Test(new ThreadedHeapTester); }
~ThreadedHeapTester() override {
// Verify that the threads cleared their CTPs when
// terminating, preventing access to a finalized heap.
for (auto& global_int_wrapper : cross_persistents_) {
DCHECK(global_int_wrapper.get());
EXPECT_FALSE(global_int_wrapper.get()->Get());
}
}
protected:
using GlobalIntWrapperPersistent = CrossThreadPersistent<IntWrapper>;
Mutex mutex_;
Vector<std::unique_ptr<GlobalIntWrapperPersistent>> cross_persistents_;
std::unique_ptr<GlobalIntWrapperPersistent> CreateGlobalPersistent(
int value) {
return std::make_unique<GlobalIntWrapperPersistent>(
IntWrapper::Create(value));
}
void AddGlobalPersistent() {
MutexLocker lock(mutex_);
cross_persistents_.push_back(CreateGlobalPersistent(0x2a2a2a2a));
}
void RunThread() override {
ThreadState::AttachCurrentThread();
// Add a cross-thread persistent from this thread; the test object
// verifies that it will have been cleared out after the threads
// have all detached, running their termination GCs while doing so.
AddGlobalPersistent();
int gc_count = 0;
while (!Done()) {
{
Persistent<IntWrapper> wrapper;
std::unique_ptr<GlobalIntWrapperPersistent> global_persistent =
CreateGlobalPersistent(0x0ed0cabb);
for (int i = 0; i < kNumberOfAllocations; i++) {
wrapper = IntWrapper::Create(0x0bbac0de);
if (!(i % 10)) {
global_persistent = CreateGlobalPersistent(0x0ed0cabb);
}
test::YieldCurrentThread();
}
if (gc_count < kGcPerThread) {
PreciselyCollectGarbage();
gc_count++;
AtomicIncrement(&gc_count_);
}
// Taking snapshot shouldn't have any bad side effect.
// TODO(haraken): This snapshot GC causes crashes, so disable
// it at the moment. Fix the crash and enable it.
// ThreadHeap::collectGarbage(BlinkGC::NoHeapPointersOnStack,
// BlinkGC::TakeSnapshot, BlinkGC::ForcedGC);
PreciselyCollectGarbage();
EXPECT_EQ(wrapper->Value(), 0x0bbac0de);
EXPECT_EQ((*global_persistent)->Value(), 0x0ed0cabb);
}
test::YieldCurrentThread();
}
ThreadState::DetachCurrentThread();
AtomicDecrement(&threads_to_finish_);
}
};
class ThreadedWeaknessTester : public ThreadedTesterBase {
public:
static void Test() { ThreadedTesterBase::Test(new ThreadedWeaknessTester); }
private:
void RunThread() override {
ThreadState::AttachCurrentThread();
int gc_count = 0;
while (!Done()) {
{
Persistent<HeapHashMap<ThreadMarker, WeakMember<IntWrapper>>> weak_map =
new HeapHashMap<ThreadMarker, WeakMember<IntWrapper>>;
for (int i = 0; i < kNumberOfAllocations; i++) {
weak_map->insert(static_cast<unsigned>(i), IntWrapper::Create(0));
test::YieldCurrentThread();
}
if (gc_count < kGcPerThread) {
PreciselyCollectGarbage();
gc_count++;
AtomicIncrement(&gc_count_);
}
// Taking snapshot shouldn't have any bad side effect.
// TODO(haraken): This snapshot GC causes crashes, so disable
// it at the moment. Fix the crash and enable it.
// ThreadHeap::collectGarbage(BlinkGC::NoHeapPointersOnStack,
// BlinkGC::TakeSnapshot, BlinkGC::ForcedGC);
PreciselyCollectGarbage();
EXPECT_TRUE(weak_map->IsEmpty());
}
test::YieldCurrentThread();
}
ThreadState::DetachCurrentThread();
AtomicDecrement(&threads_to_finish_);
}
};
class ThreadPersistentHeapTester : public ThreadedTesterBase {
public:
static void Test() {
ThreadedTesterBase::Test(new ThreadPersistentHeapTester);
}
protected:
class Local final : public GarbageCollected<Local> {
public:
Local() = default;
void Trace(blink::Visitor* visitor) {}
};
class PersistentChain;
class RefCountedChain : public RefCounted<RefCountedChain> {
public:
static RefCountedChain* Create(int count) {
return new RefCountedChain(count);
}
private:
explicit RefCountedChain(int count) {
if (count > 0) {
--count;
persistent_chain_ = PersistentChain::Create(count);
}
}
Persistent<PersistentChain> persistent_chain_;
};
class PersistentChain : public GarbageCollectedFinalized<PersistentChain> {
public:
static PersistentChain* Create(int count) {
return new PersistentChain(count);
}
void Trace(blink::Visitor* visitor) {}
private:
explicit PersistentChain(int count) {
ref_counted_chain_ = base::AdoptRef(RefCountedChain::Create(count));
}
scoped_refptr<RefCountedChain> ref_counted_chain_;
};
void RunThread() override {
ThreadState::AttachCurrentThread();
PersistentChain::Create(100);
// Upon thread detach, GCs will run until all persistents have been
// released. We verify that the draining of persistents proceeds
// as expected by dropping one Persistent<> per GC until there
// are none left.
ThreadState::DetachCurrentThread();
AtomicDecrement(&threads_to_finish_);
}
};
// 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(blink::Visitor* visitor) { trace_count_++; }
int TraceCount() const { return trace_count_; }
private:
TraceCounter() : trace_count_(0) {}
int trace_count_;
};
TEST(HeapTest, IsHeapObjectAliveForConstPointer) {
// See http://crbug.com/661363.
SimpleObject* object = SimpleObject::Create();
HeapObjectHeader* header = HeapObjectHeader::FromPayload(object);
header->Mark();
EXPECT_TRUE(ThreadHeap::IsHeapObjectAlive(object));
const SimpleObject* const_object = const_cast<const SimpleObject*>(object);
EXPECT_TRUE(ThreadHeap::IsHeapObjectAlive(const_object));
}
class ClassWithMember : public GarbageCollected<ClassWithMember> {
public:
static ClassWithMember* Create() { return new ClassWithMember(); }
void Trace(blink::Visitor* visitor) {
visitor->Trace(trace_counter_);
}
int TraceCount() const { return trace_counter_->TraceCount(); }
private:
ClassWithMember() : trace_counter_(TraceCounter::Create()) {}
Member<TraceCounter> trace_counter_;
};
class SimpleFinalizedObject
: public GarbageCollectedFinalized<SimpleFinalizedObject> {
public:
static SimpleFinalizedObject* Create() { return new SimpleFinalizedObject(); }
~SimpleFinalizedObject() { ++destructor_calls_; }
static int destructor_calls_;
void Trace(blink::Visitor* visitor) {}
private:
SimpleFinalizedObject() = default;
};
int SimpleFinalizedObject::destructor_calls_ = 0;
class IntNode : public GarbageCollected<IntNode> {
public:
// IntNode is used to test typed heap allocation. Instead of
// redefining blink::Node to our test version, we keep it separate
// so as to avoid possible warnings about linker duplicates.
// Override operator new to allocate IntNode subtype objects onto
// the dedicated heap for blink::Node.
//
// TODO(haraken): untangling the heap unit tests from Blink would
// simplify and avoid running into this problem - http://crbug.com/425381
GC_PLUGIN_IGNORE("crbug.com/443854")
void* operator new(size_t size) {
ThreadState* state = ThreadState::Current();
const char* type_name = WTF_HEAP_PROFILER_TYPE_NAME(IntNode);
return state->Heap().AllocateOnArenaIndex(
state, size, BlinkGC::kNodeArenaIndex, GCInfoTrait<IntNode>::Index(),
type_name);
}
static IntNode* Create(int i) { return new IntNode(i); }
void Trace(blink::Visitor* visitor) {}
int Value() { return value_; }
private:
IntNode(int i) : value_(i) {}
int value_;
};
class Bar : public GarbageCollectedFinalized<Bar> {
public:
static Bar* Create() { return new Bar(); }
void FinalizeGarbageCollectedObject() {
EXPECT_TRUE(magic_ == kMagic);
magic_ = 0;
live_--;
}
bool HasBeenFinalized() const { return !magic_; }
virtual void Trace(blink::Visitor* visitor) {}
static unsigned live_;
protected:
static const int kMagic = 1337;
int magic_;
Bar() : magic_(kMagic) { live_++; }
};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(Bar);
unsigned Bar::live_ = 0;
class Baz : public GarbageCollected<Baz> {
public:
static Baz* Create(Bar* bar) { return new Baz(bar); }
void Trace(blink::Visitor* visitor) { visitor->Trace(bar_); }
void Clear() { bar_.Release(); }
// willFinalize is called by FinalizationObserver.
void WillFinalize() { EXPECT_TRUE(!bar_->HasBeenFinalized()); }
private:
explicit Baz(Bar* bar) : bar_(bar) {}
Member<Bar> bar_;
};
class Foo : public Bar {
public:
static Foo* Create(Bar* bar) { return new Foo(bar); }
static Foo* Create(Foo* foo) { return new Foo(foo); }
void Trace(blink::Visitor* visitor) override {
if (points_to_foo_)
visitor->Trace(static_cast<Foo*>(bar_));
else
visitor->Trace(bar_);
}
private:
Foo(Bar* bar) : Bar(), bar_(bar), points_to_foo_(false) {}
Foo(Foo* foo) : Bar(), bar_(foo), points_to_foo_(true) {}
Bar* bar_;
bool points_to_foo_;
};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(Foo);
class Bars : public Bar {
public:
static Bars* Create() { return new Bars(); }
void Trace(blink::Visitor* visitor) override {
for (unsigned i = 0; i < width_; i++)
visitor->Trace(bars_[i]);
}
unsigned GetWidth() const { return width_; }
static const unsigned kWidth = 7500;
private:
Bars() : width_(0) {
for (unsigned i = 0; i < kWidth; i++) {
bars_[i] = Bar::Create();
width_++;
}
}
unsigned width_;
Member<Bar> bars_[kWidth];
};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(Bars);
class ConstructorAllocation : public GarbageCollected<ConstructorAllocation> {
public:
static ConstructorAllocation* Create() { return new ConstructorAllocation(); }
void Trace(blink::Visitor* visitor) { visitor->Trace(int_wrapper_); }
private:
ConstructorAllocation() { int_wrapper_ = IntWrapper::Create(42); }
Member<IntWrapper> int_wrapper_;
};
class LargeHeapObject : public GarbageCollectedFinalized<LargeHeapObject> {
public:
~LargeHeapObject() { destructor_calls_++; }
static LargeHeapObject* Create() { return new LargeHeapObject(); }
char Get(size_t i) { return data_[i]; }
void Set(size_t i, char c) { data_[i] = c; }
size_t length() { return kLength; }
void Trace(blink::Visitor* visitor) { visitor->Trace(int_wrapper_); }
static int destructor_calls_;
private:
static const size_t kLength = 1024 * 1024;
LargeHeapObject() { int_wrapper_ = IntWrapper::Create(23); }
Member<IntWrapper> int_wrapper_;
char data_[kLength];
};
int LargeHeapObject::destructor_calls_ = 0;
// This test class served a more important role while Blink
// was transitioned over to using Oilpan. That required classes
// that were hybrid, both ref-counted and on the Oilpan heap
// (the RefCountedGarbageCollected<> class providing just that.)
//
// There's no current need for having a ref-counted veneer on
// top of a GCed class, but we preserve it here to exercise the
// implementation technique that it used -- keeping an internal
// "keep alive" persistent reference that is set & cleared across
// ref-counting operations.
//
class RefCountedAndGarbageCollected
: public GarbageCollectedFinalized<RefCountedAndGarbageCollected> {
public:
static RefCountedAndGarbageCollected* Create() {
return new RefCountedAndGarbageCollected;
}
~RefCountedAndGarbageCollected() { ++destructor_calls_; }
void AddRef() {
if (UNLIKELY(!ref_count_)) {
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(
reinterpret_cast<Address>(this)));
#endif
keep_alive_ = this;
}
++ref_count_;
}
void Release() {
DCHECK_GT(ref_count_, 0);
if (!--ref_count_)
keep_alive_.Clear();
}
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
private:
RefCountedAndGarbageCollected() : ref_count_(0) {}
int ref_count_;
SelfKeepAlive<RefCountedAndGarbageCollected> keep_alive_;
};
int RefCountedAndGarbageCollected::destructor_calls_ = 0;
class RefCountedAndGarbageCollected2
: public HeapTestOtherSuperClass,
public GarbageCollectedFinalized<RefCountedAndGarbageCollected2> {
public:
static RefCountedAndGarbageCollected2* Create() {
return new RefCountedAndGarbageCollected2;
}
~RefCountedAndGarbageCollected2() { ++destructor_calls_; }
void Ref() {
if (UNLIKELY(!ref_count_)) {
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(
reinterpret_cast<Address>(this)));
#endif
keep_alive_ = this;
}
++ref_count_;
}
void Deref() {
DCHECK_GT(ref_count_, 0);
if (!--ref_count_)
keep_alive_.Clear();
}
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
private:
RefCountedAndGarbageCollected2() : ref_count_(0) {}
int ref_count_;
SelfKeepAlive<RefCountedAndGarbageCollected2> keep_alive_;
};
int RefCountedAndGarbageCollected2::destructor_calls_ = 0;
class Weak : public Bar {
public:
static Weak* Create(Bar* strong, Bar* weak) { return new Weak(strong, weak); }
void Trace(blink::Visitor* visitor) override {
visitor->Trace(strong_bar_);
visitor->template RegisterWeakMembers<Weak, &Weak::ZapWeakMembers>(this);
}
void ZapWeakMembers(Visitor* visitor) {
if (!ThreadHeap::IsHeapObjectAlive(weak_bar_))
weak_bar_ = nullptr;
}
bool StrongIsThere() { return !!strong_bar_; }
bool WeakIsThere() { return !!weak_bar_; }
private:
Weak(Bar* strong_bar, Bar* weak_bar)
: Bar(), strong_bar_(strong_bar), weak_bar_(weak_bar) {}
Member<Bar> strong_bar_;
Bar* weak_bar_;
};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(Weak);
class WithWeakMember : public Bar {
public:
static WithWeakMember* Create(Bar* strong, Bar* weak) {
return new WithWeakMember(strong, weak);
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(strong_bar_);
visitor->Trace(weak_bar_);
}
bool StrongIsThere() { return !!strong_bar_; }
bool WeakIsThere() { return !!weak_bar_; }
private:
WithWeakMember(Bar* strong_bar, Bar* weak_bar)
: Bar(), strong_bar_(strong_bar), weak_bar_(weak_bar) {}
Member<Bar> strong_bar_;
WeakMember<Bar> weak_bar_;
};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(WithWeakMember);
class Observable : public GarbageCollectedFinalized<Observable> {
USING_PRE_FINALIZER(Observable, WillFinalize);
public:
static Observable* Create(Bar* bar) { return new Observable(bar); }
~Observable() { was_destructed_ = true; }
void Trace(blink::Visitor* visitor) { visitor->Trace(bar_); }
// willFinalize is called by FinalizationObserver. willFinalize can touch
// other on-heap objects.
void WillFinalize() {
EXPECT_FALSE(was_destructed_);
EXPECT_FALSE(bar_->HasBeenFinalized());
will_finalize_was_called_ = true;
}
static bool will_finalize_was_called_;
private:
explicit Observable(Bar* bar) : bar_(bar), was_destructed_(false) {}
Member<Bar> bar_;
bool was_destructed_;
};
bool Observable::will_finalize_was_called_ = false;
class ObservableWithPreFinalizer
: public GarbageCollectedFinalized<ObservableWithPreFinalizer> {
USING_PRE_FINALIZER(ObservableWithPreFinalizer, Dispose);
public:
static ObservableWithPreFinalizer* Create() {
return new ObservableWithPreFinalizer();
}
~ObservableWithPreFinalizer() { was_destructed_ = true; }
void Trace(blink::Visitor* visitor) {}
void Dispose() {
EXPECT_FALSE(was_destructed_);
dispose_was_called_ = true;
}
static bool dispose_was_called_;
protected:
ObservableWithPreFinalizer() : was_destructed_(false) {}
bool was_destructed_;
};
bool ObservableWithPreFinalizer::dispose_was_called_ = false;
bool g_dispose_was_called_for_pre_finalizer_base = false;
bool g_dispose_was_called_for_pre_finalizer_mixin = false;
bool g_dispose_was_called_for_pre_finalizer_sub_class = false;
class PreFinalizerBase : public GarbageCollectedFinalized<PreFinalizerBase> {
USING_PRE_FINALIZER(PreFinalizerBase, Dispose);
public:
static PreFinalizerBase* Create() { return new PreFinalizerBase(); }
virtual ~PreFinalizerBase() { was_destructed_ = true; }
virtual void Trace(blink::Visitor* visitor) {}
void Dispose() {
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_base);
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_sub_class);
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_mixin);
EXPECT_FALSE(was_destructed_);
g_dispose_was_called_for_pre_finalizer_base = true;
}
protected:
PreFinalizerBase() : was_destructed_(false) {}
bool was_destructed_;
};
class PreFinalizerMixin : public GarbageCollectedMixin {
USING_PRE_FINALIZER(PreFinalizerMixin, Dispose);
public:
~PreFinalizerMixin() { was_destructed_ = true; }
void Trace(blink::Visitor* visitor) override {}
void Dispose() {
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_base);
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_sub_class);
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_mixin);
EXPECT_FALSE(was_destructed_);
g_dispose_was_called_for_pre_finalizer_mixin = true;
}
protected:
PreFinalizerMixin() : was_destructed_(false) {}
bool was_destructed_;
};
class PreFinalizerSubClass : public PreFinalizerBase, public PreFinalizerMixin {
USING_GARBAGE_COLLECTED_MIXIN(PreFinalizerSubClass);
USING_PRE_FINALIZER(PreFinalizerSubClass, Dispose);
public:
static PreFinalizerSubClass* Create() { return new PreFinalizerSubClass(); }
~PreFinalizerSubClass() override { was_destructed_ = true; }
void Trace(blink::Visitor* visitor) override {}
void Dispose() {
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_base);
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_sub_class);
EXPECT_FALSE(g_dispose_was_called_for_pre_finalizer_mixin);
EXPECT_FALSE(was_destructed_);
g_dispose_was_called_for_pre_finalizer_sub_class = true;
}
protected:
PreFinalizerSubClass() : was_destructed_(false) {}
bool was_destructed_;
};
template <typename T>
class FinalizationObserver : public GarbageCollected<FinalizationObserver<T>> {
public:
static FinalizationObserver* Create(T* data) {
return new FinalizationObserver(data);
}
bool DidCallWillFinalize() const { return did_call_will_finalize_; }
void Trace(blink::Visitor* visitor) {
visitor->template RegisterWeakMembers<
FinalizationObserver<T>, &FinalizationObserver<T>::ZapWeakMembers>(
this);
}
void ZapWeakMembers(Visitor* visitor) {
if (data_ && !ThreadHeap::IsHeapObjectAlive(data_)) {
data_->WillFinalize();
data_ = nullptr;
did_call_will_finalize_ = true;
}
}
private:
FinalizationObserver(T* data) : data_(data), did_call_will_finalize_(false) {}
WeakMember<T> data_;
bool did_call_will_finalize_;
};
class FinalizationObserverWithHashMap {
public:
typedef HeapHashMap<WeakMember<Observable>,
std::unique_ptr<FinalizationObserverWithHashMap>>
ObserverMap;
explicit FinalizationObserverWithHashMap(Observable& target)
: target_(target) {}
~FinalizationObserverWithHashMap() {
target_.WillFinalize();
did_call_will_finalize_ = true;
}
static ObserverMap& Observe(Observable& target) {
ObserverMap& map = Observers();
ObserverMap::AddResult result = map.insert(&target, nullptr);
if (result.is_new_entry) {
result.stored_value->value =
std::make_unique<FinalizationObserverWithHashMap>(target);
} else {
DCHECK(result.stored_value->value);
}
return map;
}
static void ClearObservers() {
delete observer_map_;
observer_map_ = nullptr;
}
static bool did_call_will_finalize_;
private:
static ObserverMap& Observers() {
if (!observer_map_)
observer_map_ = new Persistent<ObserverMap>(new ObserverMap());
return **observer_map_;
}
Observable& target_;
static Persistent<ObserverMap>* observer_map_;
};
bool FinalizationObserverWithHashMap::did_call_will_finalize_ = false;
Persistent<FinalizationObserverWithHashMap::ObserverMap>*
FinalizationObserverWithHashMap::observer_map_;
class SuperClass;
class PointsBack : public GarbageCollectedFinalized<PointsBack> {
public:
static PointsBack* Create() { return new PointsBack; }
~PointsBack() { --alive_count_; }
void SetBackPointer(SuperClass* back_pointer) {
back_pointer_ = back_pointer;
}
SuperClass* BackPointer() const { return back_pointer_; }
void Trace(blink::Visitor* visitor) { visitor->Trace(back_pointer_); }
static int alive_count_;
private:
PointsBack() : back_pointer_(nullptr) { ++alive_count_; }
WeakMember<SuperClass> back_pointer_;
};
int PointsBack::alive_count_ = 0;
class SuperClass : public GarbageCollectedFinalized<SuperClass> {
public:
static SuperClass* Create(PointsBack* points_back) {
return new SuperClass(points_back);
}
virtual ~SuperClass() { --alive_count_; }
void DoStuff(SuperClass* target,
PointsBack* points_back,
int super_class_count) {
ConservativelyCollectGarbage();
EXPECT_EQ(points_back, target->GetPointsBack());
EXPECT_EQ(super_class_count, SuperClass::alive_count_);
}
virtual void Trace(blink::Visitor* visitor) { visitor->Trace(points_back_); }
PointsBack* GetPointsBack() const { return points_back_.Get(); }
static int alive_count_;
protected:
explicit SuperClass(PointsBack* points_back) : points_back_(points_back) {
points_back_->SetBackPointer(this);
++alive_count_;
}
private:
Member<PointsBack> points_back_;
};
int SuperClass::alive_count_ = 0;
class SubData : public GarbageCollectedFinalized<SubData> {
public:
SubData() { ++alive_count_; }
~SubData() { --alive_count_; }
void Trace(blink::Visitor* visitor) {}
static int alive_count_;
};
int SubData::alive_count_ = 0;
class SubClass : public SuperClass {
public:
static SubClass* Create(PointsBack* points_back) {
return new SubClass(points_back);
}
~SubClass() override { --alive_count_; }
void Trace(blink::Visitor* visitor) override {
visitor->Trace(data_);
SuperClass::Trace(visitor);
}
static int alive_count_;
private:
explicit SubClass(PointsBack* points_back)
: SuperClass(points_back), data_(new SubData) {
++alive_count_;
}
private:
Member<SubData> data_;
};
int SubClass::alive_count_ = 0;
class Mixin : public GarbageCollectedMixin {
public:
void Trace(blink::Visitor* visitor) override {}
virtual char GetPayload(int i) { return padding_[i]; }
protected:
int padding_[8];
};
class UseMixin : public SimpleObject, public Mixin {
USING_GARBAGE_COLLECTED_MIXIN(UseMixin)
public:
static UseMixin* Create() { return new UseMixin(); }
static int trace_count_;
void Trace(blink::Visitor* visitor) override {
SimpleObject::Trace(visitor);
Mixin::Trace(visitor);
++trace_count_;
}
private:
UseMixin() {
// Verify that WTF::IsGarbageCollectedType<> works as expected for mixins.
static_assert(WTF::IsGarbageCollectedType<UseMixin>::value,
"IsGarbageCollectedType<> sanity check failed for GC mixin.");
trace_count_ = 0;
}
};
int UseMixin::trace_count_ = 0;
class VectorObject {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
VectorObject() { value_ = SimpleFinalizedObject::Create(); }
void Trace(blink::Visitor* visitor) { visitor->Trace(value_); }
private:
Member<SimpleFinalizedObject> value_;
};
class VectorObjectInheritedTrace : public VectorObject {};
class VectorObjectNoTrace {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
VectorObjectNoTrace() { value_ = SimpleFinalizedObject::Create(); }
private:
Member<SimpleFinalizedObject> value_;
};
class TerminatedArrayItem {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
TerminatedArrayItem(IntWrapper* payload)
: payload_(payload), is_last_(false) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(payload_); }
bool IsLastInArray() const { return is_last_; }
void SetLastInArray(bool value) { is_last_ = value; }
IntWrapper* Payload() const { return payload_; }
private:
Member<IntWrapper> payload_;
bool is_last_;
};
} // namespace blink
WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::TerminatedArrayItem);
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() { destructor_calls_++; }
char* Data() { return data_; }
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
private:
static const size_t kLength = 1024;
char data_[kLength];
};
int OneKiloByteObject::destructor_calls_ = 0;
class DynamicallySizedObject : public GarbageCollected<DynamicallySizedObject> {
public:
static DynamicallySizedObject* Create(size_t size) {
void* slot = ThreadHeap::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(blink::Visitor* visitor) {}
private:
DynamicallySizedObject() = default;
};
class FinalizationAllocator
: public GarbageCollectedFinalized<FinalizationAllocator> {
public:
FinalizationAllocator(Persistent<IntWrapper>* wrapper) : wrapper_(wrapper) {}
~FinalizationAllocator() {
for (int i = 0; i < 10; ++i)
*wrapper_ = IntWrapper::Create(42);
for (int i = 0; i < 512; ++i)
new OneKiloByteObject();
for (int i = 0; i < 32; ++i)
LargeHeapObject::Create();
}
void Trace(blink::Visitor* visitor) {}
private:
Persistent<IntWrapper>* wrapper_;
};
class PreFinalizationAllocator
: public GarbageCollectedFinalized<PreFinalizationAllocator> {
USING_PRE_FINALIZER(PreFinalizationAllocator, Dispose);
public:
PreFinalizationAllocator(Persistent<IntWrapper>* wrapper)
: wrapper_(wrapper) {}
void Dispose() {
for (int i = 0; i < 10; ++i)
*wrapper_ = IntWrapper::Create(42);
for (int i = 0; i < 512; ++i)
new OneKiloByteObject();
for (int i = 0; i < 32; ++i)
LargeHeapObject::Create();
}
void Trace(blink::Visitor* visitor) {}
private:
Persistent<IntWrapper>* wrapper_;
};
class PreFinalizerBackingShrinkForbidden
: public GarbageCollectedFinalized<PreFinalizerBackingShrinkForbidden> {
USING_PRE_FINALIZER(PreFinalizerBackingShrinkForbidden, Dispose);
public:
PreFinalizerBackingShrinkForbidden() {
for (int i = 0; i < 32; ++i) {
vector_.push_back(new IntWrapper(i));
}
EXPECT_LT(31ul, vector_.capacity());
for (int i = 0; i < 32; ++i) {
map_.insert(i + 1, new IntWrapper(i + 1));
}
EXPECT_LT(31ul, map_.Capacity());
}
void Dispose() {
// Remove all elemets except one so that vector_ will try to shrink.
for (int i = 1; i < 32; ++i) {
vector_.pop_back();
}
// Check that vector_ hasn't shrunk.
EXPECT_LT(31ul, vector_.capacity());
// Just releasing the backing is allowed.
vector_.clear();
EXPECT_EQ(0ul, vector_.capacity());
// Remove elemets so that map_ will try to shrink.
for (int i = 0; i < 32; ++i) {
map_.erase(i + 1);
}
// Check that map_ hasn't shrunk.
EXPECT_LT(31ul, map_.Capacity());
// Just releasing the backing is allowed.
map_.clear();
EXPECT_EQ(0ul, map_.Capacity());
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(vector_);
visitor->Trace(map_);
}
private:
HeapVector<Member<IntWrapper>> vector_;
HeapHashMap<int, Member<IntWrapper>> map_;
};
TEST(HeapTest, PreFinalizerBackingShrinkForbidden) {
new PreFinalizerBackingShrinkForbidden();
PreciselyCollectGarbage();
}
class PreFinalizerVectorBackingExpandForbidden
: public GarbageCollectedFinalized<
PreFinalizerVectorBackingExpandForbidden> {
USING_PRE_FINALIZER(PreFinalizerVectorBackingExpandForbidden, Dispose);
public:
PreFinalizerVectorBackingExpandForbidden() {
vector_.push_back(new IntWrapper(1));
}
void Dispose() { EXPECT_DEATH(Test(), ""); }
void Test() {
// vector_'s backing will need to expand.
for (int i = 0; i < 32; ++i) {
vector_.push_back(nullptr);
}
}
void Trace(blink::Visitor* visitor) { visitor->Trace(vector_); }
private:
HeapVector<Member<IntWrapper>> vector_;
};
TEST(HeapDeathTest, PreFinalizerVectorBackingExpandForbidden) {
new PreFinalizerVectorBackingExpandForbidden();
PreciselyCollectGarbage();
}
class PreFinalizerHashTableBackingExpandForbidden
: public GarbageCollectedFinalized<
PreFinalizerHashTableBackingExpandForbidden> {
USING_PRE_FINALIZER(PreFinalizerHashTableBackingExpandForbidden, Dispose);
public:
PreFinalizerHashTableBackingExpandForbidden() {
map_.insert(123, new IntWrapper(123));
}
void Dispose() { EXPECT_DEATH(Test(), ""); }
void Test() {
// map_'s backing will need to expand.
for (int i = 1; i < 32; ++i) {
map_.insert(i, nullptr);
}
}
void Trace(blink::Visitor* visitor) { visitor->Trace(map_); }
private:
HeapHashMap<int, Member<IntWrapper>> map_;
};
TEST(HeapDeathTest, PreFinalizerHashTableBackingExpandForbidden) {
new PreFinalizerHashTableBackingExpandForbidden();
PreciselyCollectGarbage();
}
class LargeMixin : public GarbageCollected<LargeMixin>, public Mixin {
USING_GARBAGE_COLLECTED_MIXIN(LargeMixin);
private:
char data[65536];
};
TEST(HeapDeathTest, LargeGarbageCollectedMixin) {
EXPECT_DEATH(new LargeMixin(), "");
}
TEST(HeapTest, Transition) {
{
RefCountedAndGarbageCollected::destructor_calls_ = 0;
Persistent<RefCountedAndGarbageCollected> ref_counted =
RefCountedAndGarbageCollected::Create();
PreciselyCollectGarbage();
EXPECT_EQ(0, RefCountedAndGarbageCollected::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(1, RefCountedAndGarbageCollected::destructor_calls_);
RefCountedAndGarbageCollected::destructor_calls_ = 0;
Persistent<PointsBack> points_back1 = PointsBack::Create();
Persistent<PointsBack> points_back2 = PointsBack::Create();
Persistent<SuperClass> super_class = SuperClass::Create(points_back1);
Persistent<SubClass> sub_class = SubClass::Create(points_back2);
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(2, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
PreciselyCollectGarbage();
EXPECT_EQ(0, RefCountedAndGarbageCollected::destructor_calls_);
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(2, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
super_class->DoStuff(super_class.Release(), points_back1.Get(), 2);
PreciselyCollectGarbage();
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(1, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
EXPECT_EQ(nullptr, points_back1->BackPointer());
points_back1.Release();
PreciselyCollectGarbage();
EXPECT_EQ(1, PointsBack::alive_count_);
EXPECT_EQ(1, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
sub_class->DoStuff(sub_class.Release(), points_back2.Get(), 1);
PreciselyCollectGarbage();
EXPECT_EQ(1, PointsBack::alive_count_);
EXPECT_EQ(0, SuperClass::alive_count_);
EXPECT_EQ(0, SubClass::alive_count_);
EXPECT_EQ(0, SubData::alive_count_);
EXPECT_EQ(nullptr, points_back2->BackPointer());
points_back2.Release();
PreciselyCollectGarbage();
EXPECT_EQ(0, PointsBack::alive_count_);
EXPECT_EQ(0, SuperClass::alive_count_);
EXPECT_EQ(0, SubClass::alive_count_);
EXPECT_EQ(0, SubData::alive_count_);
EXPECT_TRUE(super_class == sub_class);
}
TEST(HeapTest, Threading) {
ThreadedHeapTester::Test();
}
TEST(HeapTest, ThreadedWeakness) {
ThreadedWeaknessTester::Test();
}
TEST(HeapTest, ThreadPersistent) {
ThreadPersistentHeapTester::Test();
}
TEST(HeapTest, BasicFunctionality) {
ThreadHeap& heap = ThreadState::Current()->Heap();
ClearOutOldGarbage();
size_t initial_object_payload_size = heap.ObjectPayloadSizeForTesting();
{
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 base_level = initial_object_payload_size;
bool test_pages_allocated = !base_level;
if (test_pages_allocated)
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes());
// 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;
CheckWithSlack(base_level + total, heap.ObjectPayloadSizeForTesting(),
slack);
if (test_pages_allocated) {
EXPECT_EQ(kBlinkPageSize * 2,
heap.stats_collector()->allocated_space_bytes());
}
EXPECT_EQ(alloc32->Get(0), 40);
EXPECT_EQ(alloc32->Get(31), 40);
EXPECT_EQ(alloc64->Get(0), 27);
EXPECT_EQ(alloc64->Get(63), 27);
ConservativelyCollectGarbage();
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();
size_t total = 0;
size_t slack = 0;
size_t base_level = heap.ObjectPayloadSizeForTesting();
bool test_pages_allocated = !base_level;
if (test_pages_allocated)
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes());
size_t big = 1008;
Persistent<DynamicallySizedObject> big_area =
DynamicallySizedObject::Create(big);
total += big;
slack += 4;
size_t persistent_count = 0;
const size_t kNumPersistents = 100000;
Persistent<DynamicallySizedObject>* persistents[kNumPersistents];
for (int i = 0; i < 1000; i++) {
size_t size = 128 + i * 8;
total += size;
persistents[persistent_count++] = new Persistent<DynamicallySizedObject>(
DynamicallySizedObject::Create(size));
slack += 4;
CheckWithSlack(base_level + total, heap.ObjectPayloadSizeForTesting(),
slack);
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes() &
(kBlinkPageSize - 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;
CheckWithSlack(base_level + total, heap.ObjectPayloadSizeForTesting(),
slack);
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes() &
(kBlinkPageSize - 1));
}
}
ClearOutOldGarbage();
total -= 96;
slack -= 8;
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes() &
(kBlinkPageSize - 1));
}
// Clear the persistent, so that the big area will be garbage collected.
big_area.Release();
ClearOutOldGarbage();
total -= big;
slack -= 4;
CheckWithSlack(base_level + total, heap.ObjectPayloadSizeForTesting(), slack);
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes() &
(kBlinkPageSize - 1));
}
CheckWithSlack(base_level + total, heap.ObjectPayloadSizeForTesting(), slack);
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap.stats_collector()->allocated_space_bytes() &
(kBlinkPageSize - 1));
}
for (size_t i = 0; i < persistent_count; i++) {
delete persistents[i];
persistents[i] = nullptr;
}
uint8_t* address = reinterpret_cast<uint8_t*>(
ThreadHeap::Allocate<DynamicallySizedObject>(100));
for (int i = 0; i < 100; i++)
address[i] = i;
address = reinterpret_cast<uint8_t*>(
ThreadHeap::Reallocate<DynamicallySizedObject>(address, 100000));
for (int i = 0; i < 100; i++)
EXPECT_EQ(address[i], i);
address = reinterpret_cast<uint8_t*>(
ThreadHeap::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>(
ThreadHeap::Reallocate<DynamicallySizedObject>(address, 0)),
0ul);
// This should be equivalent to malloc(0).
EXPECT_EQ(reinterpret_cast<uintptr_t>(
ThreadHeap::Reallocate<DynamicallySizedObject>(nullptr, 0)),
0ul);
}
TEST(HeapTest, SimpleAllocation) {
ThreadHeap& heap = ThreadState::Current()->Heap();
ClearOutOldGarbage();
EXPECT_EQ(0ul, heap.ObjectPayloadSizeForTesting());
// Allocate an object in the heap.
HeapAllocatedArray* array = new HeapAllocatedArray();
EXPECT_TRUE(heap.ObjectPayloadSizeForTesting() >= 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> trace_counter = TraceCounter::Create();
EXPECT_EQ(0, trace_counter->TraceCount());
PreciselyCollectGarbage();
int saved_trace_count = trace_counter->TraceCount();
EXPECT_LT(0, saved_trace_count);
Persistent<ClassWithMember> class_with_member = ClassWithMember::Create();
EXPECT_EQ(0, class_with_member->TraceCount());
PreciselyCollectGarbage();
EXPECT_LT(0, class_with_member->TraceCount());
EXPECT_LT(saved_trace_count, trace_counter->TraceCount());
}
TEST(HeapTest, SimpleFinalization) {
{
Persistent<SimpleFinalizedObject> finalized =
SimpleFinalizedObject::Create();
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
PreciselyCollectGarbage();
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(1, SimpleFinalizedObject::destructor_calls_);
}
#if DCHECK_IS_ON() || defined(LEAK_SANITIZER) || defined(ADDRESS_SANITIZER)
TEST(HeapTest, FreelistReuse) {
ClearOutOldGarbage();
for (int i = 0; i < 100; i++)
new IntWrapper(i);
IntWrapper* p1 = new IntWrapper(100);
PreciselyCollectGarbage();
// In non-production builds, we delay reusing freed memory for at least
// one GC cycle.
for (int i = 0; i < 100; i++) {
IntWrapper* p2 = new IntWrapper(i);
EXPECT_NE(p1, p2);
}
PreciselyCollectGarbage();
PreciselyCollectGarbage();
// Now the freed memory in the first GC should be reused.
bool reused_memory_found = false;
for (int i = 0; i < 10000; i++) {
IntWrapper* p2 = new IntWrapper(i);
if (p1 == p2) {
reused_memory_found = true;
break;
}
}
EXPECT_TRUE(reused_memory_found);
}
#endif
TEST(HeapTest, LazySweepingPages) {
ClearOutOldGarbage();
SimpleFinalizedObject::destructor_calls_ = 0;
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
for (int i = 0; i < 1000; i++)
SimpleFinalizedObject::Create();
ThreadState::Current()->CollectGarbage(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kForcedGC);
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
for (int i = 0; i < 10000; i++)
SimpleFinalizedObject::Create();
EXPECT_EQ(1000, SimpleFinalizedObject::destructor_calls_);
PreciselyCollectGarbage();
EXPECT_EQ(11000, SimpleFinalizedObject::destructor_calls_);
}
TEST(HeapTest, LazySweepingLargeObjectPages) {
ClearOutOldGarbage();
// Create free lists that can be reused for IntWrappers created in
// LargeHeapObject::create().
Persistent<IntWrapper> p1 = new IntWrapper(1);
for (int i = 0; i < 100; i++) {
new IntWrapper(i);
}
Persistent<IntWrapper> p2 = new IntWrapper(2);
PreciselyCollectGarbage();
PreciselyCollectGarbage();
LargeHeapObject::destructor_calls_ = 0;
EXPECT_EQ(0, LargeHeapObject::destructor_calls_);
for (int i = 0; i < 10; i++)
LargeHeapObject::Create();
ThreadState::Current()->CollectGarbage(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kForcedGC);
EXPECT_EQ(0, LargeHeapObject::destructor_calls_);
for (int i = 0; i < 10; i++) {
LargeHeapObject::Create();
EXPECT_EQ(i + 1, LargeHeapObject::destructor_calls_);
}
LargeHeapObject::Create();
LargeHeapObject::Create();
EXPECT_EQ(10, LargeHeapObject::destructor_calls_);
ThreadState::Current()->CollectGarbage(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kForcedGC);
EXPECT_EQ(10, LargeHeapObject::destructor_calls_);
PreciselyCollectGarbage();
EXPECT_EQ(22, LargeHeapObject::destructor_calls_);
}
class SimpleFinalizedEagerObjectBase
: public GarbageCollectedFinalized<SimpleFinalizedEagerObjectBase> {
public:
virtual ~SimpleFinalizedEagerObjectBase() = default;
void Trace(blink::Visitor* visitor) {}
EAGERLY_FINALIZE();
protected:
SimpleFinalizedEagerObjectBase() = default;
};
class SimpleFinalizedEagerObject : public SimpleFinalizedEagerObjectBase {
public:
static SimpleFinalizedEagerObject* Create() {
return new SimpleFinalizedEagerObject();
}
~SimpleFinalizedEagerObject() override { ++destructor_calls_; }
static int destructor_calls_;
private:
SimpleFinalizedEagerObject() = default;
};
template <typename T>
class ParameterizedButEmpty {
public:
EAGERLY_FINALIZE();
};
class SimpleFinalizedObjectInstanceOfTemplate final
: public GarbageCollectedFinalized<SimpleFinalizedObjectInstanceOfTemplate>,
public ParameterizedButEmpty<SimpleFinalizedObjectInstanceOfTemplate> {
public:
static SimpleFinalizedObjectInstanceOfTemplate* Create() {
return new SimpleFinalizedObjectInstanceOfTemplate();
}
~SimpleFinalizedObjectInstanceOfTemplate() { ++destructor_calls_; }
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
private:
SimpleFinalizedObjectInstanceOfTemplate() = default;
};
int SimpleFinalizedEagerObject::destructor_calls_ = 0;
int SimpleFinalizedObjectInstanceOfTemplate::destructor_calls_ = 0;
TEST(HeapTest, EagerlySweepingPages) {
ClearOutOldGarbage();
SimpleFinalizedObject::destructor_calls_ = 0;
SimpleFinalizedEagerObject::destructor_calls_ = 0;
SimpleFinalizedObjectInstanceOfTemplate::destructor_calls_ = 0;
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
EXPECT_EQ(0, SimpleFinalizedEagerObject::destructor_calls_);
for (int i = 0; i < 1000; i++)
SimpleFinalizedObject::Create();
for (int i = 0; i < 100; i++)
SimpleFinalizedEagerObject::Create();
for (int i = 0; i < 100; i++)
SimpleFinalizedObjectInstanceOfTemplate::Create();
ThreadState::Current()->CollectGarbage(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kForcedGC);
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
EXPECT_EQ(100, SimpleFinalizedEagerObject::destructor_calls_);
EXPECT_EQ(100, SimpleFinalizedObjectInstanceOfTemplate::destructor_calls_);
}
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.
PreciselyCollectGarbage();
EXPECT_EQ(2, HeapTestSubClass::destructor_calls_);
EXPECT_EQ(3, HeapTestSuperClass::destructor_calls_);
// Destructors not called again when GCing again.
PreciselyCollectGarbage();
EXPECT_EQ(2, HeapTestSubClass::destructor_calls_);
EXPECT_EQ(3, HeapTestSuperClass::destructor_calls_);
}
TEST(HeapTest, TypedArenaSanity) {
// We use TraceCounter for allocating an object on the general heap.
Persistent<TraceCounter> general_heap_object = TraceCounter::Create();
Persistent<IntNode> typed_heap_object = IntNode::Create(0);
EXPECT_NE(PageFromObject(general_heap_object.Get()),
PageFromObject(typed_heap_object.Get()));
}
TEST(HeapTest, NoAllocation) {
ThreadState* state = ThreadState::Current();
EXPECT_TRUE(state->IsAllocationAllowed());
{
// Disallow allocation
ThreadState::NoAllocationScope no_allocation_scope(state);
EXPECT_FALSE(state->IsAllocationAllowed());
}
EXPECT_TRUE(state->IsAllocationAllowed());
}
TEST(HeapTest, Members) {
Bar::live_ = 0;
{
Persistent<Baz> h1;
Persistent<Baz> h2;
{
h1 = Baz::Create(Bar::Create());
PreciselyCollectGarbage();
EXPECT_EQ(1u, Bar::live_);
h2 = Baz::Create(Bar::Create());
PreciselyCollectGarbage();
EXPECT_EQ(2u, Bar::live_);
}
PreciselyCollectGarbage();
EXPECT_EQ(2u, Bar::live_);
h1->Clear();
PreciselyCollectGarbage();
EXPECT_EQ(1u, Bar::live_);
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
}
TEST(HeapTest, MarkTest) {
{
Bar::live_ = 0;
Persistent<Bar> bar = Bar::Create();
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(bar));
#endif
EXPECT_EQ(1u, Bar::live_);
{
Foo* foo = Foo::Create(bar);
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(foo));
#endif
EXPECT_EQ(2u, Bar::live_);
EXPECT_TRUE(reinterpret_cast<Address>(foo) !=
reinterpret_cast<Address>(bar.Get()));
ConservativelyCollectGarbage();
EXPECT_TRUE(foo != bar); // To make sure foo is kept alive.
EXPECT_EQ(2u, Bar::live_);
}
PreciselyCollectGarbage();
EXPECT_EQ(1u, Bar::live_);
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
}
TEST(HeapTest, DeepTest) {
const unsigned kDepth = 100000;
Bar::live_ = 0;
{
Bar* bar = Bar::Create();
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(bar));
#endif
Foo* foo = Foo::Create(bar);
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(foo));
#endif
EXPECT_EQ(2u, Bar::live_);
for (unsigned i = 0; i < kDepth; i++) {
Foo* foo2 = Foo::Create(foo);
foo = foo2;
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(foo));
#endif
}
EXPECT_EQ(kDepth + 2, Bar::live_);
ConservativelyCollectGarbage();
EXPECT_TRUE(foo != bar); // To make sure foo and bar are kept alive.
EXPECT_EQ(kDepth + 2, Bar::live_);
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
}
TEST(HeapTest, WideTest) {
Bar::live_ = 0;
{
Bars* bars = Bars::Create();
unsigned width = Bars::kWidth;
EXPECT_EQ(width + 1, Bar::live_);
ConservativelyCollectGarbage();
EXPECT_EQ(width + 1, Bar::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::kWidth + 1, Bar::live_);
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
}
TEST(HeapTest, HashMapOfMembers) {
ThreadHeap& heap = ThreadState::Current()->Heap();
IntWrapper::destructor_calls_ = 0;
ClearOutOldGarbage();
size_t initial_object_payload_size = heap.ObjectPayloadSizeForTesting();
{
typedef HeapHashMap<Member<IntWrapper>, Member<IntWrapper>,
DefaultHash<Member<IntWrapper>>::Hash,
HashTraits<Member<IntWrapper>>,
HashTraits<Member<IntWrapper>>>
HeapObjectIdentityMap;
Persistent<HeapObjectIdentityMap> map = new HeapObjectIdentityMap();
map->clear();
size_t after_set_was_created = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(after_set_was_created > initial_object_payload_size);
PreciselyCollectGarbage();
size_t after_gc = heap.ObjectPayloadSizeForTesting();
EXPECT_EQ(after_gc, after_set_was_created);
// If the additions below cause garbage collections, these
// pointers should be found by conservative stack scanning.
IntWrapper* one(IntWrapper::Create(1));
IntWrapper* another_one(IntWrapper::Create(1));
map->insert(one, one);
size_t after_one_add = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(after_one_add > after_gc);
HeapObjectIdentityMap::iterator it(map->begin());
HeapObjectIdentityMap::iterator it2(map->begin());
++it;
++it2;
map->insert(another_one, 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.
PreciselyCollectGarbage();
size_t after_add_and_gc = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(after_add_and_gc >= after_one_add);
EXPECT_EQ(map->size(), 2u); // Two different wrappings of '1' are distinct.
PreciselyCollectGarbage();
EXPECT_TRUE(map->Contains(one));
EXPECT_TRUE(map->Contains(another_one));
IntWrapper* gotten(map->at(one));
EXPECT_EQ(gotten->Value(), one->Value());
EXPECT_EQ(gotten, one);
size_t after_gc2 = heap.ObjectPayloadSizeForTesting();
EXPECT_EQ(after_gc2, after_add_and_gc);
IntWrapper* dozen = nullptr;
for (int i = 1; i < 1000; i++) { // 999 iterations.
IntWrapper* i_wrapper(IntWrapper::Create(i));
IntWrapper* i_squared(IntWrapper::Create(i * i));
map->insert(i_wrapper, i_squared);
if (i == 12)
dozen = i_wrapper;
}
size_t after_adding1000 = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(after_adding1000 > after_gc2);
IntWrapper* gross(map->at(dozen));
EXPECT_EQ(gross->Value(), 144);
// This should clear out any junk backings created by all the adds.
PreciselyCollectGarbage();
size_t after_gc3 = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(after_gc3 <= after_adding1000);
}
PreciselyCollectGarbage();
// The objects 'one', anotherOne, and the 999 other pairs.
EXPECT_EQ(IntWrapper::destructor_calls_, 2000);
size_t after_gc4 = heap.ObjectPayloadSizeForTesting();
EXPECT_EQ(after_gc4, initial_object_payload_size);
}
TEST(HeapTest, NestedAllocation) {
ThreadHeap& heap = ThreadState::Current()->Heap();
ClearOutOldGarbage();
size_t initial_object_payload_size = heap.ObjectPayloadSizeForTesting();
{
Persistent<ConstructorAllocation> constructor_allocation =
ConstructorAllocation::Create();
}
ClearOutOldGarbage();
size_t after_free = heap.ObjectPayloadSizeForTesting();
EXPECT_TRUE(initial_object_payload_size == after_free);
}
TEST(HeapTest, LargeHeapObjects) {
ThreadHeap& heap = ThreadState::Current()->Heap();
ClearOutOldGarbage();
size_t initial_object_payload_size = heap.ObjectPayloadSizeForTesting();
size_t initial_allocated_space =
heap.stats_collector()->allocated_space_bytes();
IntWrapper::destructor_calls_ = 0;
LargeHeapObject::destructor_calls_ = 0;
{
int slack =
8; // LargeHeapObject points to an IntWrapper that is also allocated.
Persistent<LargeHeapObject> object = LargeHeapObject::Create();
#if DCHECK_IS_ON()
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(object));
DCHECK(ThreadState::Current()->Heap().FindPageFromAddress(
reinterpret_cast<char*>(object.Get()) + sizeof(LargeHeapObject) - 1));
#endif
ClearOutOldGarbage();
size_t after_allocation = heap.stats_collector()->allocated_space_bytes();
{
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 expected_large_heap_object_payload_size =
ThreadHeap::AllocationSizeFromSize(sizeof(LargeHeapObject)) -
sizeof(HeapObjectHeader);
size_t expected_object_payload_size =
expected_large_heap_object_payload_size + sizeof(IntWrapper);
size_t actual_object_payload_size =
heap.ObjectPayloadSizeForTesting() - initial_object_payload_size;
CheckWithSlack(expected_object_payload_size, actual_object_payload_size,
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 large_object_allocation_size =
sizeof(LargeObjectPage) + expected_large_heap_object_payload_size;
size_t allocated_space_lower_bound =
initial_allocated_space + large_object_allocation_size;
size_t allocated_space_upper_bound =
allocated_space_lower_bound + slack + kBlinkPageSize;
EXPECT_LE(allocated_space_lower_bound, after_allocation);
EXPECT_LE(after_allocation, allocated_space_upper_bound);
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(0, LargeHeapObject::destructor_calls_);
for (int i = 0; i < 10; i++)
object = LargeHeapObject::Create();
}
ClearOutOldGarbage();
EXPECT_EQ(after_allocation,
heap.stats_collector()->allocated_space_bytes());
EXPECT_EQ(10, IntWrapper::destructor_calls_);
EXPECT_EQ(10, LargeHeapObject::destructor_calls_);
}
ClearOutOldGarbage();
EXPECT_TRUE(initial_object_payload_size ==
heap.ObjectPayloadSizeForTesting());
EXPECT_EQ(initial_allocated_space,
heap.stats_collector()->allocated_space_bytes());
EXPECT_EQ(11, IntWrapper::destructor_calls_);
EXPECT_EQ(11, LargeHeapObject::destructor_calls_);
PreciselyCollectGarbage();
}
// This test often fails on Android (https://crbug.com/843032).
// We run out of memory on Android devices because ReserveCapacityForSize
// actually allocates a much larger backing than specified (in this case 400MB).
#if defined(OS_ANDROID)
#define MAYBE_LargeHashMap DISABLED_LargeHashMap
#else
#define MAYBE_LargeHashMap LargeHashMap
#endif
TEST(HeapTest, MAYBE_LargeHashMap) {
ClearOutOldGarbage();
// Try to allocate a HashTable larger than kMaxHeapObjectSize
// (crbug.com/597953).
size_t size = kMaxHeapObjectSize /
sizeof(HeapHashMap<int, Member<IntWrapper>>::ValueType);
Persistent<HeapHashMap<int, Member<IntWrapper>>> map =
new HeapHashMap<int, Member<IntWrapper>>();
map->ReserveCapacityForSize(size);
EXPECT_LE(size, map->Capacity());
}
TEST(HeapTest, LargeVector) {
ClearOutOldGarbage();
// Try to allocate a HeapVectors larger than kMaxHeapObjectSize
// (crbug.com/597953).
size_t size = kMaxHeapObjectSize / sizeof(int);
Persistent<HeapVector<int>> vector = new HeapVector<int>(size);
EXPECT_LE(size, vector->capacity());
}
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> vector_wu;
HeapVector<PairUnwrappedWrapped, 2> vector_uw;
HeapDeque<Member<IntWrapper>, 0> deque;
HeapDeque<PairWrappedUnwrapped, 0> deque_wu;
HeapDeque<PairUnwrappedWrapped, 0> deque_uw;
void Trace(blink::Visitor* visitor) {
visitor->Trace(map);
visitor->Trace(set);
visitor->Trace(set2);
visitor->Trace(set3);
visitor->Trace(vector);
visitor->Trace(vector_wu);
visitor->Trace(vector_uw);
visitor->Trace(deque);
visitor->Trace(deque_wu);
visitor->Trace(deque_uw);
}
};
struct NeedsTracingTrait {
explicit NeedsTracingTrait(IntWrapper* wrapper) : wrapper_(wrapper) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(wrapper_); }
Member<IntWrapper> wrapper_;
};
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.push_back(one);
vector.push_back(two);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
vector.push_back(three);
vector.push_back(four);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
EXPECT_TRUE(vector.Contains(three));
EXPECT_TRUE(vector.Contains(four));
vector.Shrink(1);
ConservativelyCollectGarbage();
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.push_back(one);
vector2.push_back(two);
vector1.swap(vector2);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector1.Contains(two));
EXPECT_TRUE(vector2.Contains(one));
}
{
HeapVector<Member<IntWrapper>, 2> vector1;
HeapVector<Member<IntWrapper>, 2> vector2;
vector1.push_back(one);
vector1.push_back(two);
vector2.push_back(three);
vector2.push_back(four);
vector2.push_back(five);
vector2.push_back(six);
vector1.swap(vector2);
ConservativelyCollectGarbage();
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));
}
}
TEST(HeapTest, HeapVectorShrinkCapacity) {
ClearOutOldGarbage();
HeapVector<Member<IntWrapper>> vector1;
HeapVector<Member<IntWrapper>> vector2;
vector1.ReserveCapacity(96);
EXPECT_LE(96u, vector1.capacity());
vector1.Grow(vector1.capacity());
// Assumes none was allocated just after a vector backing of vector1.
vector1.Shrink(56);
vector1.ShrinkToFit();
EXPECT_GT(96u, vector1.capacity());
vector2.ReserveCapacity(20);
// Assumes another vector backing was allocated just after the vector
// backing of vector1.
vector1.Shrink(10);
vector1.ShrinkToFit();
EXPECT_GT(56u, vector1.capacity());
vector1.Grow(192);
EXPECT_LE(192u, vector1.capacity());
}
TEST(HeapTest, HeapVectorShrinkInlineCapacity) {
ClearOutOldGarbage();
const size_t kInlineCapacity = 64;
HeapVector<Member<IntWrapper>, kInlineCapacity> vector1;
vector1.ReserveCapacity(128);
EXPECT_LE(128u, vector1.capacity());
vector1.Grow(vector1.capacity());
// Shrink the external buffer.
vector1.Shrink(90);
vector1.ShrinkToFit();
EXPECT_GT(128u, vector1.capacity());
// TODO(sof): if the ASan support for 'contiguous containers' is enabled,
// Vector inline buffers are disabled; that constraint should be attempted
// removed, but until that time, disable testing handling of capacities
// of inline buffers.
#if !defined(ANNOTATE_CONTIGUOUS_CONTAINER)
// Shrinking switches the buffer from the external one to the inline one.
vector1.Shrink(kInlineCapacity - 1);
vector1.ShrinkToFit();
EXPECT_EQ(kInlineCapacity, vector1.capacity());
// Try to shrink the inline buffer.
vector1.Shrink(1);
vector1.ShrinkToFit();
EXPECT_EQ(kInlineCapacity, vector1.capacity());
#endif
}
TEST(HeapTest, HeapVectorOnStackLargeObjectPageSized) {
ClearOutOldGarbage();
// Try to allocate a vector of a size that will end exactly where the
// LargeObjectPage ends.
using Container = HeapVector<Member<IntWrapper>>;
Container vector;
size_t size = (kLargeObjectSizeThreshold + kBlinkGuardPageSize -
LargeObjectPage::PageHeaderSize() - sizeof(HeapObjectHeader)) /
sizeof(Container::ValueType);
vector.ReserveCapacity(size);
for (unsigned i = 0; i < size; ++i)
vector.push_back(IntWrapper::Create(i));
ConservativelyCollectGarbage();
}
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) {
IntWrapper::destructor_calls_ = 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> member_member = new MemberMember();
Persistent<MemberMember> member_member2 = new MemberMember();
Persistent<MemberMember> member_member3 = new MemberMember();
Persistent<MemberPrimitive> member_primitive = new MemberPrimitive();
Persistent<PrimitiveMember> primitive_member = 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> vector_wu = new VectorWU();
Persistent<VectorWU> vector_wu2 = new VectorWU();
Persistent<VectorUW> vector_uw = new VectorUW();
Persistent<VectorUW> vector_uw2 = new VectorUW();
Persistent<MemberDeque> deque = new MemberDeque();
Persistent<MemberDeque> deque2 = new MemberDeque();
Persistent<DequeWU> deque_wu = new DequeWU();
Persistent<DequeWU> deque_wu2 = new DequeWU();
Persistent<DequeUW> deque_uw = new DequeUW();
Persistent<DequeUW> deque_uw2 = new DequeUW();
Persistent<Container> container = Container::Create();
ClearOutOldGarbage();
{
Persistent<IntWrapper> one(IntWrapper::Create(1));
Persistent<IntWrapper> two(IntWrapper::Create(2));
Persistent<IntWrapper> one_b(IntWrapper::Create(1));
Persistent<IntWrapper> two_b(IntWrapper::Create(2));
Persistent<IntWrapper> one_c(IntWrapper::Create(1));
Persistent<IntWrapper> one_d(IntWrapper::Create(1));
Persistent<IntWrapper> one_e(IntWrapper::Create(1));
Persistent<IntWrapper> one_f(IntWrapper::Create(1));
{
IntWrapper* three_b(IntWrapper::Create(3));
IntWrapper* three_c(IntWrapper::Create(3));
IntWrapper* three_d(IntWrapper::Create(3));
IntWrapper* three_e(IntWrapper::Create(3));
IntWrapper* three_f(IntWrapper::Create(3));
IntWrapper* three(IntWrapper::Create(3));
IntWrapper* four_b(IntWrapper::Create(4));
IntWrapper* four_c(IntWrapper::Create(4));
IntWrapper* four_d(IntWrapper::Create(4));
IntWrapper* four_e(IntWrapper::Create(4));
IntWrapper* four_f(IntWrapper::Create(4));
IntWrapper* four(IntWrapper::Create(4));
IntWrapper* five_c(IntWrapper::Create(5));
IntWrapper* five_d(IntWrapper::Create(5));
IntWrapper* five_e(IntWrapper::Create(5));
IntWrapper* five_f(IntWrapper::Create(5));
// Member Collections.
member_member2->insert(one, two);
member_member2->insert(two, three);
member_member2->insert(three, four);
member_member2->insert(four, one);
primitive_member->insert(1, two);
primitive_member->insert(2, three);
primitive_member->insert(3, four);
primitive_member->insert(4, one);
member_primitive->insert(one, 2);
member_primitive->insert(two, 3);
member_primitive->insert(three, 4);
member_primitive->insert(four, 1);
set2->insert(one);
set2->insert(two);
set2->insert(three);
set2->insert(four);
set->insert(one_b);
set3->insert(one_b);
set3->insert(one_b);
vector->push_back(one_b);
deque->push_back(one_b);
vector2->push_back(three_b);
vector2->push_back(four_b);
deque2->push_back(three_e);
deque2->push_back(four_e);
vector_wu->push_back(PairWrappedUnwrapped(&*one_c, 42));
deque_wu->push_back(PairWrappedUnwrapped(&*one_e, 42));
vector_wu2->push_back(PairWrappedUnwrapped(&*three_c, 43));
vector_wu2->push_back(PairWrappedUnwrapped(&*four_c, 44));
vector_wu2->push_back(PairWrappedUnwrapped(&*five_c, 45));
deque_wu2->push_back(PairWrappedUnwrapped(&*three_e, 43));
deque_wu2->push_back(PairWrappedUnwrapped(&*four_e, 44));
deque_wu2->push_back(PairWrappedUnwrapped(&*five_e, 45));
vector_uw->push_back(PairUnwrappedWrapped(1, &*one_d));
vector_uw2->push_back(PairUnwrappedWrapped(103, &*three_d));
vector_uw2->push_back(PairUnwrappedWrapped(104, &*four_d));
vector_uw2->push_back(PairUnwrappedWrapped(105, &*five_d));
deque_uw->push_back(PairUnwrappedWrapped(1, &*one_f));
deque_uw2->push_back(PairUnwrappedWrapped(103, &*three_f));
deque_uw2->push_back(PairUnwrappedWrapped(104, &*four_f));
deque_uw2->push_back(PairUnwrappedWrapped(105, &*five_f));
EXPECT_TRUE(DequeContains(*deque, one_b));
// Collect garbage. This should change nothing since we are keeping
// alive the IntWrapper objects with on-stack pointers.
ConservativelyCollectGarbage();
EXPECT_TRUE(DequeContains(*deque, one_b));
EXPECT_EQ(0u, member_member->size());
EXPECT_EQ(4u, member_member2->size());
EXPECT_EQ(4u, primitive_member->size());
EXPECT_EQ(4u, member_primitive->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, vector_wu->size());
EXPECT_EQ(3u, vector_wu2->size());
EXPECT_EQ(1u, vector_uw->size());
EXPECT_EQ(3u, vector_uw2->size());
EXPECT_EQ(1u, deque->size());
EXPECT_EQ(2u, deque2->size());
EXPECT_EQ(1u, deque_wu->size());
EXPECT_EQ(3u, deque_wu2->size());
EXPECT_EQ(1u, deque_uw->size());
EXPECT_EQ(3u, deque_uw2->size());
MemberVector& cvec = container->vector;
cvec.swap(*vector.Get());
vector2->swap(cvec);
vector->swap(cvec);
VectorWU& cvec_wu = container->vector_wu;
cvec_wu.swap(*vector_wu.Get());
vector_wu2->swap(cvec_wu);
vector_wu->swap(cvec_wu);
VectorUW& cvec_uw = container->vector_uw;
cvec_uw.swap(*vector_uw.Get());
vector_uw2->swap(cvec_uw);
vector_uw->swap(cvec_uw);
MemberDeque& c_deque = container->deque;
c_deque.Swap(*deque.Get());
deque2->Swap(c_deque);
deque->Swap(c_deque);
DequeWU& c_deque_wu = container->deque_wu;
c_deque_wu.Swap(*deque_wu.Get());
deque_wu2->Swap(c_deque_wu);
deque_wu->Swap(c_deque_wu);
DequeUW& c_deque_uw = container->deque_uw;
c_deque_uw.Swap(*deque_uw.Get());
deque_uw2->Swap(c_deque_uw);
deque_uw->Swap(c_deque_uw);
// 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& c_counted_set = container->set3;
set3->swap(c_counted_set);
EXPECT_EQ(0u, set3->size());
set3->swap(c_counted_set);
// Triple swap.
container->map.swap(*member_member2);
MemberMember& contained_map = container->map;
member_member3->swap(contained_map);
member_member3->swap(*member_member);
EXPECT_TRUE(member_member->at(one) == two);
EXPECT_TRUE(member_member->at(two) == three);
EXPECT_TRUE(member_member->at(three) == four);
EXPECT_TRUE(member_member->at(four) == one);
EXPECT_TRUE(primitive_member->at(1) == two);
EXPECT_TRUE(primitive_member->at(2) == three);
EXPECT_TRUE(primitive_member->at(3) == four);
EXPECT_TRUE(primitive_member->at(4) == one);
EXPECT_EQ(1, member_primitive->at(four));
EXPECT_EQ(2, member_primitive->at(one));
EXPECT_EQ(3, member_primitive->at(two));
EXPECT_EQ(4, member_primitive->at(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(one_b));
EXPECT_TRUE(set3->Contains(one_b));
EXPECT_TRUE(vector->Contains(three_b));
EXPECT_TRUE(vector->Contains(four_b));
EXPECT_TRUE(DequeContains(*deque, three_e));
EXPECT_TRUE(DequeContains(*deque, four_e));
EXPECT_TRUE(vector2->Contains(one_b));
EXPECT_FALSE(vector2->Contains(three_b));
EXPECT_TRUE(DequeContains(*deque2, one_b));
EXPECT_FALSE(DequeContains(*deque2, three_e));
EXPECT_TRUE(vector_wu->Contains(PairWrappedUnwrapped(&*three_c, 43)));
EXPECT_TRUE(vector_wu->Contains(PairWrappedUnwrapped(&*four_c, 44)));
EXPECT_TRUE(vector_wu->Contains(PairWrappedUnwrapped(&*five_c, 45)));
EXPECT_TRUE(vector_wu2->Contains(PairWrappedUnwrapped(&*one_c, 42)));
EXPECT_FALSE(vector_wu2->Contains(PairWrappedUnwrapped(&*three_c, 43)));
EXPECT_TRUE(vector_uw->Contains(PairUnwrappedWrapped(103, &*three_d)));
EXPECT_TRUE(vector_uw->Contains(PairUnwrappedWrapped(104, &*four_d)));
EXPECT_TRUE(vector_uw->Contains(PairUnwrappedWrapped(105, &*five_d)));
EXPECT_TRUE(vector_uw2->Contains(PairUnwrappedWrapped(1, &*one_d)));
EXPECT_FALSE(vector_uw2->Contains(PairUnwrappedWrapped(103, &*three_d)));
EXPECT_TRUE(
DequeContains(*deque_wu, PairWrappedUnwrapped(&*three_e, 43)));
EXPECT_TRUE(DequeContains(*deque_wu, PairWrappedUnwrapped(&*four_e, 44)));
EXPECT_TRUE(DequeContains(*deque_wu, PairWrappedUnwrapped(&*five_e, 45)));
EXPECT_TRUE(DequeContains(*deque_wu2, PairWrappedUnwrapped(&*one_e, 42)));
EXPECT_FALSE(
DequeContains(*deque_wu2, PairWrappedUnwrapped(&*three_e, 43)));
EXPECT_TRUE(
DequeContains(*deque_uw, PairUnwrappedWrapped(103, &*three_f)));
EXPECT_TRUE(
DequeContains(*deque_uw, PairUnwrappedWrapped(104, &*four_f)));
EXPECT_TRUE(
DequeContains(*deque_uw, PairUnwrappedWrapped(105, &*five_f)));
EXPECT_TRUE(DequeContains(*deque_uw2, PairUnwrappedWrapped(1, &*one_f)));
EXPECT_FALSE(
DequeContains(*deque_uw2, PairUnwrappedWrapped(103, &*three_f)));
}
PreciselyCollectGarbage();
EXPECT_EQ(4u, member_member->size());
EXPECT_EQ(0u, member_member2->size());
EXPECT_EQ(4u, primitive_member->size());
EXPECT_EQ(4u, member_primitive->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, vector_uw->size());
EXPECT_EQ(1u, vector2->size());
EXPECT_EQ(2u, deque->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_EQ(3u, deque_uw->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_TRUE(member_member->at(one) == two);
EXPECT_TRUE(primitive_member->at(1) == two);
EXPECT_TRUE(primitive_member->at(4) == one);
EXPECT_EQ(2, member_primitive->at(one));
EXPECT_EQ(3, member_primitive->at(two));
EXPECT_TRUE(set->Contains(one));
EXPECT_TRUE(set->Contains(two));
EXPECT_FALSE(set->Contains(one_b));
EXPECT_TRUE(set2->Contains(one_b));
EXPECT_TRUE(set3->Contains(one_b));
EXPECT_EQ(2u, set3->find(one_b)->value);
EXPECT_EQ(3, vector->at(0)->Value());
EXPECT_EQ(4, vector->at(1)->Value());
EXPECT_EQ(3, deque->begin()->Get()->Value());
}
PreciselyCollectGarbage();
PreciselyCollectGarbage();
EXPECT_EQ(4u, member_member->size());
EXPECT_EQ(4u, primitive_member->size());
EXPECT_EQ(4u, member_primitive->size());
EXPECT_EQ(4u, set->size());
EXPECT_EQ(1u, set2->size());
EXPECT_EQ(2u, vector->size());
EXPECT_EQ(1u, vector2->size());
EXPECT_EQ(3u, vector_wu->size());
EXPECT_EQ(1u, vector_wu2->size());
EXPECT_EQ(3u, vector_uw->size());
EXPECT_EQ(1u, vector_uw2->size());
EXPECT_EQ(2u, deque->size());
EXPECT_EQ(1u, deque2->size());
EXPECT_EQ(3u, deque_wu->size());
EXPECT_EQ(1u, deque_wu2->size());
EXPECT_EQ(3u, deque_uw->size());
EXPECT_EQ(1u, deque_uw2->size());
}
TEST(HeapTest, PersistentVector) {
IntWrapper::destructor_calls_ = 0;
typedef Vector<Persistent<IntWrapper>> PersistentVector;
Persistent<IntWrapper> one(IntWrapper::Create(1));
Persistent<IntWrapper> two(IntWrapper::Create(2));
Persistent<IntWrapper> three(IntWrapper::Create(3));
Persistent<IntWrapper> four(IntWrapper::Create(4));
Persistent<IntWrapper> five(IntWrapper::Create(5));
Persistent<IntWrapper> six(IntWrapper::Create(6));
{
PersistentVector vector;
vector.push_back(one);
vector.push_back(two);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
vector.push_back(three);
vector.push_back(four);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
EXPECT_TRUE(vector.Contains(three));
EXPECT_TRUE(vector.Contains(four));
vector.Shrink(1);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_FALSE(vector.Contains(two));
EXPECT_FALSE(vector.Contains(three));
EXPECT_FALSE(vector.Contains(four));
}
{
PersistentVector vector1;
PersistentVector vector2;
vector1.push_back(one);
vector2.push_back(two);
vector1.swap(vector2);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector1.Contains(two));
EXPECT_TRUE(vector2.Contains(one));
}
{
PersistentVector vector1;
PersistentVector vector2;
vector1.push_back(one);
vector1.push_back(two);
vector2.push_back(three);
vector2.push_back(four);
vector2.push_back(five);
vector2.push_back(six);
vector1.swap(vector2);
ConservativelyCollectGarbage();
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));
}
}
TEST(HeapTest, CrossThreadPersistentVector) {
IntWrapper::destructor_calls_ = 0;
typedef Vector<CrossThreadPersistent<IntWrapper>> CrossThreadPersistentVector;
CrossThreadPersistent<IntWrapper> one(IntWrapper::Create(1));
CrossThreadPersistent<IntWrapper> two(IntWrapper::Create(2));
CrossThreadPersistent<IntWrapper> three(IntWrapper::Create(3));
CrossThreadPersistent<IntWrapper> four(IntWrapper::Create(4));
CrossThreadPersistent<IntWrapper> five(IntWrapper::Create(5));
CrossThreadPersistent<IntWrapper> six(IntWrapper::Create(6));
{
CrossThreadPersistentVector vector;
vector.push_back(one);
vector.push_back(two);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
vector.push_back(three);
vector.push_back(four);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_TRUE(vector.Contains(two));
EXPECT_TRUE(vector.Contains(three));
EXPECT_TRUE(vector.Contains(four));
vector.Shrink(1);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector.Contains(one));
EXPECT_FALSE(vector.Contains(two));
EXPECT_FALSE(vector.Contains(three));
EXPECT_FALSE(vector.Contains(four));
}
{
CrossThreadPersistentVector vector1;
CrossThreadPersistentVector vector2;
vector1.push_back(one);
vector2.push_back(two);
vector1.swap(vector2);
ConservativelyCollectGarbage();
EXPECT_TRUE(vector1.Contains(two));
EXPECT_TRUE(vector2.Contains(one));
}
{
CrossThreadPersistentVector vector1;
CrossThreadPersistentVector vector2;
vector1.push_back(one);
vector1.push_back(two);
vector2.push_back(three);
vector2.push_back(four);
vector2.push_back(five);
vector2.push_back(six);
vector1.swap(vector2);
ConservativelyCollectGarbage();
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));
}
}
TEST(HeapTest, PersistentSet) {
IntWrapper::destructor_calls_ = 0;
typedef HashSet<Persistent<IntWrapper>> PersistentSet;
IntWrapper* one_raw = IntWrapper::Create(1);
Persistent<IntWrapper> one(one_raw);
Persistent<IntWrapper> one2(one_raw);
Persistent<IntWrapper> two(IntWrapper::Create(2));
Persistent<IntWrapper> three(IntWrapper::Create(3));
Persistent<IntWrapper> four(IntWrapper::Create(4));
Persistent<IntWrapper> five(IntWrapper::Create(5));
Persistent<IntWrapper> six(IntWrapper::Create(6));
{
PersistentSet set;
set.insert(one);
set.insert(two);
ConservativelyCollectGarbage();
EXPECT_TRUE(set.Contains(one));
EXPECT_TRUE(set.Contains(one2));
EXPECT_TRUE(set.Contains(two));
set.insert(three);
set.insert(four);
ConservativelyCollectGarbage();
EXPECT_TRUE(set.Contains(one));
EXPECT_TRUE(set.Contains(two));
EXPECT_TRUE(set.Contains(three));
EXPECT_TRUE(set.Contains(four));
set.clear();
ConservativelyCollectGarbage();
EXPECT_FALSE(set.Contains(one));
EXPECT_FALSE(set.Contains(two));
EXPECT_FALSE(set.Contains(three));
EXPECT_FALSE(set.Contains(four));
}
{
PersistentSet set1;
PersistentSet set2;
set1.insert(one);
set2.insert(two);
set1.swap(set2);
ConservativelyCollectGarbage();
EXPECT_TRUE(set1.Contains(two));
EXPECT_TRUE(set2.Contains(one));
EXPECT_TRUE(set2.Contains(one2));
}
}
TEST(HeapTest, CrossThreadPersistentSet) {
IntWrapper::destructor_calls_ = 0;
typedef HashSet<CrossThreadPersistent<IntWrapper>> CrossThreadPersistentSet;
IntWrapper* one_raw = IntWrapper::Create(1);
CrossThreadPersistent<IntWrapper> one(one_raw);
CrossThreadPersistent<IntWrapper> one2(one_raw);
CrossThreadPersistent<IntWrapper> two(IntWrapper::Create(2));
CrossThreadPersistent<IntWrapper> three(IntWrapper::Create(3));
CrossThreadPersistent<IntWrapper> four(IntWrapper::Create(4));
CrossThreadPersistent<IntWrapper> five(IntWrapper::Create(5));
CrossThreadPersistent<IntWrapper> six(IntWrapper::Create(6));
{
CrossThreadPersistentSet set;
set.insert(one);
set.insert(two);
ConservativelyCollectGarbage();
EXPECT_TRUE(set.Contains(one));
EXPECT_TRUE(set.Contains(one2));
EXPECT_TRUE(set.Contains(two));
set.insert(three);
set.insert(four);
ConservativelyCollectGarbage();
EXPECT_TRUE(set.Contains(one));
EXPECT_TRUE(set.Contains(two));
EXPECT_TRUE(set.Contains(three));
EXPECT_TRUE(set.Contains(four));
set.clear();
ConservativelyCollectGarbage();
EXPECT_FALSE(set.Contains(one));
EXPECT_FALSE(set.Contains(two));
EXPECT_FALSE(set.Contains(three));
EXPECT_FALSE(set.Contains(four));
}
{
CrossThreadPersistentSet set1;
CrossThreadPersistentSet set2;
set1.insert(one);
set2.insert(two);
set1.swap(set2);
ConservativelyCollectGarbage();
EXPECT_TRUE(set1.Contains(two));
EXPECT_TRUE(set2.Contains(one));
EXPECT_TRUE(set2.Contains(one2));
}
}
class NonTrivialObject final {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
NonTrivialObject() = default;
explicit NonTrivialObject(int num) {
deque_.push_back(IntWrapper::Create(num));
vector_.push_back(IntWrapper::Create(num));
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(deque_);
visitor->Trace(vector_);
}
private:
HeapDeque<Member<IntWrapper>> deque_;
HeapVector<Member<IntWrapper>> vector_;
};
TEST(HeapTest, HeapHashMapWithInlinedObject) {
HeapHashMap<int, NonTrivialObject> map;
for (int num = 1; num < 1000; num++) {
NonTrivialObject object(num);
map.insert(num, object);
}
}
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) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
PersistentHeapVector<Member<IntWrapper>> keep_numbers_alive;
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> weak_strong = new WeakStrong();
Persistent<StrongWeak> strong_weak = new StrongWeak();
Persistent<WeakWeak> weak_weak = new WeakWeak();
Persistent<WeakSet> weak_set = new WeakSet();
Persistent<WeakCountedSet> weak_counted_set = new WeakCountedSet();
Persistent<IntWrapper> two = IntWrapper::Create(2);
keep_numbers_alive.push_back(IntWrapper::Create(103));
keep_numbers_alive.push_back(IntWrapper::Create(10));
{
weak_strong->insert(IntWrapper::Create(1), two);
strong_weak->insert(two, IntWrapper::Create(1));
weak_weak->insert(two, IntWrapper::Create(42));
weak_weak->insert(IntWrapper::Create(42), two);
weak_set->insert(IntWrapper::Create(0));
weak_set->insert(two);
weak_set->insert(keep_numbers_alive[0]);
weak_set->insert(keep_numbers_alive[1]);
weak_counted_set->insert(IntWrapper::Create(0));
weak_counted_set->insert(two);
weak_counted_set->insert(two);
weak_counted_set->insert(two);
weak_counted_set->insert(keep_numbers_alive[0]);
weak_counted_set->insert(keep_numbers_alive[1]);
EXPECT_EQ(1u, weak_strong->size());
EXPECT_EQ(1u, strong_weak->size());
EXPECT_EQ(2u, weak_weak->size());
EXPECT_EQ(4u, weak_set->size());
EXPECT_EQ(4u, weak_counted_set->size());
EXPECT_EQ(3u, weak_counted_set->find(two)->value);
weak_counted_set->erase(two);
EXPECT_EQ(2u, weak_counted_set->find(two)->value);
}
keep_numbers_alive[0] = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(0u, weak_strong->size());
EXPECT_EQ(0u, strong_weak->size());
EXPECT_EQ(0u, weak_weak->size());
EXPECT_EQ(2u, weak_set->size());
EXPECT_EQ(2u, weak_counted_set->size());
}
template <typename Set>
void OrderedSetHelper(bool strong) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
PersistentHeapVector<Member<IntWrapper>> keep_numbers_alive;
Persistent<Set> set1 = new Set();
Persistent<Set> set2 = new Set();
const Set& const_set = *set1.Get();
keep_numbers_alive.push_back(IntWrapper::Create(2));
keep_numbers_alive.push_back(IntWrapper::Create(103));
keep_numbers_alive.push_back(IntWrapper::Create(10));
set1->insert(IntWrapper::Create(0));
set1->insert(keep_numbers_alive[0]);
set1->insert(keep_numbers_alive[1]);
set1->insert(keep_numbers_alive[2]);
set2->clear();
set2->insert(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(const_set.begin());
typename Set::const_reverse_iterator creverse(const_set.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(const_set.end(), cit);
EXPECT_EQ(set1->rend(), reverse);
EXPECT_EQ(const_set.rend(), creverse);
typename Set::iterator i_x(set2->begin());
EXPECT_EQ(set2->end(), i_x);
if (strong)
set1->erase(keep_numbers_alive[0]);
keep_numbers_alive[0] = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(2u + (strong ? 1u : 0u), set1->size());
EXPECT_EQ(2 + (strong ? 0 : 1), IntWrapper::destructor_calls_);
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() : x_(kEmptyValue) { live_things_with_destructor_++; }
ThingWithDestructor(int x) : x_(x) { live_things_with_destructor_++; }
ThingWithDestructor(const ThingWithDestructor& other) {
*this = other;
live_things_with_destructor_++;
}
~ThingWithDestructor() { live_things_with_destructor_--; }
int Value() { return x_; }
static int live_things_with_destructor_;
unsigned GetHash() { return IntHash<int>::GetHash(x_); }
private:
static const int kEmptyValue = 0;
int x_;
};
int ThingWithDestructor::live_things_with_destructor_;
static void HeapMapDestructorHelper(bool clear_maps) {
ClearOutOldGarbage();
ThingWithDestructor::live_things_with_destructor_ = 0;
typedef HeapHashMap<WeakMember<IntWrapper>,
Member<RefCountedAndGarbageCollected>>
RefMap;
typedef HeapHashMap<WeakMember<IntWrapper>, ThingWithDestructor,
DefaultHash<WeakMember<IntWrapper>>::Hash,
HashTraits<WeakMember<IntWrapper>>>
Map;
Persistent<Map> map(new Map());
Persistent<RefMap> ref_map(new RefMap());
Persistent<IntWrapper> luck(IntWrapper::Create(103));
int base_line, ref_base_line;
{
Map stack_map;
RefMap stack_ref_map;
PreciselyCollectGarbage();
PreciselyCollectGarbage();
stack_map.insert(IntWrapper::Create(42), ThingWithDestructor(1729));
stack_map.insert(luck, ThingWithDestructor(8128));
stack_ref_map.insert(IntWrapper::Create(42),
RefCountedAndGarbageCollected::Create());
stack_ref_map.insert(luck, RefCountedAndGarbageCollected::Create());
base_line = ThingWithDestructor::live_things_with_destructor_;
ref_base_line = RefCountedAndGarbageCollected::destructor_calls_;
// 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.
PreciselyCollectGarbage();
PreciselyCollectGarbage();
EXPECT_EQ(base_line - 2, ThingWithDestructor::live_things_with_destructor_);
EXPECT_EQ(ref_base_line + 2,
RefCountedAndGarbageCollected::destructor_calls_);
// Now use maps kept alive with persistents. Here we don't expect any
// destructors to be called before there have been GCs.
map->insert(IntWrapper::Create(42), ThingWithDestructor(1729));
map->insert(luck, ThingWithDestructor(8128));
ref_map->insert(IntWrapper::Create(42),
RefCountedAndGarbageCollected::Create());
ref_map->insert(luck, RefCountedAndGarbageCollected::Create());
base_line = ThingWithDestructor::live_things_with_destructor_;
ref_base_line = RefCountedAndGarbageCollected::destructor_calls_;
luck.Clear();
if (clear_maps) {
map->clear(); // Clear map.
ref_map->clear(); // Clear map.
} else {
map.Clear(); // Clear Persistent handle, not map.
ref_map.Clear(); // Clear Persistent handle, not map.
PreciselyCollectGarbage();
PreciselyCollectGarbage();
}
EXPECT_EQ(base_line - 2, ThingWithDestructor::live_things_with_destructor_);
// Need a GC to make sure that the RefCountedAndGarbageCollected thing
// noticies it's been decremented to zero.
PreciselyCollectGarbage();
EXPECT_EQ(ref_base_line + 2,
RefCountedAndGarbageCollected::destructor_calls_);
}
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& not_a_pair) {
return not_a_pair;
}
template <typename WSSet, typename SWSet, typename WUSet, typename UWSet>
void CheckPairSets(Persistent<WSSet>& weak_strong,
Persistent<SWSet>& strong_weak,
Persistent<WUSet>& weak_unwrapped,
Persistent<UWSet>& unwrapped_weak,
bool ones,
Persistent<IntWrapper>& two) {
typename WSSet::iterator it_ws = weak_strong->begin();
typename SWSet::iterator it_sw = strong_weak->begin();
typename WUSet::iterator it_wu = weak_unwrapped->begin();
typename UWSet::iterator it_uw = unwrapped_weak->begin();
EXPECT_EQ(2u, weak_strong->size());
EXPECT_EQ(2u, strong_weak->size());
EXPECT_EQ(2u, weak_unwrapped->size());
EXPECT_EQ(2u, unwrapped_weak->size());
PairWeakStrong p = IteratorExtractor(*it_ws);
PairStrongWeak p2 = IteratorExtractor(*it_sw);
PairWeakUnwrapped p3 = IteratorExtractor(*it_wu);
PairUnwrappedWeak p4 = IteratorExtractor(*it_uw);
if (p.first == two && p.second == two)
++it_ws;
if (p2.first == two && p2.second == two)
++it_sw;
if (p3.first == two && p3.second == 2)
++it_wu;
if (p4.first == 2 && p4.second == two)
++it_uw;
p = IteratorExtractor(*it_ws);
p2 = IteratorExtractor(*it_sw);
p3 = IteratorExtractor(*it_wu);
p4 = IteratorExtractor(*it_uw);
IntWrapper* null_wrapper = nullptr;
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, null_wrapper);
EXPECT_EQ(p2.second, null_wrapper);
EXPECT_EQ(p3.first, null_wrapper);
EXPECT_EQ(p4.second, null_wrapper);
}
EXPECT_EQ(p.second->Value(), 2);
EXPECT_EQ(p2.first->Value(), 2);
EXPECT_EQ(p3.second, 2);
EXPECT_EQ(p4.first, 2);
EXPECT_TRUE(weak_strong->Contains(PairWeakStrong(&*two, &*two)));
EXPECT_TRUE(strong_weak->Contains(PairStrongWeak(&*two, &*two)));
EXPECT_TRUE(weak_unwrapped->Contains(PairWeakUnwrapped(&*two, 2)));
EXPECT_TRUE(unwrapped_weak->Contains(PairUnwrappedWeak(2, &*two)));
}
template <typename WSSet, typename SWSet, typename WUSet, typename UWSet>
void WeakPairsHelper() {
IntWrapper::destructor_calls_ = 0;
PersistentHeapVector<Member<IntWrapper>> keep_numbers_alive;
Persistent<WSSet> weak_strong = new WSSet();
Persistent<SWSet> strong_weak = new SWSet();
Persistent<WUSet> weak_unwrapped = new WUSet();
Persistent<UWSet> unwrapped_weak = new UWSet();
Persistent<IntWrapper> two = IntWrapper::Create(2);
weak_strong->insert(PairWeakStrong(IntWrapper::Create(1), &*two));
weak_strong->insert(PairWeakStrong(&*two, &*two));
strong_weak->insert(PairStrongWeak(&*two, IntWrapper::Create(1)));
strong_weak->insert(PairStrongWeak(&*two, &*two));
weak_unwrapped->insert(PairWeakUnwrapped(IntWrapper::Create(1), 2));
weak_unwrapped->insert(PairWeakUnwrapped(&*two, 2));
unwrapped_weak->insert(PairUnwrappedWeak(2, IntWrapper::Create(1)));
unwrapped_weak->insert(PairUnwrappedWeak(2, &*two));
CheckPairSets<WSSet, SWSet, WUSet, UWSet>(
weak_strong, strong_weak, weak_unwrapped, unwrapped_weak, true, two);
PreciselyCollectGarbage();
CheckPairSets<WSSet, SWSet, WUSet, UWSet>(
weak_strong, strong_weak, weak_unwrapped, unwrapped_weak, 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) {
IntWrapper::destructor_calls_ = 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();
const int kWeakStrongIndex = 0;
const int kStrongWeakIndex = 1;
const int kWeakWeakIndex = 2;
const int kNumberOfMapIndices = 3;
const int kWeakSetIndex = 3;
const int kWeakOrderedSetIndex = 4;
const int kNumberOfCollections = 5;
for (int test_run = 0; test_run < 4; test_run++) {
for (int collection_number = 0; collection_number < kNumberOfCollections;
collection_number++) {
bool delete_afterwards = (test_run == 1);
bool add_afterwards = (test_run == 2);
bool test_that_iterators_make_strong = (test_run == 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 (delete_afterwards && collection_number == kStrongWeakIndex)
continue;
unsigned added = add_afterwards ? 100 : 0;
Persistent<WeakStrong> weak_strong = new WeakStrong();
Persistent<StrongWeak> strong_weak = new StrongWeak();
Persistent<WeakWeak> weak_weak = new WeakWeak();
Persistent<WeakSet> weak_set = new WeakSet();
Persistent<WeakOrderedSet> weak_ordered_set = new WeakOrderedSet();
PersistentHeapVector<Member<IntWrapper>> keep_numbers_alive;
for (int i = 0; i < 128; i += 2) {
IntWrapper* wrapped = IntWrapper::Create(i);
IntWrapper* wrapped2 = IntWrapper::Create(i + 1);
keep_numbers_alive.push_back(wrapped);
keep_numbers_alive.push_back(wrapped2);
weak_strong->insert(wrapped, wrapped2);
strong_weak->insert(wrapped2, wrapped);
weak_weak->insert(wrapped, wrapped2);
weak_set->insert(wrapped);
weak_ordered_set->insert(wrapped);
}
EXPECT_EQ(64u, weak_strong->size());
EXPECT_EQ(64u, strong_weak->size());
EXPECT_EQ(64u, weak_weak->size());
EXPECT_EQ(64u, weak_set->size());
EXPECT_EQ(64u, weak_ordered_set->size());
// Collect garbage. This should change nothing since we are keeping
// alive the IntWrapper objects.
PreciselyCollectGarbage();
EXPECT_EQ(64u, weak_strong->size());
EXPECT_EQ(64u, strong_weak->size());
EXPECT_EQ(64u, weak_weak->size());
EXPECT_EQ(64u, weak_set->size());
EXPECT_EQ(64u, weak_ordered_set->size());
for (int i = 0; i < 128; i += 2) {
IntWrapper* wrapped = keep_numbers_alive[i];
IntWrapper* wrapped2 = keep_numbers_alive[i + 1];
EXPECT_EQ(wrapped2, weak_strong->at(wrapped));
EXPECT_EQ(wrapped, strong_weak->at(wrapped2));
EXPECT_EQ(wrapped2, weak_weak->at(wrapped));
EXPECT_TRUE(weak_set->Contains(wrapped));
EXPECT_TRUE(weak_ordered_set->Contains(wrapped));
}
for (int i = 0; i < 128; i += 3)
keep_numbers_alive[i] = nullptr;
if (collection_number != kWeakStrongIndex)
weak_strong->clear();
if (collection_number != kStrongWeakIndex)
strong_weak->clear();
if (collection_number != kWeakWeakIndex)
weak_weak->clear();
if (collection_number != kWeakSetIndex)
weak_set->clear();
if (collection_number != kWeakOrderedSetIndex)
weak_ordered_set->clear();
if (test_that_iterators_make_strong) {
WeakStrong::iterator it1 = weak_strong->begin();
StrongWeak::iterator it2 = strong_weak->begin();
WeakWeak::iterator it3 = weak_weak->begin();
WeakSet::iterator it4 = weak_set->begin();
WeakOrderedSet::iterator it5 = weak_ordered_set->begin();
// Collect garbage. This should change nothing since the
// iterators make the collections strong.
ConservativelyCollectGarbage();
if (collection_number == kWeakStrongIndex) {
EXPECT_EQ(64u, weak_strong->size());
MapIteratorCheck(it1, weak_strong->end(), 64);
} else if (collection_number == kStrongWeakIndex) {
EXPECT_EQ(64u, strong_weak->size());
MapIteratorCheck(it2, strong_weak->end(), 64);
} else if (collection_number == kWeakWeakIndex) {
EXPECT_EQ(64u, weak_weak->size());
MapIteratorCheck(it3, weak_weak->end(), 64);
} else if (collection_number == kWeakSetIndex) {
EXPECT_EQ(64u, weak_set->size());
SetIteratorCheck(it4, weak_set->end(), 64);
} else if (collection_number == kWeakOrderedSetIndex) {
EXPECT_EQ(64u, weak_ordered_set->size());
SetIteratorCheck(it5, weak_ordered_set->end(), 64);
}
} else {
// Collect garbage. This causes weak processing to remove
// things from the collections.
PreciselyCollectGarbage();
unsigned count = 0;
for (int i = 0; i < 128; i += 2) {
bool first_alive = keep_numbers_alive[i];
bool second_alive = keep_numbers_alive[i + 1];
if (first_alive && (collection_number == kWeakStrongIndex ||
collection_number == kStrongWeakIndex))
second_alive = true;
if (first_alive && second_alive &&
collection_number < kNumberOfMapIndices) {
if (collection_number == kWeakStrongIndex) {
if (delete_afterwards)
EXPECT_EQ(i + 1,
weak_strong->Take(keep_numbers_alive[i])->Value());
} else if (collection_number == kStrongWeakIndex) {
if (delete_afterwards)
EXPECT_EQ(
i, strong_weak->Take(keep_numbers_alive[i + 1])->Value());
} else if (collection_number == kWeakWeakIndex) {
if (delete_afterwards)
EXPECT_EQ(i + 1,
weak_weak->Take(keep_numbers_alive[i])->Value());
}
if (!delete_afterwards)
count++;
} else if (collection_number == kWeakSetIndex && first_alive) {
ASSERT_TRUE(weak_set->Contains(keep_numbers_alive[i]));
if (delete_afterwards)
weak_set->erase(keep_numbers_alive[i]);
else
count++;
} else if (collection_number == kWeakOrderedSetIndex && first_alive) {
ASSERT_TRUE(weak_ordered_set->Contains(keep_numbers_alive[i]));
if (delete_afterwards)
weak_ordered_set->erase(keep_numbers_alive[i]);
else
count++;
}
}
if (add_afterwards) {
for (int i = 1000; i < 1100; i++) {
IntWrapper* wrapped = IntWrapper::Create(i);
keep_numbers_alive.push_back(wrapped);
weak_strong->insert(wrapped, wrapped);
strong_weak->insert(wrapped, wrapped);
weak_weak->insert(wrapped, wrapped);
weak_set->insert(wrapped);
weak_ordered_set->insert(wrapped);
}
}
if (collection_number == kWeakStrongIndex)
EXPECT_EQ(count + added, weak_strong->size());
else if (collection_number == kStrongWeakIndex)
EXPECT_EQ(count + added, strong_weak->size());
else if (collection_number == kWeakWeakIndex)
EXPECT_EQ(count + added, weak_weak->size());
else if (collection_number == kWeakSetIndex)
EXPECT_EQ(count + added, weak_set->size());
else if (collection_number == kWeakOrderedSetIndex)
EXPECT_EQ(count + added, weak_ordered_set->size());
WeakStrong::iterator it1 = weak_strong->begin();
StrongWeak::iterator it2 = strong_weak->begin();
WeakWeak::iterator it3 = weak_weak->begin();
WeakSet::iterator it4 = weak_set->begin();
WeakOrderedSet::iterator it5 = weak_ordered_set->begin();
MapIteratorCheck(
it1, weak_strong->end(),
(collection_number == kWeakStrongIndex ? count : 0) + added);
MapIteratorCheck(
it2, strong_weak->end(),
(collection_number == kStrongWeakIndex ? count : 0) + added);
MapIteratorCheck(
it3, weak_weak->end(),
(collection_number == kWeakWeakIndex ? count : 0) + added);
SetIteratorCheck(
it4, weak_set->end(),
(collection_number == kWeakSetIndex ? count : 0) + added);
SetIteratorCheck(
it5, weak_ordered_set->end(),
(collection_number == kWeakOrderedSetIndex ? count : 0) + added);
}
for (unsigned i = 0; i < 128 + added; i++)
keep_numbers_alive[i] = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(0u, weak_strong->size());
EXPECT_EQ(0u, strong_weak->size());
EXPECT_EQ(0u, weak_weak->size());
EXPECT_EQ(0u, weak_set->size());
EXPECT_EQ(0u, weak_ordered_set->size());
}
}
}
TEST(HeapTest, HeapHashCountedSetToVector) {
HeapHashCountedSet<Member<IntWrapper>> set;
HeapVector<Member<IntWrapper>> vector;
set.insert(new IntWrapper(1));
set.insert(new IntWrapper(1));
set.insert(new IntWrapper(2));
CopyToVector(set, vector);
EXPECT_EQ(3u, vector.size());
Vector<int> int_vector;
for (const auto& i : vector)
int_vector.push_back(i->Value());
std::sort(int_vector.begin(), int_vector.end());
ASSERT_EQ(3u, int_vector.size());
EXPECT_EQ(1, int_vector[0]);
EXPECT_EQ(1, int_vector[1]);
EXPECT_EQ(2, int_vector[2]);
}
TEST(HeapTest, WeakHeapHashCountedSetToVector) {
HeapHashCountedSet<WeakMember<IntWrapper>> set;
HeapVector<Member<IntWrapper>> vector;
set.insert(new IntWrapper(1));
set.insert(new IntWrapper(1));
set.insert(new IntWrapper(2));
CopyToVector(set, vector);
EXPECT_LE(3u, vector.size());
for (const auto& i : vector)
EXPECT_TRUE(i->Value() == 1 || i->Value() == 2);
}
TEST(HeapTest, RefCountedGarbageCollected) {
RefCountedAndGarbageCollected::destructor_calls_ = 0;
{
scoped_refptr<RefCountedAndGarbageCollected> ref_ptr3;
{
Persistent<RefCountedAndGarbageCollected> persistent;
{
Persistent<RefCountedAndGarbageCollected> ref_ptr1 =
RefCountedAndGarbageCollected::Create();
Persistent<RefCountedAndGarbageCollected> ref_ptr2 =
RefCountedAndGarbageCollected::Create();
PreciselyCollectGarbage();
EXPECT_EQ(0, RefCountedAndGarbageCollected::destructor_calls_);
persistent = ref_ptr1.Get();
}
// Reference count is zero for both objects but one of
// them is kept alive by a persistent handle.
PreciselyCollectGarbage();
EXPECT_EQ(1, RefCountedAndGarbageCollected::destructor_calls_);
ref_ptr3 = persistent.Get();
}
// The persistent handle is gone but the ref count has been
// increased to 1.
PreciselyCollectGarbage();
EXPECT_EQ(1, RefCountedAndGarbageCollected::destructor_calls_);
}
// Both persistent handle is gone and ref count is zero so the
// object can be collected.
PreciselyCollectGarbage();
EXPECT_EQ(2, RefCountedAndGarbageCollected::destructor_calls_);
}
TEST(HeapTest, WeakMembers) {
Bar::live_ = 0;
{
Persistent<Bar> h1 = Bar::Create();
Persistent<Weak> h4;
Persistent<WithWeakMember> h5;
PreciselyCollectGarbage();
ASSERT_EQ(1u, Bar::live_); // h1 is live.
{
Bar* h2 = Bar::Create();
Bar* h3 = Bar::Create();
h4 = Weak::Create(h2, h3);
h5 = WithWeakMember::Create(h2, h3);
ConservativelyCollectGarbage();
EXPECT_EQ(5u, Bar::live_); // The on-stack pointer keeps h3 alive.
EXPECT_FALSE(h3->HasBeenFinalized());
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.
PreciselyCollectGarbage();
EXPECT_EQ(4u, Bar::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.
PreciselyCollectGarbage();
EXPECT_EQ(3u, Bar::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.
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::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.
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
EXPECT_TRUE(o->DidCallWillFinalize());
FinalizationObserverWithHashMap::did_call_will_finalize_ = false;
Observable* foo = Observable::Create(Bar::Create());
FinalizationObserverWithHashMap::ObserverMap& map =
FinalizationObserverWithHashMap::Observe(*foo);
EXPECT_EQ(1u, map.size());
foo = nullptr;
// FinalizationObserverWithHashMap doesn't have a strong reference to
// |foo|. So |foo| and its member will be collected.
PreciselyCollectGarbage();
EXPECT_EQ(0u, Bar::live_);
EXPECT_EQ(0u, map.size());
EXPECT_TRUE(FinalizationObserverWithHashMap::did_call_will_finalize_);
FinalizationObserverWithHashMap::ClearObservers();
}
TEST(HeapTest, PreFinalizer) {
Observable::will_finalize_was_called_ = false;
{ Observable::Create(Bar::Create()); }
PreciselyCollectGarbage();
EXPECT_TRUE(Observable::will_finalize_was_called_);
}
TEST(HeapTest, PreFinalizerUnregistersItself) {
ObservableWithPreFinalizer::dispose_was_called_ = false;
ObservableWithPreFinalizer::Create();
PreciselyCollectGarbage();
EXPECT_TRUE(ObservableWithPreFinalizer::dispose_was_called_);
// Don't crash, and assertions don't fail.
}
TEST(HeapTest, NestedPreFinalizer) {
g_dispose_was_called_for_pre_finalizer_base = false;
g_dispose_was_called_for_pre_finalizer_sub_class = false;
g_dispose_was_called_for_pre_finalizer_mixin = false;
PreFinalizerSubClass::Create();
PreciselyCollectGarbage();
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_base);
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_sub_class);
EXPECT_TRUE(g_dispose_was_called_for_pre_finalizer_mixin);
// Don't crash, and assertions don't fail.
}
TEST(HeapTest, Comparisons) {
Persistent<Bar> bar_persistent = Bar::Create();
Persistent<Foo> foo_persistent = Foo::Create(bar_persistent);
EXPECT_TRUE(bar_persistent != foo_persistent);
bar_persistent = foo_persistent;
EXPECT_TRUE(bar_persistent == foo_persistent);
}
#if DCHECK_IS_ON()
namespace {
static size_t g_check_mark_count = 0;
bool ReportMarkedPointer(HeapObjectHeader*) {
g_check_mark_count++;
// Do not try to mark the located heap object.
return true;
}
}
#endif
TEST(HeapTest, CheckAndMarkPointer) {
#if DCHECK_IS_ON()
ThreadHeap& heap = ThreadState::Current()->Heap();
ClearOutOldGarbage();
Vector<Address> object_addresses;
Vector<Address> end_addresses;
Address large_object_address;
Address large_object_end_address;
for (int i = 0; i < 10; i++) {
SimpleObject* object = SimpleObject::Create();
Address object_address = reinterpret_cast<Address>(object);
object_addresses.push_back(object_address);
end_addresses.push_back(object_address + sizeof(SimpleObject) - 1);
}
LargeHeapObject* large_object = LargeHeapObject::Create();
large_object_address = reinterpret_cast<Address>(large_object);
large_object_end_address = large_object_address + sizeof(LargeHeapObject) - 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(BlinkGC::kHeapPointersOnStack);
MarkingVisitor visitor(ThreadState::Current(),
MarkingVisitor::kGlobalMarking);
heap.address_cache()->EnableLookup();
heap.address_cache()->Flush();
for (size_t i = 0; i < object_addresses.size(); i++) {
EXPECT_TRUE(heap.CheckAndMarkPointer(&visitor, object_addresses[i],
ReportMarkedPointer));
EXPECT_TRUE(heap.CheckAndMarkPointer(&visitor, end_addresses[i],
ReportMarkedPointer));
}
EXPECT_EQ(object_addresses.size() * 2, g_check_mark_count);
g_check_mark_count = 0;
EXPECT_TRUE(heap.CheckAndMarkPointer(&visitor, large_object_address,
ReportMarkedPointer));
EXPECT_TRUE(heap.CheckAndMarkPointer(&visitor, large_object_end_address,
ReportMarkedPointer));
EXPECT_EQ(2ul, g_check_mark_count);
g_check_mark_count = 0ul;
}
// 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();
{
TestGCScope scope(BlinkGC::kHeapPointersOnStack);
MarkingVisitor visitor(ThreadState::Current(),
MarkingVisitor::kGlobalMarking);
heap.address_cache()->EnableLookup();
heap.address_cache()->Flush();
for (size_t i = 0; i < object_addresses.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, object_addresses[i],
ReportMarkedPointer);
heap.CheckAndMarkPointer(&visitor, end_addresses[i], ReportMarkedPointer);
}
EXPECT_EQ(0ul, g_check_mark_count);
heap.CheckAndMarkPointer(&visitor, large_object_address,
ReportMarkedPointer);
heap.CheckAndMarkPointer(&visitor, large_object_end_address,
ReportMarkedPointer);
EXPECT_EQ(0ul, g_check_mark_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();
#endif
}
TEST(HeapTest, PersistentHeapCollectionTypes) {
IntWrapper::destructor_calls_ = 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();
{
PVec p_vec;
PDeque p_deque;
PSet p_set;
PListSet p_list_set;
PLinkedSet p_linked_set;
PMap p_map;
WeakPMap wp_map;
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));
p_vec.push_back(one);
p_vec.push_back(two);
p_deque.push_back(seven);
p_deque.push_back(two);
Vec* vec = new Vec();
vec->swap(p_vec);
p_vec.push_back(two);
p_vec.push_back(three);
p_set.insert(four);
p_list_set.insert(eight);
p_linked_set.insert(nine);
p_map.insert(five, six);
wp_map.insert(ten, eleven);
// Collect |vec| and |one|.
vec = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(1, IntWrapper::destructor_calls_);
EXPECT_EQ(2u, p_vec.size());
EXPECT_EQ(two, p_vec.at(0));
EXPECT_EQ(three, p_vec.at(1));
EXPECT_EQ(2u, p_deque.size());
EXPECT_EQ(seven, p_deque.front());
EXPECT_EQ(seven, p_deque.TakeFirst());
EXPECT_EQ(two, p_deque.front());
EXPECT_EQ(1u, p_deque.size());
EXPECT_EQ(1u, p_set.size());
EXPECT_TRUE(p_set.Contains(four));
EXPECT_EQ(1u, p_list_set.size());
EXPECT_TRUE(p_list_set.Contains(eight));
EXPECT_EQ(1u, p_linked_set.size());
EXPECT_TRUE(p_linked_set.Contains(nine));
EXPECT_EQ(1u, p_map.size());
EXPECT_EQ(six, p_map.at(five));
EXPECT_EQ(1u, wp_map.size());
EXPECT_EQ(eleven, wp_map.at(ten));
ten.Clear();
PreciselyCollectGarbage();
EXPECT_EQ(0u, wp_map.size());
}
// Collect previous roots.
PreciselyCollectGarbage();
EXPECT_EQ(11, IntWrapper::destructor_calls_);
}
TEST(HeapTest, CollectionNesting) {
ClearOutOldGarbage();
int* key = &IntWrapper::destructor_calls_;
IntWrapper::destructor_calls_ = 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>();
static_assert(WTF::IsTraceable<IntVector>::value,
"Failed to recognize HeapVector as traceable");
static_assert(WTF::IsTraceable<IntDeque>::value,
"Failed to recognize HeapDeque as traceable");
map->insert(key, IntVector());
map2->insert(key, IntDeque());
HeapHashMap<void*, IntVector>::iterator it = map->find(key);
EXPECT_EQ(0u, map->at(key).size());
HeapHashMap<void*, IntDeque>::iterator it2 = map2->find(key);
EXPECT_EQ(0u, map2->at(key).size());
it->value.push_back(IntWrapper::Create(42));
EXPECT_EQ(1u, map->at(key).size());
it2->value.push_back(IntWrapper::Create(42));
EXPECT_EQ(1u, map2->at(key).size());
Persistent<HeapHashMap<void*, IntVector>> keep_alive(map);
Persistent<HeapHashMap<void*, IntDeque>> keep_alive2(map2);
for (int i = 0; i < 100; i++) {
map->insert(key + 1 + i, IntVector());
map2->insert(key + 1 + i, IntDeque());
}
PreciselyCollectGarbage();
EXPECT_EQ(1u, map->at(key).size());
EXPECT_EQ(1u, map2->at(key).size());
EXPECT_EQ(0, IntWrapper::destructor_calls_);
keep_alive = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(1, IntWrapper::destructor_calls_);
}
TEST(HeapTest, GarbageCollectedMixin) {
ClearOutOldGarbage();
Persistent<UseMixin> usemixin = UseMixin::Create();
EXPECT_EQ(0, UseMixin::trace_count_);
PreciselyCollectGarbage();
EXPECT_EQ(1, UseMixin::trace_count_);
Persistent<Mixin> mixin = usemixin;
usemixin = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(2, UseMixin::trace_count_);
PersistentHeapHashSet<WeakMember<Mixin>> weak_map;
weak_map.insert(UseMixin::Create());
PreciselyCollectGarbage();
EXPECT_EQ(0u, weak_map.size());
}
TEST(HeapTest, CollectionNesting2) {
ClearOutOldGarbage();
void* key = &IntWrapper::destructor_calls_;
IntWrapper::destructor_calls_ = 0;
typedef HeapHashSet<Member<IntWrapper>> IntSet;
HeapHashMap<void*, IntSet>* map = new HeapHashMap<void*, IntSet>();
map->insert(key, IntSet());
HeapHashMap<void*, IntSet>::iterator it = map->find(key);
EXPECT_EQ(0u, map->at(key).size());
it->value.insert(IntWrapper::Create(42));
EXPECT_EQ(1u, map->at(key).size());
Persistent<HeapHashMap<void*, IntSet>> keep_alive(map);
PreciselyCollectGarbage();
EXPECT_EQ(1u, map->at(key).size());
EXPECT_EQ(0, IntWrapper::destructor_calls_);
}
TEST(HeapTest, CollectionNesting3) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
typedef HeapVector<Member<IntWrapper>> IntVector;
typedef HeapDeque<Member<IntWrapper>> IntDeque;
HeapVector<IntVector>* vector = new HeapVector<IntVector>();
HeapDeque<IntDeque>* deque = new HeapDeque<IntDeque>();
vector->push_back(IntVector());
deque->push_back(IntDeque());
HeapVector<IntVector>::iterator it = vector->begin();
HeapDeque<IntDeque>::iterator it2 = deque->begin();
EXPECT_EQ(0u, it->size());
EXPECT_EQ(0u, it2->size());
it->push_back(IntWrapper::Create(42));
it2->push_back(IntWrapper::Create(42));
EXPECT_EQ(1u, it->size());
EXPECT_EQ(1u, it2->size());
Persistent<HeapVector<IntVector>> keep_alive(vector);
Persistent<HeapDeque<IntDeque>> keep_alive2(deque);
PreciselyCollectGarbage();
EXPECT_EQ(1u, it->size());
EXPECT_EQ(1u, it2->size());
EXPECT_EQ(0, IntWrapper::destructor_calls_);
}
TEST(HeapTest, EmbeddedInVector) {
ClearOutOldGarbage();
SimpleFinalizedObject::destructor_calls_ = 0;
{
PersistentHeapVector<VectorObject, 2> inline_vector;
PersistentHeapVector<VectorObject> outline_vector;
VectorObject i1, i2;
inline_vector.push_back(i1);
inline_vector.push_back(i2);
VectorObject o1, o2;
outline_vector.push_back(o1);
outline_vector.push_back(o2);
PersistentHeapVector<VectorObjectInheritedTrace> vector_inherited_trace;
VectorObjectInheritedTrace it1, it2;
vector_inherited_trace.push_back(it1);
vector_inherited_trace.push_back(it2);
PreciselyCollectGarbage();
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(6, SimpleFinalizedObject::destructor_calls_);
}
TEST(HeapTest, EmbeddedInDeque) {
ClearOutOldGarbage();
SimpleFinalizedObject::destructor_calls_ = 0;
{
PersistentHeapDeque<VectorObject, 2> inline_deque;
PersistentHeapDeque<VectorObject> outline_deque;
VectorObject i1, i2;
inline_deque.push_back(i1);
inline_deque.push_back(i2);
VectorObject o1, o2;
outline_deque.push_back(o1);
outline_deque.push_back(o2);
PersistentHeapDeque<VectorObjectInheritedTrace> deque_inherited_trace;
VectorObjectInheritedTrace it1, it2;
deque_inherited_trace.push_back(it1);
deque_inherited_trace.push_back(it2);
PreciselyCollectGarbage();
EXPECT_EQ(0, SimpleFinalizedObject::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(6, SimpleFinalizedObject::destructor_calls_);
}
class InlinedVectorObject {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
InlinedVectorObject() = default;
~InlinedVectorObject() { destructor_calls_++; }
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
};
int InlinedVectorObject::destructor_calls_ = 0;
class InlinedVectorObjectWithVtable {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
InlinedVectorObjectWithVtable() = default;
virtual ~InlinedVectorObjectWithVtable() { destructor_calls_++; }
virtual void VirtualMethod() {}
void Trace(blink::Visitor* visitor) {}
static int destructor_calls_;
};
int InlinedVectorObjectWithVtable::destructor_calls_ = 0;
} // namespace blink
WTF_ALLOW_MOVE_AND_INIT_WITH_MEM_FUNCTIONS(blink::InlinedVectorObject);
namespace blink {
class InlinedVectorObjectWrapper final
: public GarbageCollectedFinalized<InlinedVectorObjectWrapper> {
public:
InlinedVectorObjectWrapper() {
InlinedVectorObject i1, i2;
vector1_.push_back(i1);
vector1_.push_back(i2);
vector2_.push_back(i1);
vector2_.push_back(i2); // This allocates an out-of-line buffer.
vector3_.push_back(i1);
vector3_.push_back(i2);
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(vector1_);
visitor->Trace(vector2_);
visitor->Trace(vector3_);
}
private:
HeapVector<InlinedVectorObject> vector1_;
HeapVector<InlinedVectorObject, 1> vector2_;
HeapVector<InlinedVectorObject, 2> vector3_;
};
class InlinedVectorObjectWithVtableWrapper final
: public GarbageCollectedFinalized<InlinedVectorObjectWithVtableWrapper> {
public:
InlinedVectorObjectWithVtableWrapper() {
InlinedVectorObjectWithVtable i1, i2;
vector1_.push_back(i1);
vector1_.push_back(i2);
vector2_.push_back(i1);
vector2_.push_back(i2); // This allocates an out-of-line buffer.
vector3_.push_back(i1);
vector3_.push_back(i2);
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(vector1_);
visitor->Trace(vector2_);
visitor->Trace(vector3_);
}
private:
HeapVector<InlinedVectorObjectWithVtable> vector1_;
HeapVector<InlinedVectorObjectWithVtable, 1> vector2_;
HeapVector<InlinedVectorObjectWithVtable, 2> vector3_;
};
TEST(HeapTest, VectorDestructors) {
ClearOutOldGarbage();
InlinedVectorObject::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObject> vector;
InlinedVectorObject i1, i2;
vector.push_back(i1);
vector.push_back(i2);
}
PreciselyCollectGarbage();
// This is not EXPECT_EQ but EXPECT_LE because a HeapVectorBacking calls
// destructors for all elements in (not the size but) the capacity of
// the vector. Thus the number of destructors called becomes larger
// than the actual number of objects in the vector.
EXPECT_LE(4, InlinedVectorObject::destructor_calls_);
InlinedVectorObject::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObject, 1> vector;
InlinedVectorObject i1, i2;
vector.push_back(i1);
vector.push_back(i2); // This allocates an out-of-line buffer.
}
PreciselyCollectGarbage();
EXPECT_LE(4, InlinedVectorObject::destructor_calls_);
InlinedVectorObject::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObject, 2> vector;
InlinedVectorObject i1, i2;
vector.push_back(i1);
vector.push_back(i2);
}
PreciselyCollectGarbage();
EXPECT_LE(4, InlinedVectorObject::destructor_calls_);
InlinedVectorObject::destructor_calls_ = 0;
{
Persistent<InlinedVectorObjectWrapper> vector_wrapper =
new InlinedVectorObjectWrapper();
ConservativelyCollectGarbage();
EXPECT_EQ(2, InlinedVectorObject::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_LE(8, InlinedVectorObject::destructor_calls_);
}
// TODO(Oilpan): when Vector.h's contiguous container support no longer disables
// Vector<>s with inline capacity, enable this test.
#if !defined(ANNOTATE_CONTIGUOUS_CONTAINER)
TEST(HeapTest, VectorDestructorsWithVtable) {
ClearOutOldGarbage();
InlinedVectorObjectWithVtable::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObjectWithVtable> vector;
InlinedVectorObjectWithVtable i1, i2;
vector.push_back(i1);
vector.push_back(i2);
}
PreciselyCollectGarbage();
EXPECT_EQ(4, InlinedVectorObjectWithVtable::destructor_calls_);
InlinedVectorObjectWithVtable::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObjectWithVtable, 1> vector;
InlinedVectorObjectWithVtable i1, i2;
vector.push_back(i1);
vector.push_back(i2); // This allocates an out-of-line buffer.
}
PreciselyCollectGarbage();
EXPECT_EQ(5, InlinedVectorObjectWithVtable::destructor_calls_);
InlinedVectorObjectWithVtable::destructor_calls_ = 0;
{
HeapVector<InlinedVectorObjectWithVtable, 2> vector;
InlinedVectorObjectWithVtable i1, i2;
vector.push_back(i1);
vector.push_back(i2);
}
PreciselyCollectGarbage();
EXPECT_EQ(4, InlinedVectorObjectWithVtable::destructor_calls_);
InlinedVectorObjectWithVtable::destructor_calls_ = 0;
{
Persistent<InlinedVectorObjectWithVtableWrapper> vector_wrapper =
new InlinedVectorObjectWithVtableWrapper();
ConservativelyCollectGarbage();
EXPECT_EQ(3, InlinedVectorObjectWithVtable::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(9, InlinedVectorObjectWithVtable::destructor_calls_);
}
#endif
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, HeapTerminatedArray) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
HeapTerminatedArray<TerminatedArrayItem>* arr = nullptr;
const size_t kPrefixSize = 4;
const size_t kSuffixSize = 4;
{
HeapTerminatedArrayBuilder<TerminatedArrayItem> builder(arr);
builder.Grow(kPrefixSize);
ConservativelyCollectGarbage();
for (size_t i = 0; i < kPrefixSize; i++)
builder.Append(TerminatedArrayItem(IntWrapper::Create(i)));
arr = builder.Release();
}
ConservativelyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(kPrefixSize, arr->size());
for (size_t i = 0; i < kPrefixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).Payload()->Value()));
{
HeapTerminatedArrayBuilder<TerminatedArrayItem> builder(arr);
builder.Grow(kSuffixSize);
for (size_t i = 0; i < kSuffixSize; i++)
builder.Append(TerminatedArrayItem(IntWrapper::Create(kPrefixSize + i)));
arr = builder.Release();
}
ConservativelyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(kPrefixSize + kSuffixSize, arr->size());
for (size_t i = 0; i < kPrefixSize + kSuffixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).Payload()->Value()));
{
Persistent<HeapTerminatedArray<TerminatedArrayItem>> persistent_arr = arr;
arr = nullptr;
PreciselyCollectGarbage();
arr = persistent_arr.Get();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(kPrefixSize + kSuffixSize, arr->size());
for (size_t i = 0; i < kPrefixSize + kSuffixSize; i++)
EXPECT_EQ(i, static_cast<size_t>(arr->at(i).Payload()->Value()));
}
arr = nullptr;
PreciselyCollectGarbage();
EXPECT_EQ(8, IntWrapper::destructor_calls_);
}
TEST(HeapTest, HeapLinkedStack) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
HeapLinkedStack<TerminatedArrayItem>* stack =
new HeapLinkedStack<TerminatedArrayItem>();
const size_t kStackSize = 10;
for (size_t i = 0; i < kStackSize; i++)
stack->Push(TerminatedArrayItem(IntWrapper::Create(i)));
ConservativelyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(kStackSize, stack->size());
while (!stack->IsEmpty()) {
EXPECT_EQ(stack->size() - 1,
static_cast<size_t>(stack->Peek().Payload()->Value()));
stack->Pop();
}
Persistent<HeapLinkedStack<TerminatedArrayItem>> p_stack = stack;
PreciselyCollectGarbage();
EXPECT_EQ(kStackSize, static_cast<size_t>(IntWrapper::destructor_calls_));
EXPECT_EQ(0u, p_stack->size());
}
TEST(HeapTest, AllocationDuringFinalization) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
OneKiloByteObject::destructor_calls_ = 0;
LargeHeapObject::destructor_calls_ = 0;
Persistent<IntWrapper> wrapper;
new FinalizationAllocator(&wrapper);
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(0, OneKiloByteObject::destructor_calls_);
EXPECT_EQ(0, LargeHeapObject::destructor_calls_);
// 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();
PreciselyCollectGarbage();
// The 42 IntWrappers were the ones allocated in ~FinalizationAllocator
// and the ones allocated in LargeHeapObject.
EXPECT_EQ(42, IntWrapper::destructor_calls_);
EXPECT_EQ(512, OneKiloByteObject::destructor_calls_);
EXPECT_EQ(32, LargeHeapObject::destructor_calls_);
}
TEST(HeapTest, AllocationDuringPrefinalizer) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
OneKiloByteObject::destructor_calls_ = 0;
LargeHeapObject::destructor_calls_ = 0;
Persistent<IntWrapper> wrapper;
new PreFinalizationAllocator(&wrapper);
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_EQ(0, OneKiloByteObject::destructor_calls_);
EXPECT_EQ(0, LargeHeapObject::destructor_calls_);
// 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();
PreciselyCollectGarbage();
// The 42 IntWrappers were the ones allocated in the pre-finalizer
// of PreFinalizationAllocator and the ones allocated in LargeHeapObject.
EXPECT_EQ(42, IntWrapper::destructor_calls_);
EXPECT_EQ(512, OneKiloByteObject::destructor_calls_);
EXPECT_EQ(32, LargeHeapObject::destructor_calls_);
}
class SimpleClassWithDestructor {
public:
SimpleClassWithDestructor() = default;
~SimpleClassWithDestructor() { was_destructed_ = true; }
static bool was_destructed_;
};
bool SimpleClassWithDestructor::was_destructed_;
class RefCountedWithDestructor : public RefCounted<RefCountedWithDestructor> {
public:
RefCountedWithDestructor() = default;
~RefCountedWithDestructor() { was_destructed_ = true; }
static bool was_destructed_;
};
bool RefCountedWithDestructor::was_destructed_;
template <typename Set>
void DestructorsCalledOnGC(bool add_lots) {
RefCountedWithDestructor::was_destructed_ = false;
{
Set set;
RefCountedWithDestructor* has_destructor = new RefCountedWithDestructor();
set.Add(base::AdoptRef(has_destructor));
EXPECT_FALSE(RefCountedWithDestructor::was_destructed_);
if (add_lots) {
for (int i = 0; i < 1000; i++) {
set.Add(base::AdoptRef(new RefCountedWithDestructor()));
}
}
EXPECT_FALSE(RefCountedWithDestructor::was_destructed_);
ConservativelyCollectGarbage();
EXPECT_FALSE(RefCountedWithDestructor::was_destructed_);
}
// 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::was_destructed_);
PreciselyCollectGarbage();
EXPECT_TRUE(RefCountedWithDestructor::was_destructed_);
}
template <typename Set>
void DestructorsCalledOnClear(bool add_lots) {
RefCountedWithDestructor::was_destructed_ = false;
Set set;
RefCountedWithDestructor* has_destructor = new RefCountedWithDestructor();
set.Add(base::AdoptRef(has_destructor));
EXPECT_FALSE(RefCountedWithDestructor::was_destructed_);
if (add_lots) {
for (int i = 0; i < 1000; i++) {
set.Add(base::AdoptRef(new RefCountedWithDestructor()));
}
}
EXPECT_FALSE(RefCountedWithDestructor::was_destructed_);
set.Clear();
EXPECT_TRUE(RefCountedWithDestructor::was_destructed_);
}
TEST(HeapTest, DestructorsCalled) {
HeapHashMap<Member<IntWrapper>, std::unique_ptr<SimpleClassWithDestructor>>
map;
SimpleClassWithDestructor* has_destructor = new SimpleClassWithDestructor();
map.insert(IntWrapper::Create(1), base::WrapUnique(has_destructor));
SimpleClassWithDestructor::was_destructed_ = false;
map.clear();
EXPECT_TRUE(SimpleClassWithDestructor::was_destructed_);
}
class MixinA : public GarbageCollectedMixin {
public:
MixinA() : obj_(IntWrapper::Create(100)) {}
void Trace(blink::Visitor* visitor) override {
trace_count_++;
visitor->Trace(obj_);
}
static int trace_count_;
Member<IntWrapper> obj_;
};
int MixinA::trace_count_ = 0;
class MixinB : public GarbageCollectedMixin {
public:
MixinB() : obj_(IntWrapper::Create(101)) {}
void Trace(blink::Visitor* visitor) override { visitor->Trace(obj_); }
Member<IntWrapper> obj_;
};
class MultipleMixins : public GarbageCollected<MultipleMixins>,
public MixinA,
public MixinB {
USING_GARBAGE_COLLECTED_MIXIN(MultipleMixins);
public:
MultipleMixins() : obj_(IntWrapper::Create(102)) {}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(obj_);
MixinA::Trace(visitor);
MixinB::Trace(visitor);
}
Member<IntWrapper> obj_;
};
class DerivedMultipleMixins : public MultipleMixins {
public:
DerivedMultipleMixins() : obj_(IntWrapper::Create(103)) {}
void Trace(blink::Visitor* visitor) override {
trace_called_++;
visitor->Trace(obj_);
MultipleMixins::Trace(visitor);
}
static int trace_called_;
private:
Member<IntWrapper> obj_;
};
int DerivedMultipleMixins::trace_called_ = 0;
static const bool kIsMixinTrue = IsGarbageCollectedMixin<MultipleMixins>::value;
static const bool kIsMixinFalse = IsGarbageCollectedMixin<IntWrapper>::value;
TEST(HeapTest, MultipleMixins) {
EXPECT_TRUE(kIsMixinTrue);
EXPECT_FALSE(kIsMixinFalse);
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
MultipleMixins* obj = new MultipleMixins();
{
Persistent<MixinA> a = obj;
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
}
{
Persistent<MixinB> b = obj;
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
}
PreciselyCollectGarbage();
EXPECT_EQ(3, IntWrapper::destructor_calls_);
}
TEST(HeapTest, DerivedMultipleMixins) {
ClearOutOldGarbage();
IntWrapper::destructor_calls_ = 0;
DerivedMultipleMixins::trace_called_ = 0;
DerivedMultipleMixins* obj = new DerivedMultipleMixins();
{
Persistent<MixinA> a = obj;
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_LT(0, DerivedMultipleMixins::trace_called_);
}
{
Persistent<MixinB> b = obj;
PreciselyCollectGarbage();
EXPECT_EQ(0, IntWrapper::destructor_calls_);
EXPECT_LT(0, DerivedMultipleMixins::trace_called_);
}
PreciselyCollectGarbage();
EXPECT_EQ(4, IntWrapper::destructor_calls_);
}
class MixinInstanceWithoutTrace
: public GarbageCollected<MixinInstanceWithoutTrace>,
public MixinA {
USING_GARBAGE_COLLECTED_MIXIN(MixinInstanceWithoutTrace);
public:
MixinInstanceWithoutTrace() = default;
};
TEST(HeapTest, MixinInstanceWithoutTrace) {
// Verify that a mixin instance without any traceable
// references inherits the mixin's trace implementation.
ClearOutOldGarbage();
MixinA::trace_count_ = 0;
MixinInstanceWithoutTrace* obj = new MixinInstanceWithoutTrace();
int saved_trace_count = 0;
{
Persistent<MixinA> a = obj;
PreciselyCollectGarbage();
saved_trace_count = MixinA::trace_count_;
EXPECT_LT(0, saved_trace_count);
}
{
Persistent<MixinInstanceWithoutTrace> b = obj;
PreciselyCollectGarbage();
EXPECT_LT(saved_trace_count, MixinA::trace_count_);
saved_trace_count = MixinA::trace_count_;
}
PreciselyCollectGarbage();
// Oilpan might still call trace on dead objects for various reasons which is
// valid before sweeping started.
EXPECT_LE(saved_trace_count, MixinA::trace_count_);
}
TEST(HeapTest, NeedsAdjustPointer) {
// class Mixin : public GarbageCollectedMixin {};
static_assert(NeedsAdjustPointer<Mixin>::value,
"A Mixin pointer needs adjustment");
static_assert(NeedsAdjustPointer<const Mixin>::value,
"A const Mixin pointer needs adjustment");
// class SimpleObject : public GarbageCollected<SimpleObject> {};
static_assert(!NeedsAdjustPointer<SimpleObject>::value,
"A SimpleObject pointer does not need adjustment");
static_assert(!NeedsAdjustPointer<const SimpleObject>::value,
"A const SimpleObject pointer does not need adjustment");
// class UseMixin : public SimpleObject, public Mixin {};
static_assert(!NeedsAdjustPointer<UseMixin>::value,
"A UseMixin pointer does not need adjustment");
static_assert(!NeedsAdjustPointer<const UseMixin>::value,
"A const UseMixin pointer does not need adjustment");
}
template <typename Set>
void SetWithCustomWeaknessHandling() {
typedef typename Set::iterator Iterator;
Persistent<IntWrapper> living_int(IntWrapper::Create(42));
Persistent<Set> set1(new Set());
{
Set set2;
Set* set3 = new Set();
set2.insert(
PairWithWeakHandling(IntWrapper::Create(0), IntWrapper::Create(1)));
set3->insert(
PairWithWeakHandling(IntWrapper::Create(2), IntWrapper::Create(3)));
set1->insert(
PairWithWeakHandling(IntWrapper::Create(4), IntWrapper::Create(5)));
ConservativelyCollectGarbage();
// 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());
}
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, set1->size());
set1->insert(PairWithWeakHandling(IntWrapper::Create(103), living_int));
// This one gets zapped at GC time because nothing holds the 103 alive.
set1->insert(PairWithWeakHandling(living_int, IntWrapper::Create(103)));
set1->insert(PairWithWeakHandling(
IntWrapper::Create(103),
IntWrapper::Create(103))); // This one gets zapped too.
set1->insert(PairWithWeakHandling(living_int, living_int));
// This one is identical to the previous and doesn't add anything.
set1->insert(PairWithWeakHandling(living_int, living_int));
EXPECT_EQ(4u, set1->size());
PreciselyCollectGarbage();
EXPECT_EQ(2u, set1->size());
Iterator i1 = set1->begin();
EXPECT_TRUE(i1->first->Value() == 103 || i1->first == living_int);
EXPECT_EQ(living_int, i1->second);
++i1;
EXPECT_TRUE(i1->first->Value() == 103 || i1->first == living_int);
EXPECT_EQ(living_int, i1->second);
}
TEST(HeapTest, SetWithCustomWeaknessHandling) {
SetWithCustomWeaknessHandling<HeapHashSet<PairWithWeakHandling>>();
SetWithCustomWeaknessHandling<HeapLinkedHashSet<PairWithWeakHandling>>();
}
TEST(HeapTest, MapWithCustomWeaknessHandling) {
typedef HeapHashMap<PairWithWeakHandling, scoped_refptr<OffHeapInt>> Map;
typedef Map::iterator Iterator;
ClearOutOldGarbage();
OffHeapInt::destructor_calls_ = 0;
Persistent<Map> map1(new Map());
Persistent<IntWrapper> living_int(IntWrapper::Create(42));
{
Map map2;
Map* map3 = new Map();
map2.insert(
PairWithWeakHandling(IntWrapper::Create(0), IntWrapper::Create(1)),
OffHeapInt::Create(1001));
map3->insert(
PairWithWeakHandling(IntWrapper::Create(2), IntWrapper::Create(3)),
OffHeapInt::Create(1002));
map1->insert(
PairWithWeakHandling(IntWrapper::Create(4), IntWrapper::Create(5)),
OffHeapInt::Create(1003));
EXPECT_EQ(0, OffHeapInt::destructor_calls_);
ConservativelyCollectGarbage();
// 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());
}
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, map1->size());
EXPECT_EQ(3, OffHeapInt::destructor_calls_);
OffHeapInt::destructor_calls_ = 0;
map1->insert(PairWithWeakHandling(IntWrapper::Create(103), living_int),
OffHeapInt::Create(2000));
map1->insert(PairWithWeakHandling(living_int, IntWrapper::Create(103)),
OffHeapInt::Create(2001)); // This one gets zapped at GC time
// because nothing holds the 103 alive.
map1->insert(
PairWithWeakHandling(IntWrapper::Create(103), IntWrapper::Create(103)),
OffHeapInt::Create(2002)); // This one gets zapped too.
scoped_refptr<OffHeapInt> dupe_int(OffHeapInt::Create(2003));
map1->insert(PairWithWeakHandling(living_int, living_int), dupe_int);
map1->insert(
PairWithWeakHandling(living_int, living_int),
dupe_int); // This one is identical to the previous and doesn't add
// anything.
dupe_int = nullptr;
EXPECT_EQ(0, OffHeapInt::destructor_calls_);
EXPECT_EQ(4u, map1->size());
PreciselyCollectGarbage();
EXPECT_EQ(2, OffHeapInt::destructor_calls_);
EXPECT_EQ(2u, map1->size());
Iterator i1 = map1->begin();
EXPECT_TRUE(i1->key.first->Value() == 103 || i1->key.first == living_int);
EXPECT_EQ(living_int, i1->key.second);
++i1;
EXPECT_TRUE(i1->key.first->Value() == 103 || i1->key.first == living_int);
EXPECT_EQ(living_int, i1->key.second);
}
TEST(HeapTest, MapWithCustomWeaknessHandling2) {
typedef HeapHashMap<scoped_refptr<OffHeapInt>, PairWithWeakHandling> Map;
typedef Map::iterator Iterator;
ClearOutOldGarbage();
OffHeapInt::destructor_calls_ = 0;
Persistent<Map> map1(new Map());
Persistent<IntWrapper> living_int(IntWrapper::Create(42));
{
Map map2;
Map* map3 = new Map();
map2.insert(
OffHeapInt::Create(1001),
PairWithWeakHandling(IntWrapper::Create(0), IntWrapper::Create(1)));
map3->insert(
OffHeapInt::Create(1002),
PairWithWeakHandling(IntWrapper::Create(2), IntWrapper::Create(3)));
map1->insert(
OffHeapInt::Create(1003),
PairWithWeakHandling(IntWrapper::Create(4), IntWrapper::Create(5)));
EXPECT_EQ(0, OffHeapInt::destructor_calls_);
ConservativelyCollectGarbage();
// 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());
}
}
PreciselyCollectGarbage();
EXPECT_EQ(0u, map1->size());
EXPECT_EQ(3, OffHeapInt::destructor_calls_);
OffHeapInt::destructor_calls_ = 0;
map1->insert(OffHeapInt::Create(2000),
PairWithWeakHandling(IntWrapper::Create(103), living_int));
// This one gets zapped at GC time because nothing holds the 103 alive.
map1->insert(OffHeapInt::Create(2001),
PairWithWeakHandling(living_int, IntWrapper::Create(103)));
map1->insert(OffHeapInt::Create(2002),
PairWithWeakHandling(
IntWrapper::Create(103),
IntWrapper::Create(103))); // This one gets zapped too.
scoped_refptr<OffHeapInt> dupe_int(OffHeapInt::Create(2003));
map1->insert(dupe_int, PairWithWeakHandling(living_int, living_int));
// This one is identical to the previous and doesn't add anything.
map1->insert(dupe_int, PairWithWeakHandling(living_int, living_int));
dupe_int = nullptr;
EXPECT_EQ(0, OffHeapInt::destructor_calls_);
EXPECT_EQ(4u, map1->size());
PreciselyCollectGarbage();
EXPECT_EQ(2, OffHeapInt::destructor_calls_);
EXPECT_EQ(2u, map1->size());
Iterator i1 = map1->begin();
EXPECT_TRUE(i1->value.first->Value() == 103 || i1->value.first == living_int);
EXPECT_EQ(living_int, i1->value.second);
++i1;
EXPECT_TRUE(i1->value.first->Value() == 103 || i1->value.first == living_int);
EXPECT_EQ(living_int, 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->insert(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->insert(800, nullptr);
ConservativelyCollectGarbage();
result.stored_value->value = IntWrapper::Create(42);
}
TEST(HeapTest, Bind) {
base::OnceClosure closure =
WTF::Bind(static_cast<void (Bar::*)(Visitor*)>(&Bar::Trace),
WrapPersistent(Bar::Create()), nullptr);
// OffHeapInt* should not make Persistent.
base::OnceClosure closure2 =
WTF::Bind(&OffHeapInt::VoidFunction, OffHeapInt::Create(1));
PreciselyCollectGarbage();
// The closure should have a persistent handle to the Bar.
EXPECT_EQ(1u, Bar::live_);
UseMixin::trace_count_ = 0;
Mixin* mixin = UseMixin::Create();
base::OnceClosure mixin_closure =
WTF::Bind(static_cast<void (Mixin::*)(Visitor*)>(&Mixin::Trace),
WrapPersistent(mixin), nullptr);
PreciselyCollectGarbage();
// The closure should have a persistent handle to the mixin.
EXPECT_EQ(1, UseMixin::trace_count_);
}
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 kWeakHandlingFlag = WTF::kWeakHandling;
static bool IsAlive(WeakSet& set) {
bool live_entries_found = false;
WeakSet::iterator end = set.end();
for (WeakSet::iterator it = set.begin(); it != end; ++it) {
if (ThreadHeap::IsHeapObjectAlive(*it)) {
live_entries_found = true;
break;
}
}
return live_entries_found;
}
template <typename VisitorDispatcher>
static bool TraceInCollection(VisitorDispatcher visitor,
WeakSet& set,
WTF::WeakHandlingFlag weakenss) {
bool live_entries_found = false;
WeakSet::iterator end = set.end();
for (WeakSet::iterator it = set.begin(); it != end; ++it) {
if (ThreadHeap::IsHeapObjectAlive(*it)) {
live_entries_found = 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 (live_entries_found)
set.Trace(visitor);
return !live_entries_found;
}
};
// 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) {
ClearOutOldGarbage();
OffHeapInt::destructor_calls_ = 0;
Persistent<IntWrapper> living_int(IntWrapper::Create(42));
typedef scoped_refptr<OffHeapInt> Key;
typedef HeapHashMap<Key, WeakSet, WTF::DefaultHash<Key>::Hash,
HashTraits<Key>, EmptyClearingHashSetTraits>
Map;
Persistent<Map> map(new Map());
map->insert(OffHeapInt::Create(1), WeakSet());
{
WeakSet& set = map->begin()->value;
set.insert(IntWrapper::Create(103)); // Weak set can't hold this long.
set.insert(living_int); // 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->insert(OffHeapInt::Create(2), WeakSet());
EXPECT_EQ(2u, map->size());
PreciselyCollectGarbage();
EXPECT_EQ(1u, map->size()); // The one with key 2 was removed.
EXPECT_EQ(1, OffHeapInt::destructor_calls_);
{
WeakSet& set = map->begin()->value;
EXPECT_EQ(1u, set.size());
}
living_int.Clear(); // The weak set can no longer keep the '42' alive now.
PreciselyCollectGarbage();
EXPECT_EQ(0u, map->size());
}
TEST(HeapTest, EphemeronsInEphemerons) {
typedef HeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper>> InnerMap;
typedef HeapHashMap<WeakMember<IntWrapper>, InnerMap> OuterMap;
for (int keep_outer_alive = 0; keep_outer_alive <= 1; keep_outer_alive++) {
for (int keep_inner_alive = 0; keep_inner_alive <= 1; keep_inner_alive++) {
Persistent<OuterMap> outer = new OuterMap();
Persistent<IntWrapper> one = IntWrapper::Create(1);
Persistent<IntWrapper> two = IntWrapper::Create(2);
outer->insert(one, InnerMap());
outer->begin()->value.insert(two, IntWrapper::Create(3));
EXPECT_EQ(1u, outer->at(one).size());
if (!keep_outer_alive)
one.Clear();
if (!keep_inner_alive)
two.Clear();
PreciselyCollectGarbage();
if (keep_outer_alive) {
const InnerMap& inner = outer->at(one);
if (keep_inner_alive) {
EXPECT_EQ(1u, inner.size());
IntWrapper* three = inner.at(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>>> keep_alive =
new HeapVector<Member<IntWrapper>>();
for (int i = 0; i < 10000; i++) {
IntWrapper* value = IntWrapper::Create(i);
keep_alive->push_back(value);
OuterMap::AddResult new_entry = outer->insert(value, InnerMap());
new_entry.stored_value->value.insert(deep, home);
new_entry.stored_value->value.insert(composite, home);
}
composite.Clear();
PreciselyCollectGarbage();
EXPECT_EQ(10000u, outer->size());
for (int i = 0; i < 10000; i++) {
IntWrapper* value = keep_alive->at(i);
EXPECT_EQ(1u,
outer->at(value)
.size()); // Other one was deleted by weak handling.
if (i & 1)
keep_alive->at(i) = nullptr;
}
PreciselyCollectGarbage();
EXPECT_EQ(5000u, outer->size());
}
}
}
class EphemeronWrapper : public GarbageCollected<EphemeronWrapper> {
public:
void Trace(blink::Visitor* visitor) { visitor->Trace(map_); }
typedef HeapHashMap<WeakMember<IntWrapper>, Member<EphemeronWrapper>> Map;
Map& GetMap() { return map_; }
private:
Map 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* old_head = chain;
chain = new EphemeronWrapper();
if (i == 50)
chain->GetMap().insert(key2, old_head);
else
chain->GetMap().insert(key, old_head);
chain->GetMap().insert(IntWrapper::Create(103), new EphemeronWrapper());
}
PreciselyCollectGarbage();
EphemeronWrapper* wrapper = chain;
for (int i = 0; i < 100; i++) {
EXPECT_EQ(1u, wrapper->GetMap().size());
if (i == 49)
wrapper = wrapper->GetMap().at(key2);
else
wrapper = wrapper->GetMap().at(key);
}
EXPECT_EQ(nullptr, wrapper);
key2.Clear();
PreciselyCollectGarbage();
wrapper = chain;
for (int i = 0; i < 50; i++) {
EXPECT_EQ(i == 49 ? 0u : 1u, wrapper->GetMap().size());
wrapper = wrapper->GetMap().at(key);
}
EXPECT_EQ(nullptr, wrapper);
key.Clear();
PreciselyCollectGarbage();
EXPECT_EQ(0u, chain->GetMap().size());
}
TEST(HeapTest, Ephemeron) {
typedef HeapHashMap<WeakMember<IntWrapper>, PairWithWeakHandling> WeakPairMap;
typedef HeapHashMap<PairWithWeakHandling, WeakMember<IntWrapper>> PairWeakMap;
typedef HeapHashSet<WeakMember<IntWrapper>> Set;
Persistent<WeakPairMap> weak_pair_map = new WeakPairMap();
Persistent<WeakPairMap> weak_pair_map2 = new WeakPairMap();
Persistent<WeakPairMap> weak_pair_map3 = new WeakPairMap();
Persistent<WeakPairMap> weak_pair_map4 = new WeakPairMap();
Persistent<PairWeakMap> pair_weak_map = new PairWeakMap();
Persistent<PairWeakMap> pair_weak_map2 = 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);
weak_pair_map->insert(wp1, PairWithWeakHandling(wp1, wp1));
weak_pair_map->insert(wp2, PairWithWeakHandling(wp1, wp1));
weak_pair_map2->insert(wp1, PairWithWeakHandling(wp1, wp2));
weak_pair_map2->insert(wp2, PairWithWeakHandling(wp1, wp2));
// The map from wp1 to (wp2, wp1) would mark wp2 live, so we skip that.
weak_pair_map3->insert(wp2, PairWithWeakHandling(wp2, wp1));
weak_pair_map4->insert(wp1, PairWithWeakHandling(wp2, wp2));
weak_pair_map4->insert(wp2, PairWithWeakHandling(wp2, wp2));
pair_weak_map->insert(PairWithWeakHandling(pw1, pw1), pw1);
pair_weak_map->insert(PairWithWeakHandling(pw1, pw2), pw1);
// The map from (pw2, pw1) to pw1 would make pw2 live, so we skip that.
pair_weak_map->insert(PairWithWeakHandling(pw2, pw2), pw1);
pair_weak_map2->insert(PairWithWeakHandling(pw1, pw1), pw2);
pair_weak_map2->insert(PairWithWeakHandling(pw1, pw2), pw2);
pair_weak_map2->insert(PairWithWeakHandling(pw2, pw1), pw2);
pair_weak_map2->insert(PairWithWeakHandling(pw2, pw2), pw2);
set->insert(wp1);
set->insert(wp2);
set->insert(pw1);
set->insert(pw2);
PreciselyCollectGarbage();
EXPECT_EQ(2u, weak_pair_map->size());
EXPECT_EQ(2u, weak_pair_map2->size());
EXPECT_EQ(1u, weak_pair_map3->size());
EXPECT_EQ(2u, weak_pair_map4->size());
EXPECT_EQ(3u, pair_weak_map->size());
EXPECT_EQ(4u, pair_weak_map2->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++) {
PreciselyCollectGarbage();
EXPECT_EQ(1u, weak_pair_map->size());
EXPECT_EQ(0u, weak_pair_map2->size());
EXPECT_EQ(0u, weak_pair_map3->size());
EXPECT_EQ(0u, weak_pair_map4->size());
EXPECT_EQ(1u, pair_weak_map->size());
EXPECT_EQ(0u, pair_weak_map2->size());
EXPECT_EQ(2u, set->size()); // wp1 and pw1.
}
wp1.Clear();
pw1.Clear();
PreciselyCollectGarbage();
EXPECT_EQ(0u, weak_pair_map->size());
EXPECT_EQ(0u, pair_weak_map->size());
EXPECT_EQ(0u, set->size());
}
class Link1 : public GarbageCollected<Link1> {
public:
Link1(IntWrapper* link) : link_(link) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(link_); }
IntWrapper* Link() { return link_; }
private:
Member<IntWrapper> link_;
};
TEST(HeapTest, IndirectStrongToWeak) {
typedef HeapHashMap<WeakMember<IntWrapper>, Member<Link1>> Map;
Persistent<Map> map = new Map();
Persistent<IntWrapper> dead_object =
IntWrapper::Create(100); // Named for "Drowning by Numbers" (1988).
Persistent<IntWrapper> life_object = IntWrapper::Create(42);
map->insert(dead_object, new Link1(dead_object));
map->insert(life_object, new Link1(life_object));
EXPECT_EQ(2u, map->size());
PreciselyCollectGarbage();
EXPECT_EQ(2u, map->size());
EXPECT_EQ(dead_object, map->at(dead_object)->Link());
EXPECT_EQ(life_object, map->at(life_object)->Link());
dead_object.Clear(); // Now it can live up to its name.
PreciselyCollectGarbage();
EXPECT_EQ(1u, map->size());
EXPECT_EQ(life_object, map->at(life_object)->Link());
life_object.Clear(); // Despite its name.
PreciselyCollectGarbage();
EXPECT_EQ(0u, map->size());
}
static Mutex& MainThreadMutex() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, main_mutex, ());
return main_mutex;
}
static ThreadCondition& MainThreadCondition() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadCondition, main_condition, ());
return main_condition;
}
static void ParkMainThread() {
MainThreadCondition().Wait(MainThreadMutex());
}
static void WakeMainThread() {
MutexLocker locker(MainThreadMutex());
MainThreadCondition().Signal();
}
static Mutex& WorkerThreadMutex() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(Mutex, worker_mutex, ());
return worker_mutex;
}
static ThreadCondition& WorkerThreadCondition() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadCondition, worker_condition, ());
return worker_condition;
}
static void ParkWorkerThread() {
WorkerThreadCondition().Wait(WorkerThreadMutex());
}
static void WakeWorkerThread() {
MutexLocker locker(WorkerThreadMutex());
WorkerThreadCondition().Signal();
}
class ThreadedStrongificationTester {
public:
static void Test() {
IntWrapper::destructor_calls_ = 0;
MutexLocker locker(MainThreadMutex());
std::unique_ptr<WebThread> worker_thread =
Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("Test Worker Thread"));
PostCrossThreadTask(*worker_thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(WorkerThreadMain));
// Wait for the worker thread initialization. The worker
// allocates a weak collection where both collection and
// contents are kept alive via persistent pointers.
ParkMainThread();
// Perform two garbage collections where the worker thread does
// not wake up in between. This will cause us to remove marks
// and mark unmarked objects dead. The collection on the worker
// heap is found through the persistent and the backing should
// be marked.
PreciselyCollectGarbage();
PreciselyCollectGarbage();
// Wake up the worker thread so it can continue. It will sweep
// and perform another GC where the backing store of its
// collection should be strongified.
WakeWorkerThread();
// Wait for the worker thread to sweep its heaps before checking.
ParkMainThread();
}
private:
using WeakCollectionType =
HeapHashMap<WeakMember<IntWrapper>, Member<IntWrapper>>;
static WeakCollectionType* AllocateCollection() {
// Create a weak collection that is kept alive by a persistent
// and keep the contents alive with a persistents as
// well.
Persistent<IntWrapper> wrapper1 = IntWrapper::Create(32);
Persistent<IntWrapper> wrapper2 = IntWrapper::Create(32);
Persistent<IntWrapper> wrapper3 = IntWrapper::Create(32);
Persistent<IntWrapper> wrapper4 = IntWrapper::Create(32);
Persistent<IntWrapper> wrapper5 = IntWrapper::Create(32);
Persistent<IntWrapper> wrapper6 = IntWrapper::Create(32);
Persistent<WeakCollectionType> weak_collection = new WeakCollectionType;
weak_collection->insert(wrapper1, wrapper1);
weak_collection->insert(wrapper2, wrapper2);
weak_collection->insert(wrapper3, wrapper3);
weak_collection->insert(wrapper4, wrapper4);
weak_collection->insert(wrapper5, wrapper5);
weak_collection->insert(wrapper6, wrapper6);
// 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.
ParkWorkerThread();
return weak_collection;
}
static void WorkerThreadMain() {
MutexLocker locker(WorkerThreadMutex());
ThreadState::AttachCurrentThread();
{
Persistent<WeakCollectionType> collection = AllocateCollection();
{
// Prevent weak processing with an iterator and GC.
WeakCollectionType::iterator it = collection->begin();
ConservativelyCollectGarbage();
// The backing should be strongified because of the iterator.
EXPECT_EQ(6u, collection->size());
EXPECT_EQ(32, it->value->Value());
}
// Disregarding the iterator but keeping the collection alive
// with a persistent should lead to weak processing.
PreciselyCollectGarbage();
EXPECT_EQ(0u, collection->size());
}
WakeMainThread();
ThreadState::DetachCurrentThread();
}
static volatile uintptr_t worker_object_pointer_;
};
TEST(HeapTest, ThreadedStrongification) {
ThreadedStrongificationTester::Test();
}
class MemberSameThreadCheckTester {
public:
void Test() {
IntWrapper::destructor_calls_ = 0;
MutexLocker locker(MainThreadMutex());
std::unique_ptr<WebThread> worker_thread =
Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("Test Worker Thread"));
PostCrossThreadTask(
*worker_thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(&MemberSameThreadCheckTester::WorkerThreadMain,
CrossThreadUnretained(this)));
ParkMainThread();
}
private:
Member<IntWrapper> wrapper_;
void WorkerThreadMain() {
MutexLocker locker(WorkerThreadMutex());
ThreadState::AttachCurrentThread();
// Setting an object created on the worker thread to a Member allocated on
// the main thread is not allowed.
wrapper_ = IntWrapper::Create(42);
WakeMainThread();
ThreadState::DetachCurrentThread();
}
};
#if DCHECK_IS_ON()
// TODO(keishi) This test is flaky on mac_chromium_rel_ng bot.
// crbug.com/709069
#if !defined(OS_MACOSX)
TEST(HeapDeathTest, MemberSameThreadCheck) {
EXPECT_DEATH(MemberSameThreadCheckTester().Test(), "");
}
#endif
#endif
class PersistentSameThreadCheckTester {
public:
void Test() {
IntWrapper::destructor_calls_ = 0;
MutexLocker locker(MainThreadMutex());
std::unique_ptr<WebThread> worker_thread =
Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("Test Worker Thread"));
PostCrossThreadTask(
*worker_thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(&PersistentSameThreadCheckTester::WorkerThreadMain,
CrossThreadUnretained(this)));
ParkMainThread();
}
private:
Persistent<IntWrapper> wrapper_;
void WorkerThreadMain() {
MutexLocker locker(WorkerThreadMutex());
ThreadState::AttachCurrentThread();
// Setting an object created on the worker thread to a Persistent allocated
// on the main thread is not allowed.
wrapper_ = IntWrapper::Create(42);
WakeMainThread();
ThreadState::DetachCurrentThread();
}
};
#if DCHECK_IS_ON()
// TODO(keishi) This test is flaky on mac_chromium_rel_ng bot.
// crbug.com/709069
#if !defined(OS_MACOSX)
TEST(HeapDeathTest, PersistentSameThreadCheck) {
EXPECT_DEATH(PersistentSameThreadCheckTester().Test(), "");
}
#endif
#endif
class MarkingSameThreadCheckTester {
public:
void Test() {
IntWrapper::destructor_calls_ = 0;
MutexLocker locker(MainThreadMutex());
std::unique_ptr<WebThread> worker_thread =
Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("Test Worker Thread"));
Persistent<MainThreadObject> main_thread_object = new MainThreadObject();
PostCrossThreadTask(
*worker_thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(&MarkingSameThreadCheckTester::WorkerThreadMain,
CrossThreadUnretained(this),
WrapCrossThreadPersistent(main_thread_object.Get())));
ParkMainThread();
// This will try to mark MainThreadObject when it tries to mark IntWrapper
// it should crash.
PreciselyCollectGarbage();
}
private:
class MainThreadObject : public GarbageCollectedFinalized<MainThreadObject> {
public:
void Trace(blink::Visitor* visitor) { visitor->Trace(wrapper_set_); }
void AddToSet(IntWrapper* wrapper) { wrapper_set_.insert(42, wrapper); }
private:
HeapHashMap<int, Member<IntWrapper>> wrapper_set_;
};
void WorkerThreadMain(MainThreadObject* main_thread_object) {
MutexLocker locker(WorkerThreadMutex());
ThreadState::AttachCurrentThread();
// Adding a reference to an object created on the worker thread to a
// HeapHashMap created on the main thread is not allowed.
main_thread_object->AddToSet(IntWrapper::Create(42));
WakeMainThread();
ThreadState::DetachCurrentThread();
}
};
#if DCHECK_IS_ON()
// TODO(keishi) This test is flaky on mac_chromium_rel_ng bot.
// crbug.com/709069
#if !defined(OS_MACOSX)
TEST(HeapDeathTest, MarkingSameThreadCheck) {
// This will crash during marking, at the DCHECK in Visitor::markHeader() or
// earlier.
EXPECT_DEATH(MarkingSameThreadCheckTester().Test(), "");
}
#endif
#endif
static bool AllocateAndReturnBool() {
ConservativelyCollectGarbage();
return true;
}
static bool CheckGCForbidden() {
DCHECK(ThreadState::Current()->IsGCForbidden());
return true;
}
class MixinClass : public GarbageCollectedMixin {
public:
MixinClass() : dummy_(CheckGCForbidden()) {}
private:
bool dummy_;
};
class ClassWithGarbageCollectingMixinConstructor
: public GarbageCollected<ClassWithGarbageCollectingMixinConstructor>,
public MixinClass {
USING_GARBAGE_COLLECTED_MIXIN(ClassWithGarbageCollectingMixinConstructor);
public:
static int trace_called_;
ClassWithGarbageCollectingMixinConstructor()
: trace_counter_(TraceCounter::Create()),
wrapper_(IntWrapper::Create(32)) {}
void Trace(blink::Visitor* visitor) override {
trace_called_++;
visitor->Trace(trace_counter_);
visitor->Trace(wrapper_);
}
void Verify() {
EXPECT_EQ(32, wrapper_->Value());
EXPECT_EQ(0, trace_counter_->TraceCount());
EXPECT_EQ(0, trace_called_);
}
private:
Member<TraceCounter> trace_counter_;
Member<IntWrapper> wrapper_;
};
int ClassWithGarbageCollectingMixinConstructor::trace_called_ = 0;
// Regression test for out of bounds call through vtable.
// Passes if it doesn't crash.
TEST(HeapTest, GarbageCollectionDuringMixinConstruction) {
ClassWithGarbageCollectingMixinConstructor* a =
new ClassWithGarbageCollectingMixinConstructor();
a->Verify();
}
class DestructorLockingObject
: public GarbageCollectedFinalized<DestructorLockingObject> {
public:
static DestructorLockingObject* Create() {
return new DestructorLockingObject();
}
virtual ~DestructorLockingObject() {
++destructor_calls_;
}
static int destructor_calls_;
void Trace(blink::Visitor* visitor) {}
private:
DestructorLockingObject() = default;
};
int DestructorLockingObject::destructor_calls_ = 0;
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(blink::Visitor* visitor) {
TraceIfNeeded<T>::Trace(visitor, obj_);
}
T& Obj() { return obj_; }
~TraceIfNeededTester() = default;
private:
TraceIfNeededTester() = default;
explicit TraceIfNeededTester(const T& obj) : obj_(obj) {}
T obj_;
};
class PartObject {
DISALLOW_NEW();
public:
PartObject() : obj_(SimpleObject::Create()) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(obj_); }
private:
Member<SimpleObject> obj_;
};
class AllocatesOnAssignment {
public:
AllocatesOnAssignment(std::nullptr_t) : value_(nullptr) {}
AllocatesOnAssignment(int x) : value_(new IntWrapper(x)) {}
AllocatesOnAssignment(IntWrapper* x) : value_(x) {}
AllocatesOnAssignment& operator=(const AllocatesOnAssignment x) {
value_ = x.value_;
return *this;
}
enum DeletedMarker { kDeletedValue };
AllocatesOnAssignment(const AllocatesOnAssignment& other) {
if (!ThreadState::Current()->IsGCForbidden())
ConservativelyCollectGarbage();
value_ = new IntWrapper(other.value_->Value());
}
AllocatesOnAssignment(DeletedMarker) : value_(WTF::kHashTableDeletedValue) {}
inline bool IsDeleted() const { return value_.IsHashTableDeletedValue(); }
void Trace(blink::Visitor* visitor) { visitor->Trace(value_); }
int Value() { return value_->Value(); }
private:
Member<IntWrapper> value_;
friend bool operator==(const AllocatesOnAssignment&,
const AllocatesOnAssignment&);
friend void swap(AllocatesOnAssignment&, AllocatesOnAssignment&);
};
bool operator==(const AllocatesOnAssignment& a,
const AllocatesOnAssignment& b) {
if (a.value_)
return b.value_ && a.value_->Value() == b.value_->Value();
return !b.value_;
}
void swap(AllocatesOnAssignment& a, AllocatesOnAssignment& b) {
std::swap(a.value_, b.value_);
}
struct DegenerateHash {
static unsigned GetHash(const AllocatesOnAssignment&) { return 0; }
static bool Equal(const AllocatesOnAssignment& a,
const AllocatesOnAssignment& b) {
return !a.IsDeleted() && a == b;
}
static const bool safe_to_compare_to_empty_or_deleted = true;
};
struct AllocatesOnAssignmentHashTraits
: WTF::GenericHashTraits<AllocatesOnAssignment> {
typedef AllocatesOnAssignment T;
typedef std::nullptr_t EmptyValueType;
static EmptyValueType EmptyValue() { return nullptr; }
static const bool kEmptyValueIsZero =
false; // Can't be zero if it has a vtable.
static void ConstructDeletedValue(T& slot, bool) {
slot = T(AllocatesOnAssignment::kDeletedValue);
}
static bool IsDeletedValue(const T& value) { return value.IsDeleted(); }
};
} // namespace blink
namespace WTF {
template <>
struct DefaultHash<blink::AllocatesOnAssignment> {
typedef blink::DegenerateHash Hash;
};
template <>
struct HashTraits<blink::AllocatesOnAssignment>
: blink::AllocatesOnAssignmentHashTraits {};
} // namespace WTF
namespace blink {
TEST(HeapTest, GCInHashMapOperations) {
typedef HeapHashMap<AllocatesOnAssignment, AllocatesOnAssignment> Map;
Map* map = new Map();
IntWrapper* key = new IntWrapper(42);
map->insert(key, AllocatesOnAssignment(103));
map->erase(key);
for (int i = 0; i < 10; i++)
map->insert(AllocatesOnAssignment(i), AllocatesOnAssignment(i));
for (Map::iterator it = map->begin(); it != map->end(); ++it)
EXPECT_EQ(it->key.Value(), it->value.Value());
}
class PartObjectWithVirtualMethod {
public:
virtual void Trace(blink::Visitor* visitor) {}
};
class ObjectWithVirtualPartObject
: public GarbageCollected<ObjectWithVirtualPartObject> {
public:
ObjectWithVirtualPartObject() : dummy_(AllocateAndReturnBool()) {}
void Trace(blink::Visitor* visitor) { visitor->Trace(part_); }
private:
bool dummy_;
PartObjectWithVirtualMethod part_;
};
TEST(HeapTest, PartObjectWithVirtualMethod) {
ObjectWithVirtualPartObject* object = new ObjectWithVirtualPartObject();
EXPECT_TRUE(object);
}
class AllocInSuperConstructorArgumentSuper
: public GarbageCollectedFinalized<AllocInSuperConstructorArgumentSuper> {
public:
AllocInSuperConstructorArgumentSuper(bool value) : value_(value) {}
virtual ~AllocInSuperConstructorArgumentSuper() = default;
virtual void Trace(blink::Visitor* visitor) {}
bool Value() { return value_; }
private:
bool 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);
ThreadState::Current()->CollectAllGarbage();
}
class NonNodeAllocatingNodeInDestructor
: public GarbageCollectedFinalized<NonNodeAllocatingNodeInDestructor> {
public:
~NonNodeAllocatingNodeInDestructor() {
node_ = new Persistent<IntNode>(IntNode::Create(10));
}
void Trace(blink::Visitor* visitor) {}
static Persistent<IntNode>* node_;
};
Persistent<IntNode>* NonNodeAllocatingNodeInDestructor::node_ = nullptr;
TEST(HeapTest, NonNodeAllocatingNodeInDestructor) {
new NonNodeAllocatingNodeInDestructor();
PreciselyCollectGarbage();
EXPECT_EQ(10, (*NonNodeAllocatingNodeInDestructor::node_)->Value());
delete NonNodeAllocatingNodeInDestructor::node_;
NonNodeAllocatingNodeInDestructor::node_ = nullptr;
}
class TraceTypeEagerly1 : public GarbageCollected<TraceTypeEagerly1> {};
class TraceTypeEagerly2 : public TraceTypeEagerly1 {};
class TraceTypeNonEagerly1 {};
WILL_NOT_BE_EAGERLY_TRACED_CLASS(TraceTypeNonEagerly1);
class TraceTypeNonEagerly2 : public TraceTypeNonEagerly1 {};
TEST(HeapTest, TraceTypesEagerly) {
static_assert(TraceEagerlyTrait<TraceTypeEagerly1>::value, "should be true");
static_assert(TraceEagerlyTrait<Member<TraceTypeEagerly1>>::value,
"should be true");
static_assert(TraceEagerlyTrait<WeakMember<TraceTypeEagerly1>>::value,
"should be true");
static_assert(TraceEagerlyTrait<HeapVector<Member<TraceTypeEagerly1>>>::value,
"should be true");
static_assert(
TraceEagerlyTrait<HeapVector<WeakMember<TraceTypeEagerly1>>>::value,
"should be true");
static_assert(
TraceEagerlyTrait<HeapHashSet<Member<TraceTypeEagerly1>>>::value,
"should be true");
static_assert(
TraceEagerlyTrait<HeapHashSet<Member<TraceTypeEagerly1>>>::value,
"should be true");
using HashMapIntToObj = HeapHashMap<int, Member<TraceTypeEagerly1>>;
static_assert(TraceEagerlyTrait<HashMapIntToObj>::value, "should be true");
using HashMapObjToInt = HeapHashMap<Member<TraceTypeEagerly1>, int>;
static_assert(TraceEagerlyTrait<HashMapObjToInt>::value, "should be true");
static_assert(TraceEagerlyTrait<TraceTypeEagerly2>::value, "should be true");
static_assert(TraceEagerlyTrait<Member<TraceTypeEagerly2>>::value,
"should be true");
static_assert(!TraceEagerlyTrait<TraceTypeNonEagerly1>::value,
"should be false");
static_assert(TraceEagerlyTrait<TraceTypeNonEagerly2>::value,
"should be true");
}
class DeepEagerly final : public GarbageCollected<DeepEagerly> {
public:
DeepEagerly(DeepEagerly* next) : next_(next) {}
void Trace(blink::Visitor* visitor) {
int calls = ++s_trace_calls_;
if (s_trace_lazy_ <= 2)
visitor->Trace(next_);
if (s_trace_calls_ == calls)
s_trace_lazy_++;
}
Member<DeepEagerly> next_;
static int s_trace_calls_;
static int s_trace_lazy_;
};
int DeepEagerly::s_trace_calls_ = 0;
int DeepEagerly::s_trace_lazy_ = 0;
TEST(HeapTest, TraceDeepEagerly) {
// The allocation & GC overhead is considerable for this test,
// straining debug builds and lower-end targets too much to be
// worth running.
#if !DCHECK_IS_ON() && !defined(OS_ANDROID)
DeepEagerly* obj = nullptr;
for (int i = 0; i < 10000000; i++)
obj = new DeepEagerly(obj);
Persistent<DeepEagerly> persistent(obj);
PreciselyCollectGarbage();
// Verify that the DeepEagerly chain isn't completely unravelled
// by performing eager trace() calls, but the explicit mark
// stack is switched once some nesting limit is exceeded.
EXPECT_GT(DeepEagerly::s_trace_lazy_, 2);
#endif
}
TEST(HeapTest, DequeExpand) {
// Test expansion of a HeapDeque<>'s buffer.
typedef HeapDeque<Member<IntWrapper>> IntDeque;
Persistent<IntDeque> deque = new IntDeque();
// Append a sequence, bringing about repeated expansions of the
// deque's buffer.
int i = 0;
for (; i < 60; ++i)
deque->push_back(IntWrapper::Create(i));
EXPECT_EQ(60u, deque->size());
i = 0;
for (const auto& int_wrapper : *deque) {
EXPECT_EQ(i, int_wrapper->Value());
i++;
}
// Remove most of the queued objects and have the buffer's start index
// 'point' somewhere into the buffer, just behind the end index.
for (i = 0; i < 50; ++i)
deque->TakeFirst();
EXPECT_EQ(10u, deque->size());
i = 0;
for (const auto& int_wrapper : *deque) {
EXPECT_EQ(50 + i, int_wrapper->Value());
i++;
}
// Append even more, eventually causing an expansion of the underlying
// buffer once the end index wraps around and reaches the start index.
for (i = 0; i < 70; ++i)
deque->push_back(IntWrapper::Create(60 + i));
// Verify that the final buffer expansion copied the start and end segments
// of the old buffer to both ends of the expanded buffer, along with
// re-adjusting both start&end indices in terms of that expanded buffer.
EXPECT_EQ(80u, deque->size());
i = 0;
for (const auto& int_wrapper : *deque) {
EXPECT_EQ(i + 50, int_wrapper->Value());
i++;
}
}
class SimpleRefValue : public RefCounted<SimpleRefValue> {
public:
static scoped_refptr<SimpleRefValue> Create(int i) {
return base::AdoptRef(new SimpleRefValue(i));
}
int Value() const { return value_; }
private:
explicit SimpleRefValue(int value) : value_(value) {}
int value_;
};
class PartObjectWithRef {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
PartObjectWithRef(int i) : value_(SimpleRefValue::Create(i)) {}
void Trace(blink::Visitor* visitor) {}
int Value() const { return value_->Value(); }
private:
scoped_refptr<SimpleRefValue> value_;
};
} // namespace blink
WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::PartObjectWithRef);
namespace blink {
TEST(HeapTest, DequePartObjectsExpand) {
// Test expansion of HeapDeque<PartObject>
using PartDeque = HeapDeque<PartObjectWithRef>;
Persistent<PartDeque> deque = new PartDeque();
// Auxillary Deque used to prevent 'inline' buffer expansion.
Persistent<PartDeque> deque_unused = new PartDeque();
// Append a sequence, bringing about repeated expansions of the
// deque's buffer.
int i = 0;
for (; i < 60; ++i) {
deque->push_back(PartObjectWithRef(i));
deque_unused->push_back(PartObjectWithRef(i));
}
EXPECT_EQ(60u, deque->size());
i = 0;
for (const PartObjectWithRef& part : *deque) {
EXPECT_EQ(i, part.Value());
i++;
}
// Remove most of the queued objects and have the buffer's start index
// 'point' somewhere into the buffer, just behind the end index.
for (i = 0; i < 50; ++i)
deque->TakeFirst();
EXPECT_EQ(10u, deque->size());
i = 0;
for (const PartObjectWithRef& part : *deque) {
EXPECT_EQ(50 + i, part.Value());
i++;
}
// Append even more, eventually causing an expansion of the underlying
// buffer once the end index wraps around and reaches the start index.
for (i = 0; i < 70; ++i)
deque->push_back(PartObjectWithRef(60 + i));
// Verify that the final buffer expansion copied the start and end segments
// of the old buffer to both ends of the expanded buffer, along with
// re-adjusting both start&end indices in terms of that expanded buffer.
EXPECT_EQ(80u, deque->size());
i = 0;
for (const PartObjectWithRef& part : *deque) {
EXPECT_EQ(i + 50, part.Value());
i++;
}
for (i = 0; i < 70; ++i)
deque->push_back(PartObjectWithRef(130 + i));
EXPECT_EQ(150u, deque->size());
i = 0;
for (const PartObjectWithRef& part : *deque) {
EXPECT_EQ(i + 50, part.Value());
i++;
}
}
TEST(HeapTest, HeapVectorPartObjects) {
HeapVector<PartObjectWithRef> vector1;
HeapVector<PartObjectWithRef> vector2;
for (int i = 0; i < 10; ++i) {
vector1.push_back(PartObjectWithRef(i));
vector2.push_back(PartObjectWithRef(i));
}
vector1.ReserveCapacity(150);
EXPECT_LE(150u, vector1.capacity());
EXPECT_EQ(10u, vector1.size());
vector2.ReserveCapacity(100);
EXPECT_LE(100u, vector2.capacity());
EXPECT_EQ(10u, vector2.size());
for (int i = 0; i < 4; ++i) {
vector1.push_back(PartObjectWithRef(10 + i));
vector2.push_back(PartObjectWithRef(10 + i));
vector2.push_back(PartObjectWithRef(10 + i));
}
// Shrinking heap vector backing stores always succeeds,
// so these two will not currently exercise the code path
// where shrinking causes copying into a new, small buffer.
vector2.ShrinkToReasonableCapacity();
EXPECT_EQ(18u, vector2.size());
vector1.ShrinkToReasonableCapacity();
EXPECT_EQ(14u, vector1.size());
}
namespace {
enum GrowthDirection {
kGrowsTowardsHigher,
kGrowsTowardsLower,
};
NOINLINE NO_SANITIZE_ADDRESS GrowthDirection StackGrowthDirection() {
// Disable ASan, otherwise its stack checking (use-after-return) will
// confuse the direction check.
static char* previous = nullptr;
char dummy;
if (!previous) {
previous = &dummy;
GrowthDirection result = StackGrowthDirection();
previous = nullptr;
return result;
}
DCHECK_NE(&dummy, previous);
return &dummy < previous ? kGrowsTowardsLower : kGrowsTowardsHigher;
}
} // namespace
TEST(HeapTest, StackGrowthDirection) {
// The implementation of marking probes stack usage as it runs,
// and has a builtin assumption that the stack grows towards
// lower addresses.
EXPECT_EQ(kGrowsTowardsLower, StackGrowthDirection());
}
TEST(HeapTest, StackFrameDepthDisabledByDefault) {
StackFrameDepth depth;
// Only allow recursion after explicitly enabling the stack limit.
EXPECT_FALSE(depth.IsSafeToRecurse());
}
TEST(HeapTest, StackFrameDepthEnable) {
StackFrameDepth depth;
StackFrameDepthScope scope(&depth);
// The scope may fail to enable recursion when the stack is close to the
// limit. In all other cases we should be able to safely recurse.
EXPECT_TRUE(depth.IsSafeToRecurse() || !depth.IsEnabled());
}
class TestMixinAllocationA : public GarbageCollected<TestMixinAllocationA>,
public GarbageCollectedMixin {
USING_GARBAGE_COLLECTED_MIXIN(TestMixinAllocationA);
public:
TestMixinAllocationA() {
// Completely wrong in general, but test only
// runs this constructor while constructing another mixin.
DCHECK(ThreadState::Current()->IsGCForbidden());
}
void Trace(blink::Visitor* visitor) override {}
};
class TestMixinAllocationB : public TestMixinAllocationA {
USING_GARBAGE_COLLECTED_MIXIN(TestMixinAllocationB);
public:
TestMixinAllocationB()
: a_(new TestMixinAllocationA()) // Construct object during a mixin
// construction.
{
// Completely wrong in general, but test only
// runs this constructor while constructing another mixin.
DCHECK(ThreadState::Current()->IsGCForbidden());
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(a_);
TestMixinAllocationA::Trace(visitor);
}
private:
Member<TestMixinAllocationA> a_;
};
class TestMixinAllocationC final : public TestMixinAllocationB {
USING_GARBAGE_COLLECTED_MIXIN(TestMixinAllocationC);
public:
TestMixinAllocationC() { DCHECK(!ThreadState::Current()->IsGCForbidden()); }
void Trace(blink::Visitor* visitor) override {
TestMixinAllocationB::Trace(visitor);
}
};
TEST(HeapTest, NestedMixinConstruction) {
TestMixinAllocationC* object = new TestMixinAllocationC();
EXPECT_TRUE(object);
}
class ObjectWithLargeAmountsOfAllocationInConstructor {
public:
ObjectWithLargeAmountsOfAllocationInConstructor(
size_t number_of_large_objects_to_allocate,
ClassWithMember* member) {
// Should a constructor allocate plenty in its constructor,
// and it is a base of GC mixin, GCs will remain locked out
// regardless, as we cannot safely trace the leftmost GC
// mixin base.
DCHECK(ThreadState::Current()->IsGCForbidden());
for (size_t i = 0; i < number_of_large_objects_to_allocate; i++) {
LargeHeapObject* large_object = LargeHeapObject::Create();
EXPECT_TRUE(large_object);
EXPECT_EQ(0, member->TraceCount());
}
}
};
class TestMixinAllocatingObject final
: public TestMixinAllocationB,
public ObjectWithLargeAmountsOfAllocationInConstructor {
USING_GARBAGE_COLLECTED_MIXIN(TestMixinAllocatingObject);
public:
static TestMixinAllocatingObject* Create(ClassWithMember* member) {
return new TestMixinAllocatingObject(member);
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(trace_counter_);
TestMixinAllocationB::Trace(visitor);
}
int TraceCount() const { return trace_counter_->TraceCount(); }
private:
TestMixinAllocatingObject(ClassWithMember* member)
: ObjectWithLargeAmountsOfAllocationInConstructor(600, member),
trace_counter_(TraceCounter::Create()) {
DCHECK(!ThreadState::Current()->IsGCForbidden());
ConservativelyCollectGarbage();
EXPECT_GT(member->TraceCount(), 0);
EXPECT_GT(TraceCount(), 0);
}
Member<TraceCounter> trace_counter_;
};
TEST(HeapTest, MixinConstructionNoGC) {
Persistent<ClassWithMember> object = ClassWithMember::Create();
EXPECT_EQ(0, object->TraceCount());
TestMixinAllocatingObject* mixin =
TestMixinAllocatingObject::Create(object.Get());
EXPECT_TRUE(mixin);
EXPECT_GT(object->TraceCount(), 0);
EXPECT_GT(mixin->TraceCount(), 0);
}
class WeakPersistentHolder final {
public:
explicit WeakPersistentHolder(IntWrapper* object) : object_(object) {}
IntWrapper* Object() const { return object_; }
private:
WeakPersistent<IntWrapper> object_;
};
TEST(HeapTest, WeakPersistent) {
Persistent<IntWrapper> object = new IntWrapper(20);
std::unique_ptr<WeakPersistentHolder> holder =
std::make_unique<WeakPersistentHolder>(object);
PreciselyCollectGarbage();
EXPECT_TRUE(holder->Object());
object = nullptr;
PreciselyCollectGarbage();
EXPECT_FALSE(holder->Object());
}
namespace {
void WorkerThreadMainForCrossThreadWeakPersistentTest(
DestructorLockingObject** object) {
// Step 2: Create an object and store the pointer.
MutexLocker locker(WorkerThreadMutex());
ThreadState::AttachCurrentThread();
*object = DestructorLockingObject::Create();
WakeMainThread();
ParkWorkerThread();
// Step 4: Run a GC.
ThreadState::Current()->CollectGarbage(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping, BlinkGC::GCReason::kForcedGC);
WakeMainThread();
ParkWorkerThread();
// Step 6: Finish.
ThreadState::DetachCurrentThread();
WakeMainThread();
}
} // anonymous namespace
TEST(HeapTest, CrossThreadWeakPersistent) {
// Create an object in the worker thread, have a CrossThreadWeakPersistent
// pointing to it on the main thread, clear the reference in the worker
// thread, run a GC in the worker thread, and see if the
// CrossThreadWeakPersistent is cleared.
DestructorLockingObject::destructor_calls_ = 0;
// Step 1: Initiate a worker thread, and wait for |object| to get allocated on
// the worker thread.
MutexLocker main_thread_mutex_locker(MainThreadMutex());
std::unique_ptr<WebThread> worker_thread = Platform::Current()->CreateThread(
WebThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("Test Worker Thread"));
DestructorLockingObject* object = nullptr;
PostCrossThreadTask(
*worker_thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(WorkerThreadMainForCrossThreadWeakPersistentTest,
CrossThreadUnretained(&object)));
ParkMainThread();
// Step 3: Set up a CrossThreadWeakPersistent.
ASSERT_TRUE(object);
CrossThreadWeakPersistent<DestructorLockingObject>
cross_thread_weak_persistent(object);
object = nullptr;
EXPECT_EQ(0, DestructorLockingObject::destructor_calls_);
// Pretend we have no pointers on stack during the step 4.
WakeWorkerThread();
ParkMainThread();
// Step 5: Make sure the weak persistent is cleared.
EXPECT_FALSE(cross_thread_weak_persistent.Get());
EXPECT_EQ(1, DestructorLockingObject::destructor_calls_);
WakeWorkerThread();
ParkMainThread();
}
class TestPersistentHeapVectorWithUnusedSlots
: public PersistentHeapVector<VectorObject, 16> {
public:
void CheckUnused() { CheckUnusedSlots(end(), end() + (capacity() - size())); }
};
TEST(HeapTest, TestPersistentHeapVectorWithUnusedSlots) {
TestPersistentHeapVectorWithUnusedSlots vector1;
TestPersistentHeapVectorWithUnusedSlots vector2(vector1);
vector1.CheckUnused();
vector2.CheckUnused();
vector2.push_back(VectorObject());
vector2.CheckUnused();
EXPECT_EQ(0u, vector1.size());
EXPECT_EQ(1u, vector2.size());
// TODO(Oilpan): when Vector.h's contiguous container support no longer disables
// Vector<>s with inline capacity, remove.
#if !defined(ANNOTATE_CONTIGUOUS_CONTAINER)
EXPECT_EQ(16u, vector1.capacity());
EXPECT_EQ(16u, vector2.capacity());
#endif
}
TEST(HeapTest, TestStaticLocals) {
// Sanity check DEFINE_STATIC_LOCAL()s over heap allocated objects and
// collections.
DEFINE_STATIC_LOCAL(IntWrapper, int_wrapper, (new IntWrapper(33)));
DEFINE_STATIC_LOCAL(PersistentHeapVector<Member<IntWrapper>>,
persistent_heap_vector_int_wrapper, ());
DEFINE_STATIC_LOCAL(HeapVector<Member<IntWrapper>>, heap_vector_int_wrapper,
(new HeapVector<Member<IntWrapper>>));
EXPECT_EQ(33, int_wrapper.Value());
EXPECT_EQ(0u, persistent_heap_vector_int_wrapper.size());
EXPECT_EQ(0u, heap_vector_int_wrapper.size());
persistent_heap_vector_int_wrapper.push_back(&int_wrapper);
heap_vector_int_wrapper.push_back(&int_wrapper);
EXPECT_EQ(1u, persistent_heap_vector_int_wrapper.size());
EXPECT_EQ(1u, heap_vector_int_wrapper.size());
EXPECT_EQ(persistent_heap_vector_int_wrapper[0], heap_vector_int_wrapper[0]);
EXPECT_EQ(33, heap_vector_int_wrapper[0]->Value());
}
namespace {
class ThreadedClearOnShutdownTester : public ThreadedTesterBase {
public:
static void Test() {
IntWrapper::destructor_calls_ = 0;
ThreadedTesterBase::Test(new ThreadedClearOnShutdownTester);
EXPECT_EQ(kNumberOfThreads, IntWrapper::destructor_calls_);
}
private:
void RunWhileAttached();
void RunThread() override {
ThreadState::AttachCurrentThread();
EXPECT_EQ(42, ThreadSpecificIntWrapper().Value());
RunWhileAttached();
ThreadState::DetachCurrentThread();
AtomicDecrement(&threads_to_finish_);
}
class HeapObject;
friend class HeapObject;
using WeakHeapObjectSet = HeapHashSet<WeakMember<HeapObject>>;
static WeakHeapObjectSet& GetWeakHeapObjectSet();
using HeapObjectSet = HeapHashSet<Member<HeapObject>>;
static HeapObjectSet& GetHeapObjectSet();
static IntWrapper& ThreadSpecificIntWrapper() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<Persistent<IntWrapper>>,
int_wrapper, ());
Persistent<IntWrapper>& handle = *int_wrapper;
if (!handle) {
handle = new IntWrapper(42);
handle.RegisterAsStaticReference();
}
return *handle;
}
};
class ThreadedClearOnShutdownTester::HeapObject final
: public GarbageCollectedFinalized<
ThreadedClearOnShutdownTester::HeapObject> {
public:
static HeapObject* Create(bool test_destructor) {
return new HeapObject(test_destructor);
}
~HeapObject() {
if (!test_destructor_)
return;
// Verify that the weak reference is gone.
EXPECT_FALSE(GetWeakHeapObjectSet().Contains(this));
// Add a new member to the static singleton; this will
// re-initializes the persistent node of the collection
// object. Done while terminating the test thread, so
// verify that this brings about the release of the
// persistent also.
GetHeapObjectSet().insert(Create(false));
}
void Trace(blink::Visitor* visitor) {}
private:
explicit HeapObject(bool test_destructor)
: test_destructor_(test_destructor) {}
bool test_destructor_;
};
ThreadedClearOnShutdownTester::WeakHeapObjectSet&
ThreadedClearOnShutdownTester::GetWeakHeapObjectSet() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<Persistent<WeakHeapObjectSet>>,
singleton, ());
Persistent<WeakHeapObjectSet>& singleton_persistent = *singleton;
if (!singleton_persistent) {
singleton_persistent = new WeakHeapObjectSet();
singleton_persistent.RegisterAsStaticReference();
}
return *singleton_persistent;
}
ThreadedClearOnShutdownTester::HeapObjectSet&
ThreadedClearOnShutdownTester::GetHeapObjectSet() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<Persistent<HeapObjectSet>>,
singleton, ());
Persistent<HeapObjectSet>& singleton_persistent = *singleton;
if (!singleton_persistent) {
singleton_persistent = new HeapObjectSet();
singleton_persistent.RegisterAsStaticReference();
}
return *singleton_persistent;
}
void ThreadedClearOnShutdownTester::RunWhileAttached() {
EXPECT_EQ(42, ThreadSpecificIntWrapper().Value());
// Creates a thread-specific singleton to a weakly held object.
GetWeakHeapObjectSet().insert(HeapObject::Create(true));
}
} // namespace
TEST(HeapTest, TestClearOnShutdown) {
ThreadedClearOnShutdownTester::Test();
}
// Verify that WeakMember<const T> compiles and behaves as expected.
class WithWeakConstObject final : public GarbageCollected<WithWeakConstObject> {
public:
static WithWeakConstObject* Create(const IntWrapper* int_wrapper) {
return new WithWeakConstObject(int_wrapper);
}
void Trace(blink::Visitor* visitor) { visitor->Trace(wrapper_); }
const IntWrapper* Value() const { return wrapper_; }
private:
WithWeakConstObject(const IntWrapper* int_wrapper) : wrapper_(int_wrapper) {}
WeakMember<const IntWrapper> wrapper_;
};
TEST(HeapTest, TestWeakConstObject) {
Persistent<WithWeakConstObject> weak_wrapper;
{
const IntWrapper* wrapper = IntWrapper::Create(42);
weak_wrapper = WithWeakConstObject::Create(wrapper);
ConservativelyCollectGarbage();
EXPECT_EQ(wrapper, weak_wrapper->Value());
// Stub out any stack reference.
wrapper = nullptr;
}
PreciselyCollectGarbage();
EXPECT_EQ(nullptr, weak_wrapper->Value());
}
class EmptyMixin : public GarbageCollectedMixin {};
class UseMixinFromLeftmostInherited : public UseMixin, public EmptyMixin {
public:
~UseMixinFromLeftmostInherited() = default;
};
TEST(HeapTest, IsGarbageCollected) {
// Static sanity checks covering the correct operation of
// IsGarbageCollectedType<>.
static_assert(WTF::IsGarbageCollectedType<SimpleObject>::value,
"GarbageCollected<>");
static_assert(WTF::IsGarbageCollectedType<const SimpleObject>::value,
"const GarbageCollected<>");
static_assert(WTF::IsGarbageCollectedType<IntWrapper>::value,
"GarbageCollectedFinalized<>");
static_assert(WTF::IsGarbageCollectedType<GarbageCollectedMixin>::value,
"GarbageCollectedMixin");
static_assert(WTF::IsGarbageCollectedType<const GarbageCollectedMixin>::value,
"const GarbageCollectedMixin");
static_assert(WTF::IsGarbageCollectedType<UseMixin>::value,
"GarbageCollectedMixin instance");
static_assert(WTF::IsGarbageCollectedType<const UseMixin>::value,
"const GarbageCollectedMixin instance");
static_assert(
WTF::IsGarbageCollectedType<UseMixinFromLeftmostInherited>::value,
"GarbageCollectedMixin derived instance");
static_assert(WTF::IsGarbageCollectedType<MultipleMixins>::value,
"GarbageCollectedMixin");
static_assert(
WTF::IsGarbageCollectedType<HeapHashSet<Member<IntWrapper>>>::value,
"HeapHashSet");
static_assert(
WTF::IsGarbageCollectedType<HeapLinkedHashSet<Member<IntWrapper>>>::value,
"HeapLinkedHashSet");
static_assert(
WTF::IsGarbageCollectedType<HeapListHashSet<Member<IntWrapper>>>::value,
"HeapListHashSet");
static_assert(WTF::IsGarbageCollectedType<
HeapHashCountedSet<Member<IntWrapper>>>::value,
"HeapHashCountedSet");
static_assert(
WTF::IsGarbageCollectedType<HeapHashMap<int, Member<IntWrapper>>>::value,
"HeapHashMap");
static_assert(
WTF::IsGarbageCollectedType<HeapVector<Member<IntWrapper>>>::value,
"HeapVector");
static_assert(
WTF::IsGarbageCollectedType<HeapDeque<Member<IntWrapper>>>::value,
"HeapDeque");
static_assert(WTF::IsGarbageCollectedType<
HeapTerminatedArray<Member<IntWrapper>>>::value,
"HeapTerminatedArray");
}
TEST(HeapTest, HeapHashMapCallsDestructor) {
String string = "string";
EXPECT_TRUE(string.Impl()->HasOneRef());
HeapHashMap<KeyWithCopyingMoveConstructor, Member<IntWrapper>> map;
EXPECT_TRUE(string.Impl()->HasOneRef());
for (int i = 1; i <= 100; ++i) {
KeyWithCopyingMoveConstructor key(i, string);
map.insert(key, IntWrapper::Create(i));
}
EXPECT_FALSE(string.Impl()->HasOneRef());
map.clear();
EXPECT_TRUE(string.Impl()->HasOneRef());
}
class DoublyLinkedListNodeImpl
: public GarbageCollectedFinalized<DoublyLinkedListNodeImpl>,
public DoublyLinkedListNode<DoublyLinkedListNodeImpl> {
public:
DoublyLinkedListNodeImpl() = default;
static DoublyLinkedListNodeImpl* Create() {
return new DoublyLinkedListNodeImpl();
}
static int destructor_calls_;
~DoublyLinkedListNodeImpl() { ++destructor_calls_; }
void Trace(Visitor* visitor) {
visitor->Trace(prev_);
visitor->Trace(next_);
}
private:
friend class WTF::DoublyLinkedListNode<DoublyLinkedListNodeImpl>;
Member<DoublyLinkedListNodeImpl> prev_;
Member<DoublyLinkedListNodeImpl> next_;
};
int DoublyLinkedListNodeImpl::destructor_calls_ = 0;
template <typename T>
class HeapDoublyLinkedListContainer
: public GarbageCollected<HeapDoublyLinkedListContainer<T>> {
public:
static HeapDoublyLinkedListContainer<T>* Create() {
return new HeapDoublyLinkedListContainer<T>();
}
HeapDoublyLinkedListContainer<T>() = default;
HeapDoublyLinkedList<T> list_;
void Trace(Visitor* visitor) { visitor->Trace(list_); }
};
TEST(HeapTest, HeapDoublyLinkedList) {
Persistent<HeapDoublyLinkedListContainer<DoublyLinkedListNodeImpl>>
container =
HeapDoublyLinkedListContainer<DoublyLinkedListNodeImpl>::Create();
DoublyLinkedListNodeImpl::destructor_calls_ = 0;
container->list_.Append(DoublyLinkedListNodeImpl::Create());
container->list_.Append(DoublyLinkedListNodeImpl::Create());
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 0);
container->list_.RemoveHead();
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 1);
container->list_.RemoveHead();
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 2);
}
TEST(HeapTest, PersistentHeapVectorCopyAssignment) {
PersistentHeapVector<Member<IntWrapper>> vector1;
{
PersistentHeapVector<Member<IntWrapper>> vector2;
vector1 = vector2;
}
PreciselyCollectGarbage();
}
TEST(HeapTest, PromptlyFreeStackAllocatedHeapHashSet) {
NormalPageArena* normal_arena = static_cast<NormalPageArena*>(
ThreadState::Current()->Heap().Arena(BlinkGC::kHashTableArenaIndex));
CHECK(normal_arena);
Address before;
{
HeapHashSet<Member<IntWrapper>> hash_set;
hash_set.insert(new IntWrapper(0));
before = normal_arena->CurrentAllocationPoint();
}
Address after = normal_arena->CurrentAllocationPoint();
// We check the allocation point to see if promptly freed
EXPECT_NE(after, before);
}
TEST(HeapTest, PromptlyFreeStackAllocatedHeapListHashSet) {
NormalPageArena* normal_arena = static_cast<NormalPageArena*>(
ThreadState::Current()->Heap().Arena(BlinkGC::kHashTableArenaIndex));
CHECK(normal_arena);
Address before;
{
HeapListHashSet<Member<IntWrapper>> list_hash_set;
list_hash_set.insert(new IntWrapper(0));
before = normal_arena->CurrentAllocationPoint();
}
Address after = normal_arena->CurrentAllocationPoint();
// We check the allocation point to see if promptly freed
EXPECT_NE(after, before);
}
TEST(HeapTest, PromptlyFreeStackAllocatedHeapLinkedHashSet) {
NormalPageArena* normal_arena = static_cast<NormalPageArena*>(
ThreadState::Current()->Heap().Arena(BlinkGC::kHashTableArenaIndex));
CHECK(normal_arena);
Address before;
{
HeapLinkedHashSet<Member<IntWrapper>> linked_hash_set;
linked_hash_set.insert(new IntWrapper(0));
before = normal_arena->CurrentAllocationPoint();
}
Address after = normal_arena->CurrentAllocationPoint();
// We check the allocation point to see if promptly freed
EXPECT_NE(after, before);
}
} // namespace blink