//===- llvm/unittest/DebugInfo/CodeView/RandomAccessVisitorTest.cpp -------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/CodeView/AppendingTypeTableBuilder.h" #include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" #include "llvm/DebugInfo/CodeView/TypeRecordMapping.h" #include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h" #include "llvm/DebugInfo/PDB/Native/RawTypes.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/BinaryItemStream.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::codeview; using namespace llvm::pdb; namespace llvm { namespace codeview { inline bool operator==(const ArrayRecord &R1, const ArrayRecord &R2) { if (R1.ElementType != R2.ElementType) return false; if (R1.IndexType != R2.IndexType) return false; if (R1.Name != R2.Name) return false; if (R1.Size != R2.Size) return false; return true; } inline bool operator!=(const ArrayRecord &R1, const ArrayRecord &R2) { return !(R1 == R2); } inline bool operator==(const CVType &R1, const CVType &R2) { if (R1.RecordData != R2.RecordData) return false; return true; } inline bool operator!=(const CVType &R1, const CVType &R2) { return !(R1 == R2); } } } namespace llvm { template <> struct BinaryItemTraits { static size_t length(const CVType &Item) { return Item.length(); } static ArrayRef bytes(const CVType &Item) { return Item.data(); } }; } namespace { class MockCallbacks : public TypeVisitorCallbacks { public: Error visitTypeBegin(CVType &CVR, TypeIndex Index) override { Indices.push_back(Index); return Error::success(); } Error visitKnownRecord(CVType &CVR, ArrayRecord &AR) override { VisitedRecords.push_back(AR); RawRecords.push_back(CVR); return Error::success(); } uint32_t count() const { assert(Indices.size() == RawRecords.size()); assert(Indices.size() == VisitedRecords.size()); return Indices.size(); } std::vector Indices; std::vector RawRecords; std::vector VisitedRecords; }; class RandomAccessVisitorTest : public testing::Test { public: RandomAccessVisitorTest() {} static void SetUpTestCase() { GlobalState = std::make_unique(); AppendingTypeTableBuilder Builder(GlobalState->Allocator); uint32_t Offset = 0; for (int I = 0; I < 11; ++I) { ArrayRecord AR(TypeRecordKind::Array); AR.ElementType = TypeIndex::Int32(); AR.IndexType = TypeIndex::UInt32(); AR.Size = I; std::string Name; raw_string_ostream Stream(Name); Stream << "Array [" << I << "]"; AR.Name = GlobalState->Strings.save(Stream.str()); GlobalState->Records.push_back(AR); GlobalState->Indices.push_back(Builder.writeLeafType(AR)); CVType Type(Builder.records().back()); GlobalState->TypeVector.push_back(Type); GlobalState->AllOffsets.push_back( {GlobalState->Indices.back(), ulittle32_t(Offset)}); Offset += Type.length(); } GlobalState->ItemStream.setItems(GlobalState->TypeVector); GlobalState->TypeArray = VarStreamArray(GlobalState->ItemStream); } static void TearDownTestCase() { GlobalState.reset(); } void SetUp() override { TestState = std::make_unique(); } void TearDown() override { TestState.reset(); } protected: bool ValidateDatabaseRecord(LazyRandomTypeCollection &Types, uint32_t Index) { TypeIndex TI = TypeIndex::fromArrayIndex(Index); if (!Types.contains(TI)) return false; if (GlobalState->TypeVector[Index] != Types.getType(TI)) return false; return true; } bool ValidateVisitedRecord(uint32_t VisitationOrder, uint32_t GlobalArrayIndex) { TypeIndex TI = TypeIndex::fromArrayIndex(GlobalArrayIndex); if (TI != TestState->Callbacks.Indices[VisitationOrder]) return false; if (GlobalState->TypeVector[TI.toArrayIndex()] != TestState->Callbacks.RawRecords[VisitationOrder]) return false; if (GlobalState->Records[TI.toArrayIndex()] != TestState->Callbacks.VisitedRecords[VisitationOrder]) return false; return true; } struct GlobalTestState { GlobalTestState() : Strings(Allocator), ItemStream(llvm::support::little) {} BumpPtrAllocator Allocator; StringSaver Strings; std::vector Records; std::vector Indices; std::vector AllOffsets; std::vector TypeVector; BinaryItemStream ItemStream; VarStreamArray TypeArray; MutableBinaryByteStream Stream; }; struct PerTestState { FixedStreamArray Offsets; MockCallbacks Callbacks; }; FixedStreamArray createPartialOffsets(MutableBinaryByteStream &Storage, std::initializer_list Indices) { uint32_t Count = Indices.size(); uint32_t Size = Count * sizeof(TypeIndexOffset); uint8_t *Buffer = GlobalState->Allocator.Allocate(Size); MutableArrayRef Bytes(Buffer, Size); Storage = MutableBinaryByteStream(Bytes, support::little); BinaryStreamWriter Writer(Storage); for (const auto I : Indices) consumeError(Writer.writeObject(GlobalState->AllOffsets[I])); BinaryStreamReader Reader(Storage); FixedStreamArray Result; consumeError(Reader.readArray(Result, Count)); return Result; } static std::unique_ptr GlobalState; std::unique_ptr TestState; }; std::unique_ptr RandomAccessVisitorTest::GlobalState; } TEST_F(RandomAccessVisitorTest, MultipleVisits) { TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8}); LazyRandomTypeCollection Types(GlobalState->TypeArray, GlobalState->TypeVector.size(), TestState->Offsets); std::vector IndicesToVisit = {5, 5, 5}; for (uint32_t I : IndicesToVisit) { TypeIndex TI = TypeIndex::fromArrayIndex(I); CVType T = Types.getType(TI); EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks), Succeeded()); } // [0,8) should be present EXPECT_EQ(8u, Types.size()); for (uint32_t I = 0; I < 8; ++I) EXPECT_TRUE(ValidateDatabaseRecord(Types, I)); // 5, 5, 5 EXPECT_EQ(3u, TestState->Callbacks.count()); for (auto I : enumerate(IndicesToVisit)) EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value())); } TEST_F(RandomAccessVisitorTest, DescendingWithinChunk) { // Visit multiple items from the same "chunk" in reverse order. In this // example, it's 7 then 4 then 2. At the end, all records from 0 to 7 should // be known by the database, but only 2, 4, and 7 should have been visited. TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8}); std::vector IndicesToVisit = {7, 4, 2}; LazyRandomTypeCollection Types(GlobalState->TypeArray, GlobalState->TypeVector.size(), TestState->Offsets); for (uint32_t I : IndicesToVisit) { TypeIndex TI = TypeIndex::fromArrayIndex(I); CVType T = Types.getType(TI); EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks), Succeeded()); } // [0, 7] EXPECT_EQ(8u, Types.size()); for (uint32_t I = 0; I < 8; ++I) EXPECT_TRUE(ValidateDatabaseRecord(Types, I)); // 2, 4, 7 EXPECT_EQ(3u, TestState->Callbacks.count()); for (auto I : enumerate(IndicesToVisit)) EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value())); } TEST_F(RandomAccessVisitorTest, AscendingWithinChunk) { // * Visit multiple items from the same chunk in ascending order, ensuring // that intermediate items are not visited. In the below example, it's // 5 -> 6 -> 7 which come from the [4,8) chunk. TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8}); std::vector IndicesToVisit = {2, 4, 7}; LazyRandomTypeCollection Types(GlobalState->TypeArray, GlobalState->TypeVector.size(), TestState->Offsets); for (uint32_t I : IndicesToVisit) { TypeIndex TI = TypeIndex::fromArrayIndex(I); CVType T = Types.getType(TI); EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks), Succeeded()); } // [0, 7] EXPECT_EQ(8u, Types.size()); for (uint32_t I = 0; I < 8; ++I) EXPECT_TRUE(ValidateDatabaseRecord(Types, I)); // 2, 4, 7 EXPECT_EQ(3u, TestState->Callbacks.count()); for (auto &I : enumerate(IndicesToVisit)) EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value())); } TEST_F(RandomAccessVisitorTest, StopPrematurelyInChunk) { // * Don't visit the last item in one chunk, ensuring that visitation stops // at the record you specify, and the chunk is only partially visited. // In the below example, this is tested by visiting 0 and 1 but not 2, // all from the [0,3) chunk. TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8}); std::vector IndicesToVisit = {0, 1, 2}; LazyRandomTypeCollection Types(GlobalState->TypeArray, GlobalState->TypeVector.size(), TestState->Offsets); for (uint32_t I : IndicesToVisit) { TypeIndex TI = TypeIndex::fromArrayIndex(I); CVType T = Types.getType(TI); EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks), Succeeded()); } // [0, 8) should be visited. EXPECT_EQ(8u, Types.size()); for (uint32_t I = 0; I < 8; ++I) EXPECT_TRUE(ValidateDatabaseRecord(Types, I)); // [0, 2] EXPECT_EQ(3u, TestState->Callbacks.count()); for (auto I : enumerate(IndicesToVisit)) EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value())); } TEST_F(RandomAccessVisitorTest, InnerChunk) { // Test that when a request comes from a chunk in the middle of the partial // offsets array, that items from surrounding chunks are not visited or // added to the database. TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 4, 9}); std::vector IndicesToVisit = {5, 7}; LazyRandomTypeCollection Types(GlobalState->TypeArray, GlobalState->TypeVector.size(), TestState->Offsets); for (uint32_t I : IndicesToVisit) { TypeIndex TI = TypeIndex::fromArrayIndex(I); CVType T = Types.getType(TI); EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks), Succeeded()); } // [4, 9) EXPECT_EQ(5u, Types.size()); for (uint32_t I = 4; I < 9; ++I) EXPECT_TRUE(ValidateDatabaseRecord(Types, I)); // 5, 7 EXPECT_EQ(2u, TestState->Callbacks.count()); for (auto &I : enumerate(IndicesToVisit)) EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value())); } TEST_F(RandomAccessVisitorTest, CrossChunkName) { AppendingTypeTableBuilder Builder(GlobalState->Allocator); // TypeIndex 0 ClassRecord Class(TypeRecordKind::Class); Class.Name = "FooClass"; Class.Options = ClassOptions::None; Class.MemberCount = 0; Class.Size = 4U; Class.DerivationList = TypeIndex::fromArrayIndex(0); Class.FieldList = TypeIndex::fromArrayIndex(0); Class.VTableShape = TypeIndex::fromArrayIndex(0); TypeIndex IndexZero = Builder.writeLeafType(Class); // TypeIndex 1 refers to type index 0. ModifierRecord Modifier(TypeRecordKind::Modifier); Modifier.ModifiedType = TypeIndex::fromArrayIndex(0); Modifier.Modifiers = ModifierOptions::Const; TypeIndex IndexOne = Builder.writeLeafType(Modifier); // set up a type stream that refers to the above two serialized records. std::vector TypeArray = { {Builder.records()[0]}, {Builder.records()[1]}, }; BinaryItemStream ItemStream(llvm::support::little); ItemStream.setItems(TypeArray); VarStreamArray TypeStream(ItemStream); // Figure out the byte offset of the second item. auto ItemOneIter = TypeStream.begin(); ++ItemOneIter; // Set up a partial offsets buffer that contains the first and second items // in separate chunks. std::vector TIO; TIO.push_back({IndexZero, ulittle32_t(0u)}); TIO.push_back({IndexOne, ulittle32_t(ItemOneIter.offset())}); ArrayRef Buffer(reinterpret_cast(TIO.data()), TIO.size() * sizeof(TypeIndexOffset)); BinaryStreamReader Reader(Buffer, llvm::support::little); FixedStreamArray PartialOffsets; ASSERT_THAT_ERROR(Reader.readArray(PartialOffsets, 2), Succeeded()); LazyRandomTypeCollection Types(TypeStream, 2, PartialOffsets); StringRef Name = Types.getTypeName(IndexOne); EXPECT_EQ("const FooClass", Name); }