blob: 331d96a68eb4a5491ccf20ecefa31e9f866e9ca1 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "wtf/ArrayBuffer.h"
#include "wtf/HashCountedSet.h"
#include "wtf/HashMap.h"
#include "wtf/HashSet.h"
#include "wtf/ListHashSet.h"
#include "wtf/MemoryInstrumentation.h"
#include "wtf/MemoryInstrumentationArrayBufferView.h"
#include "wtf/MemoryInstrumentationHashCountedSet.h"
#include "wtf/MemoryInstrumentationHashMap.h"
#include "wtf/MemoryInstrumentationHashSet.h"
#include "wtf/MemoryInstrumentationListHashSet.h"
#include "wtf/MemoryInstrumentationString.h"
#include "wtf/MemoryInstrumentationVector.h"
#include "wtf/MemoryObjectInfo.h"
#include "wtf/RefCounted.h"
#include "wtf/Vector.h"
#include "wtf/text/AtomicString.h"
#include "wtf/text/CString.h"
#include "wtf/text/StringBuffer.h"
#include "wtf/text/StringHash.h"
#include "wtf/text/StringImpl.h"
#include "wtf/text/WTFString.h"
#include <gtest/gtest.h>
namespace {
enum TestEnum { ONE = 1, TWO, THREE, MY_ENUM_MAX };
}
namespace WTF {
template<> struct DefaultHash<TestEnum> {
typedef IntHash<unsigned> Hash;
};
template<> struct HashTraits<TestEnum> : GenericHashTraits<TestEnum> {
static const bool emptyValueIsZero = true;
static const bool needsDestruction = false;
static void constructDeletedValue(TestEnum& slot) { slot = static_cast<TestEnum>(MY_ENUM_MAX + 1); }
static bool isDeletedValue(TestEnum value) { return value == (MY_ENUM_MAX + 1); }
};
}
namespace {
using WTF::MemoryObjectInfo;
using WTF::MemoryClassInfo;
using WTF::MemoryObjectType;
using WTF::MemberType;
MemoryObjectType TestType = "TestType";
class MemoryInstrumentationTestClient : public WTF::MemoryInstrumentationClient {
public:
MemoryInstrumentationTestClient() : m_links(WTF::LastMemberTypeEntry)
{
m_links[WTF::PointerMember] = 0;
m_links[WTF::ReferenceMember] = 0;
m_links[WTF::RetainingPointer] = 0;
}
virtual void countObjectSize(const void*, MemoryObjectType objectType, size_t size)
{
TypeToSizeMap::AddResult result = m_totalSizes.add(objectType, size);
if (!result.isNewEntry)
result.iterator->value += size;
}
virtual bool visited(const void* object) { return !m_visitedObjects.add(object).isNewEntry; }
virtual bool checkCountedObject(const void*) { return true; }
virtual void reportNode(const MemoryObjectInfo&) OVERRIDE { }
virtual void reportEdge(const void*, const char*, MemberType memberType) OVERRIDE
{
++m_links[memberType];
}
virtual void reportLeaf(const MemoryObjectInfo&, const char*) OVERRIDE
{
++m_links[WTF::RetainingPointer];
}
virtual void reportBaseAddress(const void*, const void*) OVERRIDE { }
virtual int registerString(const char*) OVERRIDE { return -1; }
size_t visitedObjects() const { return m_visitedObjects.size(); }
size_t totalSize(const MemoryObjectType objectType) const
{
TypeToSizeMap::const_iterator i = m_totalSizes.find(objectType);
return i == m_totalSizes.end() ? 0 : i->value;
}
size_t linksCount(const WTF::MemberType memberType) const
{
return m_links[memberType];
}
size_t reportedSizeForAllTypes() const
{
size_t size = 0;
for (TypeToSizeMap::const_iterator i = m_totalSizes.begin(); i != m_totalSizes.end(); ++i)
size += i->value;
return size;
}
private:
typedef HashMap<MemoryObjectType, size_t> TypeToSizeMap;
TypeToSizeMap m_totalSizes;
WTF::HashSet<const void*> m_visitedObjects;
WTF::Vector<size_t, WTF::LastMemberTypeEntry> m_links;
};
class InstrumentationTestImpl : public WTF::MemoryInstrumentation {
public:
explicit InstrumentationTestImpl(MemoryInstrumentationTestClient* client)
: MemoryInstrumentation(client)
, m_client(client) { }
virtual void processDeferredObjects();
virtual void deferObject(PassOwnPtr<WrapperBase>);
size_t visitedObjects() const { return m_client->visitedObjects(); }
size_t reportedSizeForAllTypes() const { return m_client->reportedSizeForAllTypes(); }
size_t totalSize(const MemoryObjectType objectType) const { return m_client->totalSize(objectType); }
size_t linksCount(const WTF::MemberType memberType) const { return m_client->linksCount(memberType); }
private:
MemoryInstrumentationTestClient* m_client;
Vector<OwnPtr<WrapperBase> > m_deferredObjects;
};
class InstrumentationTestHelper : public InstrumentationTestImpl {
public:
InstrumentationTestHelper() : InstrumentationTestImpl(&m_client) { }
private:
MemoryInstrumentationTestClient m_client;
};
void InstrumentationTestImpl::processDeferredObjects()
{
while (!m_deferredObjects.isEmpty()) {
OwnPtr<WrapperBase> pointer = m_deferredObjects.last().release();
m_deferredObjects.removeLast();
pointer->process(this);
}
}
void InstrumentationTestImpl::deferObject(PassOwnPtr<WrapperBase> pointer)
{
m_deferredObjects.append(pointer);
}
class NotInstrumented {
public:
NotInstrumented(const char* = 0) { }
char m_data[42];
};
class Instrumented {
public:
Instrumented() : m_notInstrumented(new NotInstrumented) { }
virtual ~Instrumented() { disposeOwnedObject(); }
virtual void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
memoryObjectInfo->setClassName("Instrumented");
info.addMember(m_notInstrumented, "m_notInstrumented", WTF::RetainingPointer);
}
void disposeOwnedObject()
{
delete m_notInstrumented;
m_notInstrumented = 0;
}
NotInstrumented* m_notInstrumented;
};
TEST(MemoryInstrumentationTest, sizeOf)
{
InstrumentationTestHelper helper;
Instrumented instrumented;
helper.addRootObject(instrumented);
EXPECT_EQ(sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
EXPECT_EQ(1u, helper.linksCount(WTF::RetainingPointer));
}
TEST(MemoryInstrumentationTest, nullCheck)
{
InstrumentationTestHelper helper;
Instrumented* instrumented = 0;
helper.addRootObject(instrumented);
EXPECT_EQ(0u, helper.reportedSizeForAllTypes());
EXPECT_EQ(0u, helper.visitedObjects());
EXPECT_EQ(0u, helper.linksCount(WTF::RetainingPointer));
}
TEST(MemoryInstrumentationTest, ptrVsRef)
{
{
InstrumentationTestHelper helper;
Instrumented instrumented;
helper.addRootObject(&instrumented);
EXPECT_EQ(sizeof(Instrumented) + sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
EXPECT_EQ(1u, helper.linksCount(WTF::RetainingPointer));
}
{
InstrumentationTestHelper helper;
Instrumented instrumented;
helper.addRootObject(instrumented);
EXPECT_EQ(sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
EXPECT_EQ(1u, helper.linksCount(WTF::RetainingPointer));
}
}
class InstrumentedWithOwnPtr : public Instrumented {
public:
InstrumentedWithOwnPtr() : m_notInstrumentedOwnPtr(adoptPtr(new NotInstrumented)) { }
virtual void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
Instrumented::reportMemoryUsage(memoryObjectInfo);
info.addMember(m_notInstrumentedOwnPtr);
}
OwnPtr<NotInstrumented> m_notInstrumentedOwnPtr;
};
TEST(MemoryInstrumentationTest, ownPtrNotInstrumented)
{
InstrumentationTestHelper helper;
InstrumentedWithOwnPtr instrumentedWithOwnPtr;
helper.addRootObject(instrumentedWithOwnPtr);
EXPECT_EQ(2u * sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
EXPECT_EQ(2u, helper.linksCount(WTF::RetainingPointer));
}
class InstrumentedUndefined {
public:
InstrumentedUndefined() : m_data(0) { }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this);
}
int m_data;
};
class InstrumentedDOM {
public:
InstrumentedDOM() : m_instrumentedUndefined(adoptPtr(new InstrumentedUndefined)) { }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_instrumentedUndefined);
}
OwnPtr<InstrumentedUndefined> m_instrumentedUndefined;
};
TEST(MemoryInstrumentationTest, ownerTypePropagation)
{
InstrumentationTestHelper helper;
OwnPtr<InstrumentedDOM> instrumentedDOM(adoptPtr(new InstrumentedDOM));
helper.addRootObject(instrumentedDOM.get());
EXPECT_EQ(sizeof(InstrumentedDOM) + sizeof(InstrumentedUndefined), helper.reportedSizeForAllTypes());
EXPECT_EQ(sizeof(InstrumentedDOM) + sizeof(InstrumentedUndefined), helper.totalSize(TestType));
EXPECT_EQ(2u, helper.visitedObjects());
}
class NonVirtualInstrumented {
public:
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_instrumented);
}
Instrumented m_instrumented;
};
TEST(MemoryInstrumentationTest, visitFirstMemberInNonVirtualClass)
{
InstrumentationTestHelper helper;
NonVirtualInstrumented nonVirtualInstrumented;
helper.addRootObject(&nonVirtualInstrumented);
EXPECT_EQ(sizeof(NonVirtualInstrumented) + sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
template<typename T>
class InstrumentedOwner {
public:
template<typename V>
InstrumentedOwner(const V& value) : m_value(value) { }
InstrumentedOwner() { }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_value, "value", WTF::RetainingPointer);
}
T m_value;
};
TEST(MemoryInstrumentationTest, visitStrings)
{
{ // 8-bit string.
InstrumentationTestHelper helper;
InstrumentedOwner<String> stringInstrumentedOwner("String");
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) + stringInstrumentedOwner.m_value.length(), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
{ // 8-bit string with 16bit shadow.
InstrumentationTestHelper helper;
InstrumentedOwner<String> stringInstrumentedOwner("String");
stringInstrumentedOwner.m_value.bloatedCharacters();
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) + stringInstrumentedOwner.m_value.length() * (sizeof(LChar) + sizeof(UChar)), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
{ // 16 bit string.
InstrumentationTestHelper helper;
String string("String");
InstrumentedOwner<String> stringInstrumentedOwner(String(string.bloatedCharacters(), string.length()));
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) + stringInstrumentedOwner.m_value.length() * sizeof(UChar), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
{ // ASCIILiteral
InstrumentationTestHelper helper;
ASCIILiteral literal("String");
InstrumentedOwner<String> stringInstrumentedOwner(literal);
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
{ // Substring
InstrumentationTestHelper helper;
String baseString("String");
baseString.bloatedCharacters(); // Force 16 shadow creation.
InstrumentedOwner<String> stringInstrumentedOwner(baseString.substringSharingImpl(1, 4));
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) * 2 + baseString.length() * (sizeof(LChar) + sizeof(UChar)), helper.reportedSizeForAllTypes());
EXPECT_EQ(3u, helper.visitedObjects());
}
{ // Owned buffer.
InstrumentationTestHelper helper;
StringBuffer<LChar> buffer(6);
InstrumentedOwner<String> stringInstrumentedOwner(String::adopt(buffer));
helper.addRootObject(stringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) + stringInstrumentedOwner.m_value.length(), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
{
InstrumentationTestHelper helper;
InstrumentedOwner<AtomicString> atomicStringInstrumentedOwner("AtomicString");
atomicStringInstrumentedOwner.m_value.string().bloatedCharacters(); // Force 16bit shadow creation.
helper.addRootObject(atomicStringInstrumentedOwner);
EXPECT_EQ(sizeof(StringImpl) + atomicStringInstrumentedOwner.m_value.length() * (sizeof(LChar) + sizeof(UChar)), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
{
InstrumentationTestHelper helper;
InstrumentedOwner<CString> cStringInstrumentedOwner("CString");
helper.addRootObject(cStringInstrumentedOwner);
EXPECT_EQ(sizeof(WTF::CStringBuffer) + cStringInstrumentedOwner.m_value.length(), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
}
class TwoPointersToRefPtr {
public:
TwoPointersToRefPtr(const RefPtr<StringImpl>& value) : m_ptr1(&value), m_ptr2(&value) { }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_ptr1);
info.addMember(m_ptr2);
}
const RefPtr<StringImpl>* m_ptr1;
const RefPtr<StringImpl>* m_ptr2;
};
TEST(MemoryInstrumentationTest, refPtrPtr)
{
InstrumentationTestHelper helper;
RefPtr<StringImpl> refPtr;
TwoPointersToRefPtr root(refPtr);
helper.addRootObject(root);
EXPECT_EQ(sizeof(RefPtr<StringImpl>), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
class TwoPointersToOwnPtr {
public:
TwoPointersToOwnPtr(const OwnPtr<NotInstrumented>& value) : m_ptr1(&value), m_ptr2(&value) { }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_ptr1);
info.addMember(m_ptr2);
}
const OwnPtr<NotInstrumented>* m_ptr1;
const OwnPtr<NotInstrumented>* m_ptr2;
};
TEST(MemoryInstrumentationTest, ownPtrPtr)
{
InstrumentationTestHelper helper;
OwnPtr<NotInstrumented> ownPtr;
TwoPointersToOwnPtr root(ownPtr);
helper.addRootObject(root);
EXPECT_EQ(sizeof(OwnPtr<NotInstrumented>), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
template<typename T>
class InstrumentedTemplate {
public:
template<typename V>
InstrumentedTemplate(const V& value) : m_value(value) { }
template<typename MemoryObjectInfo>
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
typename MemoryObjectInfo::ClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_value);
}
T m_value;
};
TEST(MemoryInstrumentationTest, detectReportMemoryUsageMethod)
{
{
InstrumentationTestHelper helper;
OwnPtr<InstrumentedTemplate<String> > value(adoptPtr(new InstrumentedTemplate<String>("")));
InstrumentedOwner<InstrumentedTemplate<String>* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(InstrumentedTemplate<String>) + sizeof(StringImpl), helper.reportedSizeForAllTypes());
// FIXME: it is failing on Chromium Canary bots but works fine locally.
// EXPECT_EQ(2, helper.visitedObjects());
}
{
InstrumentationTestHelper helper;
OwnPtr<InstrumentedTemplate<NotInstrumented> > value(adoptPtr(new InstrumentedTemplate<NotInstrumented>("")));
InstrumentedOwner<InstrumentedTemplate<NotInstrumented>* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(InstrumentedTemplate<NotInstrumented>), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
}
TEST(MemoryInstrumentationTest, vectorZeroInlineCapacity)
{
InstrumentationTestHelper helper;
InstrumentedOwner<Vector<int> > vectorOwner(16);
helper.addRootObject(vectorOwner);
EXPECT_EQ(16 * sizeof(int), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, vectorFieldWithInlineCapacity)
{
InstrumentationTestHelper helper;
InstrumentedOwner<Vector<int, 4> > vectorOwner;
helper.addRootObject(vectorOwner);
EXPECT_EQ(static_cast<size_t>(0), helper.reportedSizeForAllTypes());
EXPECT_EQ(0u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, vectorFieldWithInlineCapacityResized)
{
InstrumentationTestHelper helper;
InstrumentedOwner<Vector<int, 4> > vectorOwner;
vectorOwner.m_value.reserveCapacity(8);
helper.addRootObject(vectorOwner);
EXPECT_EQ(8u * sizeof(int), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, heapAllocatedVectorWithInlineCapacity)
{
InstrumentationTestHelper helper;
InstrumentedOwner<OwnPtr<Vector<int, 4> > > vectorOwner;
vectorOwner.m_value = adoptPtr(new Vector<int, 4>());
helper.addRootObject(vectorOwner);
EXPECT_EQ(sizeof(Vector<int, 4>), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, heapAllocatedVectorWithInlineCapacityResized)
{
InstrumentationTestHelper helper;
InstrumentedOwner<OwnPtr<Vector<int, 4> > > vectorOwner;
vectorOwner.m_value = adoptPtr(new Vector<int, 4>());
vectorOwner.m_value->reserveCapacity(8);
helper.addRootObject(vectorOwner);
EXPECT_EQ(8u * sizeof(int) + sizeof(Vector<int, 4>), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, vectorWithInstrumentedType)
{
InstrumentationTestHelper helper;
typedef Vector<String> StringVector;
OwnPtr<StringVector> value = adoptPtr(new StringVector());
size_t count = 10;
for (size_t i = 0; i < count; ++i)
value->append("string");
InstrumentedOwner<StringVector* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(StringVector) + sizeof(String) * value->capacity() + (sizeof(StringImpl) + 6) * value->size(), helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 2, (size_t)helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashSetWithInstrumentedType)
{
InstrumentationTestHelper helper;
typedef HashSet<String> ValueType;
OwnPtr<ValueType> value = adoptPtr(new ValueType());
size_t count = 10;
for (size_t i = 0; i < count; ++i)
value->add(String::number(i));
InstrumentedOwner<ValueType* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(ValueType) + sizeof(String) * value->capacity() + (sizeof(StringImpl) + 1) * value->size(), helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 1, (size_t)helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashMapWithNotInstrumentedKeysAndValues)
{
InstrumentationTestHelper helper;
typedef HashMap<int, int> IntToIntMap;
OwnPtr<IntToIntMap> value = adoptPtr(new IntToIntMap());
size_t count = 10;
for (size_t i = 1; i <= count; ++i)
value->set(i, i);
InstrumentedOwner<IntToIntMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(IntToIntMap) + sizeof(IntToIntMap::ValueType) * value->capacity(), helper.reportedSizeForAllTypes());
EXPECT_EQ(1u, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashMapWithInstrumentedKeys)
{
InstrumentationTestHelper helper;
typedef HashMap<String, int> StringToIntMap;
OwnPtr<StringToIntMap> value = adoptPtr(new StringToIntMap());
size_t count = 10;
for (size_t i = 10; i < 10 + count; ++i)
value->set(String::number(i), i);
InstrumentedOwner<StringToIntMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(StringToIntMap) + sizeof(StringToIntMap::ValueType) * value->capacity() + (sizeof(StringImpl) + 2) * value->size(), helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashMapWithInstrumentedValues)
{
InstrumentationTestHelper helper;
typedef HashMap<int, String> IntToStringMap;
OwnPtr<IntToStringMap> value = adoptPtr(new IntToStringMap());
size_t count = 10;
for (size_t i = 10; i < 10 + count; ++i)
value->set(i, String::number(i));
InstrumentedOwner<IntToStringMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(IntToStringMap) + sizeof(IntToStringMap::ValueType) * value->capacity() + (sizeof(StringImpl) + 2) * value->size(), helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashMapWithInstrumentedKeysAndValues)
{
InstrumentationTestHelper helper;
typedef HashMap<String, String> StringToStringMap;
OwnPtr<StringToStringMap> value = adoptPtr(new StringToStringMap());
size_t count = 10;
for (size_t i = 10; i < 10 + count; ++i)
value->set(String::number(count + i), String::number(i));
InstrumentedOwner<StringToStringMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(StringToStringMap) + sizeof(StringToStringMap::ValueType) * value->capacity() + 2 * (sizeof(StringImpl) + 2) * value->size(), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u * count + 1, helper.visitedObjects());
}
class InstrumentedRefCounted : public RefCounted<InstrumentedRefCounted> {
public:
InstrumentedRefCounted() : m_notInstrumented(new NotInstrumented) { }
~InstrumentedRefCounted() { delete m_notInstrumented; }
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_notInstrumented, "m_notInstrumented", WTF::RetainingPointer);
}
private:
NotInstrumented* m_notInstrumented;
};
TEST(MemoryInstrumentationTest, hashMapWithInstrumentedPointerKeysAndPointerValues)
{
InstrumentationTestHelper helper;
typedef HashMap<RefPtr<InstrumentedRefCounted>, RefPtr<InstrumentedRefCounted> > InstrumentedToInstrumentedMap;
OwnPtr<InstrumentedToInstrumentedMap> value(adoptPtr(new InstrumentedToInstrumentedMap()));
size_t count = 10;
for (size_t i = 0; i < count; ++i)
value->set(adoptRef(new InstrumentedRefCounted()), adoptRef(new InstrumentedRefCounted()));
InstrumentedOwner<InstrumentedToInstrumentedMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(InstrumentedToInstrumentedMap)
+ sizeof(InstrumentedToInstrumentedMap::ValueType) * value->capacity()
+ 2 * (sizeof(InstrumentedRefCounted) + sizeof(NotInstrumented)) * value->size(),
helper.reportedSizeForAllTypes());
EXPECT_EQ(2u * 2u * count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, listHashSetWithInstrumentedType)
{
InstrumentationTestHelper helper;
typedef ListHashSet<String, 8> TestSet;
OwnPtr<TestSet> value = adoptPtr(new TestSet());
size_t count = 10;
for (size_t i = 0; i < count; ++i)
value->add(String::number(i));
InstrumentedOwner<TestSet* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(TestSet) + sizeof(String) * value->capacity() + (sizeof(StringImpl) + 1 * sizeof(LChar)) * count +
sizeof(WTF::ListHashSetNodeAllocator<String, 8>) + sizeof(WTF::ListHashSetNode<String, 8>) * (count - 8),
helper.reportedSizeForAllTypes());
EXPECT_EQ(1 + count, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, listHashSetWithInstrumentedTypeAfterValuesRemoval)
{
InstrumentationTestHelper helper;
typedef ListHashSet<String, 8> TestSet;
OwnPtr<TestSet> value = adoptPtr(new TestSet());
size_t count = 20;
for (size_t i = 0; i < count; ++i)
value->add(String::number(i));
// Remove 10 values, 8 of which were allocated in the internal buffer.
for (size_t i = 0; i < 10; ++i)
value->remove(String::number(i));
InstrumentedOwner<TestSet* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(TestSet) + sizeof(String) * value->capacity() + (sizeof(StringImpl) + 2 * sizeof(LChar)) * (count - 10) +
sizeof(WTF::ListHashSetNodeAllocator<String, 8>) + sizeof(WTF::ListHashSetNode<String, 8>) * (count - 10),
helper.reportedSizeForAllTypes());
EXPECT_EQ(1 + (count - 10), helper.visitedObjects());
}
class InstrumentedConvertibleToInt : public RefCounted<InstrumentedConvertibleToInt> {
public:
InstrumentedConvertibleToInt() : m_notInstrumented(new NotInstrumented) { }
virtual ~InstrumentedConvertibleToInt() { delete m_notInstrumented; }
virtual void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_notInstrumented);
}
operator int() const { return 2012; }
NotInstrumented* m_notInstrumented;
};
// This test checks if reportMemoryUsage method will be called on a class
// that can be implicitly cast to int. Currently objects of such classes are
// treated as integers when they are stored in a HashMap by value and
// reportMemoryUsage will not be called on them. We may fix that later.
TEST(MemoryInstrumentationTest, hashMapWithValuesConvertibleToInt)
{
InstrumentationTestHelper helper;
typedef HashMap<RefPtr<InstrumentedConvertibleToInt>, int> TestMap;
OwnPtr<TestMap> value(adoptPtr(new TestMap()));
size_t count = 10;
for (size_t i = 0; i < count; ++i)
value->set(adoptRef(new InstrumentedConvertibleToInt()), 0);
InstrumentedOwner<TestMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(TestMap) + sizeof(TestMap::ValueType) * value->capacity() +
sizeof(InstrumentedConvertibleToInt) * count /* + sizeof(NotInstrumented) * count */, helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashMapWithEnumKeysAndInstrumentedValues)
{
InstrumentationTestHelper helper;
typedef HashMap<TestEnum, String> EnumToStringMap;
OwnPtr<EnumToStringMap> value(adoptPtr(new EnumToStringMap()));
size_t count = MY_ENUM_MAX;
for (size_t i = ONE; i <= count; ++i)
value->set(static_cast<TestEnum>(i), String::number(i));
InstrumentedOwner<EnumToStringMap* > root(value.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(EnumToStringMap)
+ sizeof(EnumToStringMap::ValueType) * value->capacity()
+ (sizeof(StringImpl) + 1) * value->size(),
helper.reportedSizeForAllTypes());
EXPECT_EQ(count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, hashCountedSetWithInstrumentedValues)
{
InstrumentationTestHelper helper;
typedef HashCountedSet<RefPtr<InstrumentedRefCounted> > TestSet;
OwnPtr<TestSet> set(adoptPtr(new TestSet()));
size_t count = 10;
for (size_t i = 0; i < count; ++i) {
RefPtr<InstrumentedRefCounted> instrumentedRefCounted = adoptRef(new InstrumentedRefCounted());
for (size_t j = 0; j <= i; j++)
set->add(instrumentedRefCounted);
}
InstrumentedOwner<TestSet* > root(set.get());
helper.addRootObject(root);
EXPECT_EQ(sizeof(TestSet)
+ sizeof(HashMap<RefPtr<InstrumentedRefCounted>, unsigned>::ValueType) * set->capacity()
+ (sizeof(InstrumentedRefCounted) + sizeof(NotInstrumented)) * set->size(),
helper.reportedSizeForAllTypes());
EXPECT_EQ(2u * count + 1, helper.visitedObjects());
}
TEST(MemoryInstrumentationTest, arrayBuffer)
{
InstrumentationTestHelper helper;
typedef InstrumentedTemplate<RefPtr<ArrayBuffer> > ValueType;
ValueType value(ArrayBuffer::create(1000, sizeof(int)));
helper.addRootObject(value);
EXPECT_EQ(sizeof(int) * 1000 + sizeof(ArrayBuffer), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
EXPECT_EQ(2u, helper.linksCount(WTF::RetainingPointer));
}
class AncestorWithVirtualMethod {
public:
virtual char* data() { return m_data; }
private:
char m_data[10];
};
class ClassWithTwoAncestors : public AncestorWithVirtualMethod, public Instrumented {
public:
virtual void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
Instrumented::reportMemoryUsage(memoryObjectInfo);
}
};
TEST(MemoryInstrumentationTest, instrumentedWithMultipleAncestors)
{
InstrumentationTestHelper helper;
OwnPtr<ClassWithTwoAncestors> instance = adoptPtr(new ClassWithTwoAncestors());
ClassWithTwoAncestors* descendantPointer = instance.get();
InstrumentedOwner<ClassWithTwoAncestors*> descendantPointerOwner(descendantPointer);
Instrumented* ancestorPointer = descendantPointer;
InstrumentedOwner<Instrumented*> ancestorPointerOwner(ancestorPointer);
EXPECT_NE(static_cast<void*>(ancestorPointer), static_cast<void*>(descendantPointer));
helper.addRootObject(descendantPointerOwner);
helper.addRootObject(ancestorPointerOwner);
EXPECT_EQ(sizeof(ClassWithTwoAncestors) + sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(3u, helper.visitedObjects());
}
class CheckCountedObjectsClient : public MemoryInstrumentationTestClient {
public:
CheckCountedObjectsClient(const void* expectedPointer) : m_expectedPointer(expectedPointer), m_expectedPointerFound(false) { }
virtual bool checkCountedObject(const void* pointer)
{
EXPECT_EQ(pointer, m_expectedPointer);
m_expectedPointerFound = true;
return true;
}
bool expectedPointerFound() { return m_expectedPointerFound; }
private:
const void* m_expectedPointer;
bool m_expectedPointerFound;
};
TEST(MemoryInstrumentationTest, checkCountedObjectWithMultipleAncestors)
{
OwnPtr<ClassWithTwoAncestors> instance = adoptPtr(new ClassWithTwoAncestors());
instance->disposeOwnedObject();
ClassWithTwoAncestors* descendantPointer = instance.get();
InstrumentedOwner<ClassWithTwoAncestors*> descendantPointerOwner(descendantPointer);
Instrumented* ancestorPointer = descendantPointer;
InstrumentedOwner<Instrumented*> ancestorPointerOwner(ancestorPointer);
EXPECT_NE(static_cast<void*>(ancestorPointer), static_cast<void*>(descendantPointer));
CheckCountedObjectsClient client(instance.get());
InstrumentationTestImpl instrumentation(&client);
instrumentation.addRootObject(descendantPointerOwner);
instrumentation.addRootObject(ancestorPointerOwner);
EXPECT_TRUE(client.expectedPointerFound());
}
class TwoPointersToSameInsrumented {
public:
TwoPointersToSameInsrumented()
: m_ownPtr(adoptPtr(new ClassWithTwoAncestors()))
, m_baseClassPtr(m_ownPtr.get())
{
EXPECT_NE(static_cast<void*>(m_ownPtr.get()), static_cast<void*>(m_baseClassPtr));
}
void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, TestType);
info.addMember(m_ownPtr);
info.addMember(m_baseClassPtr);
}
private:
OwnPtr<ClassWithTwoAncestors> m_ownPtr;
Instrumented* m_baseClassPtr;
};
class CountLinksFromInstrumentedObject : public MemoryInstrumentationTestClient {
public:
CountLinksFromInstrumentedObject() : m_linkCount(0) { }
virtual void reportEdge(const void*, const char* name, MemberType) OVERRIDE
{
if (name && !strcmp("m_notInstrumented", name))
m_linkCount++;
}
int linkCount() const { return m_linkCount; }
private:
int m_linkCount;
};
TEST(MemoryInstrumentationTest, doNotReportEdgeTwice)
{
OwnPtr<TwoPointersToSameInsrumented> instance = adoptPtr(new TwoPointersToSameInsrumented());
CountLinksFromInstrumentedObject client;
InstrumentationTestImpl instrumentation(&client);
instrumentation.addRootObject(instance.get());
EXPECT_EQ(1, client.linkCount());
}
class DerivedClass : public Instrumented {
public:
size_t m_member;
};
TEST(MemoryInstrumentationTest, detectBaseClassInstrumentation)
{
OwnPtr<DerivedClass> instance = adoptPtr(new DerivedClass());
InstrumentationTestHelper helper;
helper.addRootObject(instance.get(), TestType);
EXPECT_EQ(sizeof(Instrumented) + sizeof(NotInstrumented), helper.reportedSizeForAllTypes());
EXPECT_EQ(2u, helper.visitedObjects());
}
} // namespace