runtime/heap-profiler-test.cpp (1,210 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "heap-profiler.h" #include "gtest/gtest.h" #include "dict-builtins.h" #include "object-builtins.h" #include "test-utils.h" namespace py { namespace testing { using HeapProfilerTest = RuntimeFixture; using HeapProfilerDeathTest = RuntimeFixture; TEST_F(HeapProfilerTest, ConstructorCreatesEmptyBuffer) { HeapProfiler::Buffer buffer; EXPECT_EQ(buffer.size(), 0); EXPECT_EQ(buffer.data(), nullptr); } TEST_F(HeapProfilerTest, WriteWithEmptyBufferAllocatesSpace) { HeapProfiler::Buffer buffer; byte buf[] = {0, 1, 2, 3}; buffer.write(buf, 4); EXPECT_NE(buffer.data(), nullptr); EXPECT_EQ(buffer.size(), 4); EXPECT_EQ(std::memcmp(buffer.data(), buf, 4), 0); } static void testWriter(const void* data, word size, void* stream) { Vector<byte>* result = reinterpret_cast<Vector<byte>*>(stream); for (word i = 0; i < size; i++) { result->push_back(reinterpret_cast<const byte*>(data)[i]); } } static uint8_t read8(const Vector<byte>& src, word* pos) { EXPECT_LT(*pos, src.size()); return src[(*pos)++]; } static int16_t read16(const Vector<byte>& src, word* pos) { EXPECT_LT(*pos + 1, src.size()); uint16_t result = src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; return result; } static int32_t read32(const Vector<byte>& src, word* pos) { EXPECT_LT(*pos + 3, src.size()); int32_t result = src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; return result; } static uint32_t readu32(const Vector<byte>& src, word* pos) { return read32(src, pos); } static int64_t read64(const Vector<byte>& src, word* pos) { EXPECT_LT(*pos + 7, src.size()); int64_t result = src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; result <<= 8; result |= src[(*pos)++]; return result; } static uint64_t readu64(const Vector<byte>& data, word* pos) { return read64(data, pos); } TEST_F(HeapProfilerTest, WriteCallsWriteCallback) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); byte buf[] = {0, 1, 2, 3}; profiler.write(buf, 4); word pos = 0; EXPECT_EQ(read8(result, &pos), 0); EXPECT_EQ(read8(result, &pos), 1); EXPECT_EQ(read8(result, &pos), 2); EXPECT_EQ(read8(result, &pos), 3); EXPECT_EQ(pos, result.size()); } static const char* tagStr(byte tag) { switch (tag) { case HeapProfiler::Tag::kStringInUtf8: return "STRING IN UTF8"; case HeapProfiler::Tag::kLoadClass: return "LOAD CLASS"; case HeapProfiler::Tag::kStackTrace: return "STACK TRACE"; case HeapProfiler::Tag::kHeapDumpSegment: return "HEAP DUMP SEGMENT"; case HeapProfiler::Tag::kHeapDumpEnd: return "HEAP DUMP END"; default: return "<UNKNOWN>"; } } static ::testing::AssertionResult readTag(const Vector<byte>& result, word* pos, HeapProfiler::Tag expected) { EXPECT_LT(*pos, result.size()); byte tag = result[(*pos)++]; if (tag != expected) { return ::testing::AssertionFailure() << "expected " << tagStr(expected) << " but found " << tagStr(tag) << " (" << tag << ")"; } return ::testing::AssertionSuccess(); } static ::testing::AssertionResult readStringLiteral(const Vector<byte>& result, word* pos, const char* c_str) { for (word char_idx = 0; *c_str != '\0'; (*pos)++, char_idx++, c_str++) { if (*pos >= result.size()) { return ::testing::AssertionFailure() << "output (length " << result.size() << ") not long enough to read c_str '" << c_str << "'"; } char c = result[*pos]; if (c != *c_str) { return ::testing::AssertionFailure() << "char " << char_idx << " ('" << c << "') differs from expected ('" << *c_str << "')"; } } return ::testing::AssertionSuccess(); } static ::testing::AssertionResult readStringInUtf8(const Vector<byte>& result, word* pos, uword address, const char* value) { EXPECT_TRUE(readTag(result, pos, HeapProfiler::kStringInUtf8)); EXPECT_EQ(read32(result, pos), 0); EXPECT_EQ(readu32(result, pos), std::strlen(value) + kPointerSize); EXPECT_EQ(readu64(result, pos), address); EXPECT_TRUE(readStringLiteral(result, pos, value)); return ::testing::AssertionSuccess(); } TEST_F(HeapProfilerTest, StringIdWritesStringInUTF8Once) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawStr str = Str::cast(SmallStr::fromCStr("foo")); EXPECT_EQ(profiler.stringId(str), str.raw()); word pos = 0; EXPECT_TRUE(readStringInUtf8(result, &pos, str.raw(), "foo")); EXPECT_EQ(pos, result.size()); // Size shouldn't change EXPECT_EQ(profiler.stringId(str), str.raw()); EXPECT_EQ(pos, result.size()); } static ::testing::AssertionResult readLoadClass(const Vector<byte>& result, word* pos, uword id, uword name_id) { EXPECT_TRUE(readTag(result, pos, HeapProfiler::kLoadClass)); EXPECT_EQ(read32(result, pos), 0); // time EXPECT_EQ(read32(result, pos), 24); // data length EXPECT_EQ(read32(result, pos), 1); // class serial number EXPECT_EQ(readu64(result, pos), id); // class object ID EXPECT_EQ(read32(result, pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, pos), name_id); // class name string ID return ::testing::AssertionSuccess(); } TEST_F(HeapProfilerTest, ClassIdWritesLoadClassOnce) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawLayout layout = Layout::cast(runtime_->layoutAt(LayoutId::kTuple)); EXPECT_EQ(profiler.classId(layout), layout.raw()); word pos = 0; uword tuple_address = runtime_->symbols()->at(ID(tuple)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, tuple_address, "tuple")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), tuple_address)); EXPECT_EQ(pos, result.size()); // Size shouldn't change EXPECT_EQ(profiler.classId(layout), layout.raw()); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteStringInUTF8WithLargeStrWritesStringRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); Str str(&scope, runtime_->newStrFromCStr("deadbeef")); profiler.writeStringInUTF8(*str); word pos = 0; EXPECT_TRUE(readStringInUtf8(result, &pos, str.raw(), "deadbeef")); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteCStringInUTF8WritesStringRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); const char* str = "deadbeef"; profiler.writeCStringInUTF8(str); word pos = 0; EXPECT_TRUE( readStringInUtf8(result, &pos, reinterpret_cast<uword>(str), str)); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteEmptyRecordWritesRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), &profiler); } word pos = 0; EXPECT_EQ(read8(result, &pos), 0xa); // tag EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 0); // length EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteFakeStackTraceWritesStackTrace) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); profiler.writeFakeStackTrace(); word pos = 0; EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kStackTrace)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 12); // data length EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(read32(result, &pos), 0); // thread serial number EXPECT_EQ(read32(result, &pos), 0); // number of frames EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteLoadClassWritesLoadClassRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawLayout layout = Layout::cast(runtime_->layoutAt(LayoutId::kTuple)); profiler.writeLoadClass(layout); word pos = 0; uword tuple_address = runtime_->symbols()->at(ID(tuple)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, tuple_address, "tuple")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), tuple_address)); EXPECT_EQ(pos, result.size()); } static const char* subtagStr(byte subtag) { switch (subtag) { case HeapProfiler::Subtag::kRootJniGlobal: return "ROOT JNI GLOBAL"; case HeapProfiler::Subtag::kRootJniLocal: return "ROOT JNI LOCAL"; case HeapProfiler::Subtag::kRootJavaFrame: return "ROOT JAVA FRAME"; case HeapProfiler::Subtag::kRootNativeStack: return "ROOT NATIVE STACK"; case HeapProfiler::Subtag::kRootStickyClass: return "ROOT STICKY CLASS"; case HeapProfiler::Subtag::kRootThreadBlock: return "ROOT THREAD BLOCK"; case HeapProfiler::Subtag::kRootMonitorUsed: return "ROOT MONITOR USED"; case HeapProfiler::Subtag::kRootThreadObject: return "ROOT THREAD OBJECT"; case HeapProfiler::Subtag::kRootUnknown: return "ROOT UNKNOWN"; case HeapProfiler::Subtag::kClassDump: return "CLASS DUMP"; case HeapProfiler::Subtag::kInstanceDump: return "INSTANCE DUMP"; case HeapProfiler::Subtag::kObjectArrayDump: return "OBJECT ARRAY DUMP"; case HeapProfiler::Subtag::kPrimitiveArrayDump: return "PRIMITIVE ARRAY DUMP"; default: return "<UNKNOWN>"; } } static ::testing::AssertionResult readSubtag(const Vector<byte>& result, word* pos, HeapProfiler::Subtag expected) { EXPECT_LT(*pos, result.size()); byte tag = result[(*pos)++]; if (tag != expected) { return ::testing::AssertionFailure() << "expected " << subtagStr(expected) << " but found " << subtagStr(tag) << " (" << tag << ")"; } return ::testing::AssertionSuccess(); } static ::testing::AssertionResult readClassDumpPrelude( const Vector<byte>& result, word* pos, uword layout, uword super_layout) { EXPECT_EQ(readu64(result, pos), layout); // class object ID EXPECT_EQ(read32(result, pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, pos), super_layout); // super class object ID EXPECT_EQ(read64(result, pos), 0); // class loader object ID EXPECT_EQ(read64(result, pos), 0); // signers object ID EXPECT_EQ(read64(result, pos), 0); // protection domain object ID EXPECT_EQ(read64(result, pos), 0); // reserved EXPECT_EQ(read64(result, pos), 0); // reserved return ::testing::AssertionSuccess(); } TEST_F(HeapProfilerTest, WriteThreadRootWritesRootThreadSubRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeThreadRoot(thread_); profiler.clearRecord(); } word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 17); // length EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::kRootThreadObject)); EXPECT_EQ(readu64(result, &pos), reinterpret_cast<uint64_t>(thread_)); // object id // Thread serial numbers should start from 0. JHAT and probably other tools // expect it. EXPECT_EQ(read32(result, &pos), 0); // thread serial number EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpWritesClassDumpSubRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawLayout tuple_layout = Layout::cast(runtime_->layoutAt(LayoutId::kTuple)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(tuple_layout); profiler.clearRecord(); } word pos = 0; // tuple uword tuple_address = runtime_->symbols()->at(ID(tuple)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, tuple_address, "tuple")); EXPECT_TRUE(readLoadClass(result, &pos, tuple_layout.raw(), tuple_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // _UserTuple__value uword user_tuple_value_address = runtime_->symbols()->at(ID(_UserTuple__value)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, user_tuple_value_address, "_UserTuple__value")); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 80); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kClassDump)); EXPECT_TRUE(readClassDumpPrelude(result, &pos, tuple_layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), tuple_layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 1); // number of instance fields // TODO(T61661597): Remove _UserTuple__value field from tuple layout // Field 0 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), user_tuple_value_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kObject); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpForUserClassWritesClassDumpRecord) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: def __init__(self): self.a = 1 self.b = 2 instance = C() )") .isError()); HandleScope scope(thread_); Object instance(&scope, mainModuleAt(runtime_, "instance")); Layout c_layout(&scope, runtime_->layoutAt(instance.layoutId())); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(*c_layout); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, c_layout.raw(), c_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // a word a_address = SmallStr::fromCStr("a").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, a_address, "a")); // b word b_address = SmallStr::fromCStr("b").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, b_address, "b")); // <OVERFLOW> EXPECT_TRUE(readStringInUtf8(result, &pos, reinterpret_cast<uword>(HeapProfiler::kOverflow), HeapProfiler::kOverflow)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 98); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kClassDump)); EXPECT_TRUE( readClassDumpPrelude(result, &pos, c_layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), c_layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 3); // number of instance fields // * Field 0 (u8 name, u1 type) EXPECT_EQ(read64(result, &pos), a_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kObject); // * Field 1 (u8 name, u1 type) EXPECT_EQ(read64(result, &pos), b_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kObject); // * Field 2 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), reinterpret_cast<uword>(HeapProfiler::kOverflow)); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kObject); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpWithOverflowAttributes) { HandleScope scope(thread_); Layout empty(&scope, testing::layoutCreateEmpty(thread_)); runtime_->layoutSetTupleOverflow(*empty); // Should fail to find an attribute that isn't present Object attr(&scope, Runtime::internStrFromCStr(thread_, "a")); AttributeInfo info; ASSERT_FALSE(Runtime::layoutFindAttribute(*empty, attr, &info)); // Adding a new attribute should result in a new layout being created AttributeInfo info2; Layout layout(&scope, runtime_->layoutAddAttribute(thread_, empty, attr, 0, &info2)); ASSERT_NE(*empty, *layout); EXPECT_TRUE(info2.isOverflow()); EXPECT_EQ(info2.offset(), 0); Type type(&scope, runtime_->newType()); type.setName(SmallStr::fromCStr("C")); type.setInstanceLayout(*layout); type.setInstanceLayoutId(layout.id()); layout.setDescribedType(*type); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(*layout); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), c_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // <OVERFLOW> EXPECT_TRUE(readStringInUtf8(result, &pos, reinterpret_cast<uword>(HeapProfiler::kOverflow), HeapProfiler::kOverflow)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 80); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::kClassDump)); EXPECT_TRUE( readClassDumpPrelude(result, &pos, layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 1); // number of instance fields // * Field 0 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), reinterpret_cast<uword>(HeapProfiler::kOverflow)); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kObject); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpWithDictOverflow) { HandleScope scope(thread_); // Make a new type, C Layout layout(&scope, testing::layoutCreateEmpty(thread_)); layout.setDictOverflowOffset(10); EXPECT_TRUE(layout.hasDictOverflow()); Type type(&scope, runtime_->newType()); type.setName(SmallStr::fromCStr("C")); type.setInstanceLayout(*layout); type.setInstanceLayoutId(layout.id()); layout.setDescribedType(*type); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(*layout); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), c_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 71); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::kClassDump)); EXPECT_TRUE( readClassDumpPrelude(result, &pos, layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 0); // number of instance fields EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteInstanceWithDictOverflow) { HandleScope scope(thread_); // Make a new type, C Layout layout(&scope, testing::layoutCreateEmpty(thread_)); layout.setDictOverflowOffset(10); EXPECT_FALSE(layout.hasTupleOverflow()); EXPECT_TRUE(layout.hasDictOverflow()); Type type(&scope, runtime_->newType()); type.setName(SmallStr::fromCStr("C")); type.setInstanceLayout(*layout); type.setInstanceLayoutId(layout.id()); layout.setDescribedType(*type); // Make an instance with an overflow attribute Instance instance(&scope, runtime_->newInstance(layout)); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeInstanceDump(*instance); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), c_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 25); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), instance.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 0); // number of bytes that follow EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteInstanceWithOverflowAttributes) { HandleScope scope(thread_); // Make a new type, C Layout empty(&scope, testing::layoutCreateEmpty(thread_)); runtime_->layoutSetTupleOverflow(*empty); Type type(&scope, runtime_->newType()); type.setName(SmallStr::fromCStr("C")); type.setInstanceLayout(*empty); type.setInstanceLayoutId(empty.id()); empty.setDescribedType(*type); // Make an instance with an overflow attribute Instance instance(&scope, runtime_->newInstance(empty)); Object name(&scope, Runtime::internStrFromCStr(thread_, "a")); Object value(&scope, SmallInt::fromWord(1234)); EXPECT_TRUE(instanceSetAttr(thread_, instance, name, value).isNoneType()); Layout layout(&scope, runtime_->layoutOf(*instance)); EXPECT_EQ(layout.inObjectAttributes(), runtime_->emptyTuple()); EXPECT_TRUE(layout.hasTupleOverflow()); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeInstanceDump(*instance); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, layout.raw(), c_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 33); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), instance.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), kPointerSize); // number of bytes that follow uword overflow_raw = read64(result, &pos); Tuple overflow(&scope, RawObject{overflow_raw}); EXPECT_EQ(overflow.at(0), *value); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpForFloatWritesClassDumpRecordWithOneAttribute) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawLayout float_layout = Layout::cast(runtime_->layoutAt(LayoutId::kFloat)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(float_layout); profiler.clearRecord(); } word pos = 0; // float uword float_address = runtime_->symbols()->at(ID(float)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, float_address, "float")); EXPECT_TRUE(readLoadClass(result, &pos, float_layout.raw(), float_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // value uword value_address = runtime_->symbols()->at(ID(value)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, value_address, "value")); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 80); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kClassDump)); EXPECT_TRUE(readClassDumpPrelude(result, &pos, float_layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), float_layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 1); // number of instance fields // Field 0 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), value_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kDouble); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteInstanceDumpForUserClassWritesInstanceDumpRecord) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: def __init__(self): self.a = 1 self.b = 2 instance = C() )") .isError()); HandleScope scope(thread_); Object instance(&scope, mainModuleAt(runtime_, "instance")); Layout c_layout(&scope, runtime_->layoutAt(instance.layoutId())); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeInstanceDump(Instance::cast(*instance)); profiler.clearRecord(); } word pos = 0; // C word c_address = SmallStr::fromCStr("C").raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, c_address, "C")); EXPECT_TRUE(readLoadClass(result, &pos, c_layout.raw(), c_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 49); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), instance.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), c_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 3 * kPointerSize); // number of bytes that follow EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(1).raw()); EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(2).raw()); EXPECT_EQ(readu64(result, &pos), runtime_->emptyTuple().raw()); // overflow EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteInstanceDumpForDictWritesInstanceDumpRecord) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object key(&scope, SmallStr::fromCStr("foo")); Object value(&scope, SmallStr::fromCStr("bar")); word hash = Int::cast(Interpreter::hash(thread_, key)).asWord(); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, value).isNoneType()); Layout dict_layout(&scope, runtime_->layoutAt(dict.layoutId())); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeInstanceDump(*dict); profiler.clearRecord(); } word pos = 0; // dict uword dict_address = runtime_->symbols()->at(ID(dict)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, dict_address, "dict")); EXPECT_TRUE(readLoadClass(result, &pos, dict_layout.raw(), dict_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 57); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), dict.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), dict_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 4 * kPointerSize); // number of bytes that follow EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(1).raw()); // num items EXPECT_EQ(readu64(result, &pos), dict.data().raw()); // data EXPECT_EQ(readu64(result, &pos), dict.indices().raw()); // sparse // first empty item index EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(dict.firstEmptyItemIndex()).raw()); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteInstanceDumpForFloatWritesInstanceDumpRecord) { HandleScope scope(thread_); Float obj(&scope, runtime_->newFloat(1.5)); Layout float_layout(&scope, runtime_->layoutAt(obj.layoutId())); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeFloat(*obj); profiler.clearRecord(); } word pos = 0; // float uword float_address = runtime_->symbols()->at(ID(float)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, float_address, "float")); EXPECT_TRUE(readLoadClass(result, &pos, float_layout.raw(), float_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 33); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), obj.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), float_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 1 * kPointerSize); // number of bytes that follow uint64_t value = read64(result, &pos); EXPECT_EQ(bit_cast<double>(value), obj.value()); // value EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteEllipsisWritesInstanceDumpRecord) { HandleScope scope(thread_); Ellipsis instance(&scope, runtime_->ellipsis()); Layout ellipsis_layout(&scope, runtime_->layoutAt(instance.layoutId())); Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeEllipsis(*instance); profiler.clearRecord(); } word pos = 0; // ellipsis uword ellipsis_address = runtime_->symbols()->at(ID(ellipsis)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, ellipsis_address, "ellipsis")); EXPECT_TRUE( readLoadClass(result, &pos, ellipsis_layout.raw(), ellipsis_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 25); // length // Instance dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), instance.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), ellipsis_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 0); // number of bytes that follow EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteImmediateWritesInstanceDump) { Vector<byte> result; RawObject obj = SmallInt::fromWord(1337); RawLayout smallint_layout = Layout::cast(runtime_->layoutOf(obj)); HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeImmediate(obj); profiler.clearRecord(); } word pos = 0; // smallint uword smallint_address = runtime_->symbols()->at(ID(smallint)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, smallint_address, "smallint")); EXPECT_TRUE( readLoadClass(result, &pos, smallint_layout.raw(), smallint_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 25); // length // Instance dump for 1337 EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), obj.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), smallint_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 0); // number of bytes to follow EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteFakeClassDumpWritesClassDump) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeFakeClassDump(HeapProfiler::FakeClass::kJavaLangClass, HeapProfiler::kJavaLangClass, HeapProfiler::FakeClass::kJavaLangObject); profiler.clearRecord(); } word pos = 0; // java.lang.Class uword java_lang_class_id = reinterpret_cast<uword>(HeapProfiler::kJavaLangClass); EXPECT_TRUE( readStringInUtf8(result, &pos, java_lang_class_id, "java.lang.Class")); EXPECT_TRUE(readLoadClass( result, &pos, static_cast<uword>(HeapProfiler::FakeClass::kJavaLangClass), java_lang_class_id)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 71); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kClassDump)); EXPECT_TRUE(readClassDumpPrelude( result, &pos, static_cast<uword>(HeapProfiler::FakeClass::kJavaLangClass), static_cast<uword>(HeapProfiler::FakeClass::kJavaLangObject))); EXPECT_EQ(read32(result, &pos), 0); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 0); // number of instance fields EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteFakeLoadClassWritesLoadClass) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); profiler.writeFakeLoadClass(HeapProfiler::FakeClass::kJavaLangClass, HeapProfiler::kJavaLangClass); word pos = 0; // java.lang.Class uword java_lang_class_id = reinterpret_cast<uword>(HeapProfiler::kJavaLangClass); EXPECT_TRUE( readStringInUtf8(result, &pos, java_lang_class_id, "java.lang.Class")); // Then write the LoadClass record EXPECT_TRUE(readLoadClass( result, &pos, static_cast<uword>(HeapProfiler::FakeClass::kJavaLangClass), java_lang_class_id)); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteHeaderWritesHeader) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); profiler.writeHeader(); word pos = 0; EXPECT_TRUE(readStringLiteral(result, &pos, "JAVA PROFILE 1.0.2")); EXPECT_EQ(read8(result, &pos), 0); // nul byte EXPECT_EQ(read32(result, &pos), 8); // ID length in bytes read32(result, &pos); // high value of current time in milliseconds read32(result, &pos); // low value of current time in milliseconds EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, BeginAndEndHeapDumpSegmentWritesHeapDumpSegment) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); EXPECT_EQ(result.size(), 0); profiler.clearRecord(); } word pos = 0; EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 0); // length EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, RecordDestructorWritesRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), &profiler); record.write32(0x12345678); } word pos = 0; EXPECT_EQ(read8(result, &pos), 0xa); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 4); // length EXPECT_EQ(read32(result, &pos), 0x12345678); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteHeapDumpEndWritesRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); profiler.writeHeapDumpEnd(); word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpEnd)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 0); // length EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteObjectArrayWritesObjectArrayRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); Object obj1(&scope, SmallInt::fromWord(0)); Object obj2(&scope, SmallInt::fromWord(1)); Object obj3(&scope, SmallInt::fromWord(2)); Tuple tuple(&scope, runtime_->newTupleWith3(obj1, obj2, obj3)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeObjectArray(*tuple); profiler.clearRecord(); } word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 49); // length // Object array EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kObjectArrayDump)); EXPECT_EQ(readu64(result, &pos), tuple.raw()); // array object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(read32(result, &pos), 3); // number of elements EXPECT_EQ(readu64(result, &pos), static_cast<uword>( HeapProfiler::FakeClass::kObjectArray)); // array class ID EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(0).raw()); // element 0 EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(1).raw()); // element 1 EXPECT_EQ(readu64(result, &pos), SmallInt::fromWord(2).raw()); // element 2 EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteBytesWritesPrimitiveArrayRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); byte source[] = "hello"; Bytes bytes(&scope, runtime_->newBytesWithAll(source)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeBytes(*bytes); profiler.clearRecord(); } word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 24); // length // Byte array EXPECT_TRUE( readSubtag(result, &pos, HeapProfiler::Subtag::kPrimitiveArrayDump)); EXPECT_EQ(readu64(result, &pos), bytes.raw()); // array object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu32(result, &pos), sizeof(source)); // number of elements EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kByte); // element type EXPECT_EQ(read8(result, &pos), 'h'); // element 0 EXPECT_EQ(read8(result, &pos), 'e'); // element 1 EXPECT_EQ(read8(result, &pos), 'l'); // element 2 EXPECT_EQ(read8(result, &pos), 'l'); // element 3 EXPECT_EQ(read8(result, &pos), 'o'); // element 4 EXPECT_EQ(read8(result, &pos), '\0'); // element 5 EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteClassDumpForComplexWritesClassDumpRecordWithTwoAttributes) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); RawLayout complex_layout = Layout::cast(runtime_->layoutAt(LayoutId::kComplex)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeClassDump(complex_layout); profiler.clearRecord(); } word pos = 0; // complex uword complex_address = runtime_->symbols()->at(ID(complex)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, complex_address, "complex")); EXPECT_TRUE( readLoadClass(result, &pos, complex_layout.raw(), complex_address)); // object uword object_address = runtime_->symbols()->at(ID(object)).raw(); RawLayout object_layout = Layout::cast(runtime_->layoutAt(LayoutId::kObject)); EXPECT_TRUE(readStringInUtf8(result, &pos, object_address, "object")); EXPECT_TRUE(readLoadClass(result, &pos, object_layout.raw(), object_address)); // real uword real_address = runtime_->symbols()->at(ID(real)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, real_address, "real")); // imag uword imag_address = runtime_->symbols()->at(ID(imag)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, imag_address, "imag")); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 89); // length // Class dump subrecord EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kClassDump)); EXPECT_TRUE(readClassDumpPrelude(result, &pos, complex_layout.raw(), object_layout.raw())); EXPECT_EQ(read32(result, &pos), complex_layout.instanceSize()); // instance size in bytes EXPECT_EQ(read16(result, &pos), 0); // size of constant pool and number of records that follow EXPECT_EQ(read16(result, &pos), 0); // number of static fields EXPECT_EQ(read16(result, &pos), 2); // number of instance fields // Field 0 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), real_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kDouble); // Field 1 (u8 name, u1 type) EXPECT_EQ(readu64(result, &pos), imag_address); EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kDouble); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteComplexWritesInstanceDump) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); Complex obj(&scope, runtime_->newComplex(1.0, 2.0)); Layout complex_layout(&scope, runtime_->layoutOf(*obj)); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeComplex(*obj); profiler.clearRecord(); } word pos = 0; // complex uword complex_address = runtime_->symbols()->at(ID(complex)).raw(); EXPECT_TRUE(readStringInUtf8(result, &pos, complex_address, "complex")); EXPECT_TRUE( readLoadClass(result, &pos, complex_layout.raw(), complex_address)); // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 41); // length // Complex "instance" dump EXPECT_TRUE(readSubtag(result, &pos, HeapProfiler::Subtag::kInstanceDump)); EXPECT_EQ(readu64(result, &pos), obj.raw()); // object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(readu64(result, &pos), complex_layout.raw()); // class object ID EXPECT_EQ(read32(result, &pos), 2 * kDoubleSize); // number of bytes that follow uint64_t real = readu64(result, &pos); // real EXPECT_EQ(bit_cast<double>(real), 1.0); uint64_t imag = readu64(result, &pos); // imag EXPECT_EQ(bit_cast<double>(imag), 2.0); EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteLargeIntWritesPrimitiveArrayRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); Int obj(&scope, runtime_->newInt(kMaxWord)); Int two(&scope, SmallInt::fromWord(2)); obj = runtime_->intMultiply(thread_, obj, two); CHECK(obj.isLargeInt(), "multiply failed"); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeLargeInt(LargeInt::cast(*obj)); profiler.clearRecord(); } word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 34); // length // Long array EXPECT_TRUE( readSubtag(result, &pos, HeapProfiler::Subtag::kPrimitiveArrayDump)); EXPECT_EQ(readu64(result, &pos), obj.raw()); // array object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(read32(result, &pos), 2); // number of elements EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kLong); // element type EXPECT_EQ(read64(result, &pos), -2); // element 0 EXPECT_EQ(read64(result, &pos), 0); // element 1 EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, WriteLargeStrWritesPrimitiveArrayRecord) { Vector<byte> result; HeapProfiler profiler(thread_, testWriter, &result); HandleScope scope(thread_); LargeStr obj(&scope, runtime_->newStrFromCStr("foobarbaz")); { HeapProfiler::Record record(HeapProfiler::kHeapDumpSegment, &profiler); profiler.setRecord(&record); profiler.writeLargeStr(*obj); profiler.clearRecord(); } word pos = 0; // Heap dump segment EXPECT_TRUE(readTag(result, &pos, HeapProfiler::Tag::kHeapDumpSegment)); EXPECT_EQ(read32(result, &pos), 0); // time EXPECT_EQ(read32(result, &pos), 27); // length // Byte array EXPECT_TRUE( readSubtag(result, &pos, HeapProfiler::Subtag::kPrimitiveArrayDump)); EXPECT_EQ(readu64(result, &pos), obj.raw()); // array object ID EXPECT_EQ(read32(result, &pos), 0); // stack trace serial number EXPECT_EQ(read32(result, &pos), 9); // number of elements EXPECT_EQ(read8(result, &pos), HeapProfiler::BasicType::kByte); // element type EXPECT_EQ(read8(result, &pos), 'f'); // element 0 EXPECT_EQ(read8(result, &pos), 'o'); // element 1 EXPECT_EQ(read8(result, &pos), 'o'); // element 2 EXPECT_EQ(read8(result, &pos), 'b'); // element 3 EXPECT_EQ(read8(result, &pos), 'a'); // element 4 EXPECT_EQ(read8(result, &pos), 'r'); // element 5 EXPECT_EQ(read8(result, &pos), 'b'); // element 6 EXPECT_EQ(read8(result, &pos), 'a'); // element 7 EXPECT_EQ(read8(result, &pos), 'z'); // element 8 EXPECT_EQ(pos, result.size()); } TEST_F(HeapProfilerTest, RecordConstructorSetsFields) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); EXPECT_EQ(record.tag(), 10); EXPECT_EQ(record.length(), 0); EXPECT_EQ(record.body(), nullptr); } static ::testing::AssertionResult recordEqualsBytes( const HeapProfiler::Record& record, View<byte> expected) { EXPECT_EQ(record.length(), expected.length()); const uint8_t* body = record.body(); for (word i = 0; i < record.length(); i++) { if (body[i] != expected.get(i)) { return ::testing::AssertionFailure() << "byte " << i << " differs"; } } return ::testing::AssertionSuccess(); } TEST_F(HeapProfilerTest, RecordWriteWritesToBody) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); byte buf[] = {0, 1, 2, 3}; record.write(buf, 4); EXPECT_TRUE(recordEqualsBytes(record, buf)); } TEST_F(HeapProfilerTest, RecordWrite8WritesToBody) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); record.write8(0x7d); EXPECT_EQ(record.length(), 1); EXPECT_EQ(*record.body(), 0x7d); } TEST_F(HeapProfilerTest, RecordWrite16WritesToBodyInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); record.write16(0xbeef); byte expected[] = {0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, RecordWrite32WritesToBodyInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); record.write32(0xdeadbeef); byte expected[] = {0xde, 0xad, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, RecordWrite64WritesToBodyInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); record.write64(0xdec0ffeedeadbeef); byte expected[] = {0xde, 0xc0, 0xff, 0xee, 0xde, 0xad, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, RecordWriteObjectIdWritesToBodyInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); record.writeObjectId(0xdec0ffeedeadbeef); byte expected[] = {0xde, 0xc0, 0xff, 0xee, 0xde, 0xad, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, SubRecordConstructorWritesTag) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); EXPECT_EQ(record.length(), 1); EXPECT_EQ(*record.body(), 20); } TEST_F(HeapProfilerTest, SubRecordWriteWritesToRecord) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); byte buf[] = {0, 1, 2, 3}; subrecord.write(buf, 4); byte expected[] = {20, 0, 1, 2, 3}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, SubRecordWrite8WritesToRecord) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); subrecord.write8(0x7d); byte expected[] = {20, 0x7d}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, SubRecordWrite16WritesToRecordInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); subrecord.write16(0xbeef); byte expected[] = {20, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, SubRecordWrite32WritesToRecordInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); subrecord.write32(0xdeadbeef); byte expected[] = {20, 0xde, 0xad, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, SubRecordWrite64WritesToBodyInBigEndian) { HeapProfiler::Record record(static_cast<HeapProfiler::Tag>(10), nullptr); HeapProfiler::SubRecord subrecord(static_cast<HeapProfiler::Subtag>(20), &record); subrecord.write64(0xdec0ffeedeadbeef); byte expected[] = {20, 0xde, 0xc0, 0xff, 0xee, 0xde, 0xad, 0xbe, 0xef}; EXPECT_TRUE(recordEqualsBytes(record, expected)); } TEST_F(HeapProfilerTest, HeapDumpAfterFullRuntimeBootDoesNotCrash) { // Create a __main__ and everything EXPECT_FALSE(runFromCStr(runtime_, R"( import site from _builtins import _heap_dump _heap_dump("/dev/null") )") .isError()); } class WordSetTest : public ::testing::Test { protected: WordSet ws; uword test_word1 = uword{1}; }; TEST_F(WordSetTest, ContainsWithWordNotInSetReturnsFalse) { ASSERT_FALSE(ws.contains(test_word1)); } TEST_F(WordSetTest, ContainsWithWordInSetReturnsTrue) { ws.add(test_word1); ASSERT_TRUE(ws.contains(test_word1)); } TEST_F(WordSetTest, AddWithWordNotInSetReturnsFalse) { ASSERT_FALSE(ws.add(test_word1)); } TEST_F(WordSetTest, AddWithWordInSetReturnsTrue) { ws.add(test_word1); ASSERT_TRUE(ws.add(test_word1)); } TEST_F(WordSetTest, MapWithResizeAndRehashTriggeredContainsAllOriginalElements) { // Insert one element over the default capacity of 1000 for (uword i = 0; i <= 1000; ++i) { if (i != kDummyVal) { ws.add(i); } } for (uword i = 0; i <= 1000; ++i) { if (i != kDummyVal) { ASSERT_TRUE(ws.contains(i)); } } } } // namespace testing } // namespace py