1 //===- llvm/unittest/DebugInfo/CodeView/RandomAccessVisitorTest.cpp -------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "llvm/DebugInfo/CodeView/AppendingTypeTableBuilder.h"
10 #include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
11 #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
12 #include "llvm/DebugInfo/CodeView/TypeRecord.h"
13 #include "llvm/DebugInfo/CodeView/TypeRecordMapping.h"
14 #include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h"
15 #include "llvm/DebugInfo/PDB/Native/RawTypes.h"
16 #include "llvm/Support/Allocator.h"
17 #include "llvm/Support/BinaryByteStream.h"
18 #include "llvm/Support/BinaryItemStream.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Testing/Support/Error.h"
21 
22 #include "gtest/gtest.h"
23 
24 using namespace llvm;
25 using namespace llvm::codeview;
26 using namespace llvm::pdb;
27 
28 namespace llvm {
29 namespace codeview {
operator ==(const ArrayRecord & R1,const ArrayRecord & R2)30 inline bool operator==(const ArrayRecord &R1, const ArrayRecord &R2) {
31   if (R1.ElementType != R2.ElementType)
32     return false;
33   if (R1.IndexType != R2.IndexType)
34     return false;
35   if (R1.Name != R2.Name)
36     return false;
37   if (R1.Size != R2.Size)
38     return false;
39   return true;
40 }
operator !=(const ArrayRecord & R1,const ArrayRecord & R2)41 inline bool operator!=(const ArrayRecord &R1, const ArrayRecord &R2) {
42   return !(R1 == R2);
43 }
44 
operator ==(const CVType & R1,const CVType & R2)45 inline bool operator==(const CVType &R1, const CVType &R2) {
46   if (R1.RecordData != R2.RecordData)
47     return false;
48   return true;
49 }
operator !=(const CVType & R1,const CVType & R2)50 inline bool operator!=(const CVType &R1, const CVType &R2) {
51   return !(R1 == R2);
52 }
53 }
54 }
55 
56 namespace llvm {
57 template <> struct BinaryItemTraits<CVType> {
lengthllvm::BinaryItemTraits58   static size_t length(const CVType &Item) { return Item.length(); }
bytesllvm::BinaryItemTraits59   static ArrayRef<uint8_t> bytes(const CVType &Item) { return Item.data(); }
60 };
61 }
62 
63 namespace {
64 
65 class MockCallbacks : public TypeVisitorCallbacks {
66 public:
visitTypeBegin(CVType & CVR,TypeIndex Index)67   Error visitTypeBegin(CVType &CVR, TypeIndex Index) override {
68     Indices.push_back(Index);
69     return Error::success();
70   }
visitKnownRecord(CVType & CVR,ArrayRecord & AR)71   Error visitKnownRecord(CVType &CVR, ArrayRecord &AR) override {
72     VisitedRecords.push_back(AR);
73     RawRecords.push_back(CVR);
74     return Error::success();
75   }
76 
count() const77   uint32_t count() const {
78     assert(Indices.size() == RawRecords.size());
79     assert(Indices.size() == VisitedRecords.size());
80     return Indices.size();
81   }
82   std::vector<TypeIndex> Indices;
83   std::vector<CVType> RawRecords;
84   std::vector<ArrayRecord> VisitedRecords;
85 };
86 
87 class RandomAccessVisitorTest : public testing::Test {
88 public:
RandomAccessVisitorTest()89   RandomAccessVisitorTest() {}
90 
SetUpTestCase()91   static void SetUpTestCase() {
92     GlobalState = std::make_unique<GlobalTestState>();
93 
94     AppendingTypeTableBuilder Builder(GlobalState->Allocator);
95 
96     uint32_t Offset = 0;
97     for (int I = 0; I < 11; ++I) {
98       ArrayRecord AR(TypeRecordKind::Array);
99       AR.ElementType = TypeIndex::Int32();
100       AR.IndexType = TypeIndex::UInt32();
101       AR.Size = I;
102       std::string Name;
103       raw_string_ostream Stream(Name);
104       Stream << "Array [" << I << "]";
105       AR.Name = GlobalState->Strings.save(Stream.str());
106       GlobalState->Records.push_back(AR);
107       GlobalState->Indices.push_back(Builder.writeLeafType(AR));
108 
109       CVType Type(Builder.records().back());
110       GlobalState->TypeVector.push_back(Type);
111 
112       GlobalState->AllOffsets.push_back(
113           {GlobalState->Indices.back(), ulittle32_t(Offset)});
114       Offset += Type.length();
115     }
116 
117     GlobalState->ItemStream.setItems(GlobalState->TypeVector);
118     GlobalState->TypeArray = VarStreamArray<CVType>(GlobalState->ItemStream);
119   }
120 
TearDownTestCase()121   static void TearDownTestCase() { GlobalState.reset(); }
122 
SetUp()123   void SetUp() override {
124     TestState = std::make_unique<PerTestState>();
125   }
126 
TearDown()127   void TearDown() override { TestState.reset(); }
128 
129 protected:
ValidateDatabaseRecord(LazyRandomTypeCollection & Types,uint32_t Index)130   bool ValidateDatabaseRecord(LazyRandomTypeCollection &Types, uint32_t Index) {
131     TypeIndex TI = TypeIndex::fromArrayIndex(Index);
132     if (!Types.contains(TI))
133       return false;
134     if (GlobalState->TypeVector[Index] != Types.getType(TI))
135       return false;
136     return true;
137   }
138 
ValidateVisitedRecord(uint32_t VisitationOrder,uint32_t GlobalArrayIndex)139   bool ValidateVisitedRecord(uint32_t VisitationOrder,
140                              uint32_t GlobalArrayIndex) {
141     TypeIndex TI = TypeIndex::fromArrayIndex(GlobalArrayIndex);
142     if (TI != TestState->Callbacks.Indices[VisitationOrder])
143       return false;
144 
145     if (GlobalState->TypeVector[TI.toArrayIndex()] !=
146         TestState->Callbacks.RawRecords[VisitationOrder])
147       return false;
148 
149     if (GlobalState->Records[TI.toArrayIndex()] !=
150         TestState->Callbacks.VisitedRecords[VisitationOrder])
151       return false;
152 
153     return true;
154   }
155 
156   struct GlobalTestState {
GlobalTestState__anonb2b7ee860111::RandomAccessVisitorTest::GlobalTestState157     GlobalTestState() : Strings(Allocator), ItemStream(llvm::support::little) {}
158 
159     BumpPtrAllocator Allocator;
160     StringSaver Strings;
161 
162     std::vector<ArrayRecord> Records;
163     std::vector<TypeIndex> Indices;
164     std::vector<TypeIndexOffset> AllOffsets;
165     std::vector<CVType> TypeVector;
166     BinaryItemStream<CVType> ItemStream;
167     VarStreamArray<CVType> TypeArray;
168 
169     MutableBinaryByteStream Stream;
170   };
171 
172   struct PerTestState {
173     FixedStreamArray<TypeIndexOffset> Offsets;
174 
175     MockCallbacks Callbacks;
176   };
177 
178   FixedStreamArray<TypeIndexOffset>
createPartialOffsets(MutableBinaryByteStream & Storage,std::initializer_list<uint32_t> Indices)179   createPartialOffsets(MutableBinaryByteStream &Storage,
180                        std::initializer_list<uint32_t> Indices) {
181 
182     uint32_t Count = Indices.size();
183     uint32_t Size = Count * sizeof(TypeIndexOffset);
184     uint8_t *Buffer = GlobalState->Allocator.Allocate<uint8_t>(Size);
185     MutableArrayRef<uint8_t> Bytes(Buffer, Size);
186     Storage = MutableBinaryByteStream(Bytes, support::little);
187     BinaryStreamWriter Writer(Storage);
188     for (const auto I : Indices)
189       consumeError(Writer.writeObject(GlobalState->AllOffsets[I]));
190 
191     BinaryStreamReader Reader(Storage);
192     FixedStreamArray<TypeIndexOffset> Result;
193     consumeError(Reader.readArray(Result, Count));
194     return Result;
195   }
196 
197   static std::unique_ptr<GlobalTestState> GlobalState;
198   std::unique_ptr<PerTestState> TestState;
199 };
200 
201 std::unique_ptr<RandomAccessVisitorTest::GlobalTestState>
202     RandomAccessVisitorTest::GlobalState;
203 }
204 
TEST_F(RandomAccessVisitorTest,MultipleVisits)205 TEST_F(RandomAccessVisitorTest, MultipleVisits) {
206   TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8});
207   LazyRandomTypeCollection Types(GlobalState->TypeArray,
208                                  GlobalState->TypeVector.size(),
209                                  TestState->Offsets);
210 
211   std::vector<uint32_t> IndicesToVisit = {5, 5, 5};
212 
213   for (uint32_t I : IndicesToVisit) {
214     TypeIndex TI = TypeIndex::fromArrayIndex(I);
215     CVType T = Types.getType(TI);
216     EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks),
217                       Succeeded());
218   }
219 
220   // [0,8) should be present
221   EXPECT_EQ(8u, Types.size());
222   for (uint32_t I = 0; I < 8; ++I)
223     EXPECT_TRUE(ValidateDatabaseRecord(Types, I));
224 
225   // 5, 5, 5
226   EXPECT_EQ(3u, TestState->Callbacks.count());
227   for (auto I : enumerate(IndicesToVisit))
228     EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value()));
229 }
230 
TEST_F(RandomAccessVisitorTest,DescendingWithinChunk)231 TEST_F(RandomAccessVisitorTest, DescendingWithinChunk) {
232   // Visit multiple items from the same "chunk" in reverse order.  In this
233   // example, it's 7 then 4 then 2.  At the end, all records from 0 to 7 should
234   // be known by the database, but only 2, 4, and 7 should have been visited.
235   TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8});
236 
237   std::vector<uint32_t> IndicesToVisit = {7, 4, 2};
238 
239   LazyRandomTypeCollection Types(GlobalState->TypeArray,
240                                  GlobalState->TypeVector.size(),
241                                  TestState->Offsets);
242   for (uint32_t I : IndicesToVisit) {
243     TypeIndex TI = TypeIndex::fromArrayIndex(I);
244     CVType T = Types.getType(TI);
245     EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks),
246                       Succeeded());
247   }
248 
249   // [0, 7]
250   EXPECT_EQ(8u, Types.size());
251   for (uint32_t I = 0; I < 8; ++I)
252     EXPECT_TRUE(ValidateDatabaseRecord(Types, I));
253 
254   // 2, 4, 7
255   EXPECT_EQ(3u, TestState->Callbacks.count());
256   for (auto I : enumerate(IndicesToVisit))
257     EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value()));
258 }
259 
TEST_F(RandomAccessVisitorTest,AscendingWithinChunk)260 TEST_F(RandomAccessVisitorTest, AscendingWithinChunk) {
261   // * Visit multiple items from the same chunk in ascending order, ensuring
262   //   that intermediate items are not visited.  In the below example, it's
263   //   5 -> 6 -> 7 which come from the [4,8) chunk.
264   TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8});
265 
266   std::vector<uint32_t> IndicesToVisit = {2, 4, 7};
267 
268   LazyRandomTypeCollection Types(GlobalState->TypeArray,
269                                  GlobalState->TypeVector.size(),
270                                  TestState->Offsets);
271   for (uint32_t I : IndicesToVisit) {
272     TypeIndex TI = TypeIndex::fromArrayIndex(I);
273     CVType T = Types.getType(TI);
274     EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks),
275                       Succeeded());
276   }
277 
278   // [0, 7]
279   EXPECT_EQ(8u, Types.size());
280   for (uint32_t I = 0; I < 8; ++I)
281     EXPECT_TRUE(ValidateDatabaseRecord(Types, I));
282 
283   // 2, 4, 7
284   EXPECT_EQ(3u, TestState->Callbacks.count());
285   for (auto &I : enumerate(IndicesToVisit))
286     EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value()));
287 }
288 
TEST_F(RandomAccessVisitorTest,StopPrematurelyInChunk)289 TEST_F(RandomAccessVisitorTest, StopPrematurelyInChunk) {
290   // * Don't visit the last item in one chunk, ensuring that visitation stops
291   //   at the record you specify, and the chunk is only partially visited.
292   //   In the below example, this is tested by visiting 0 and 1 but not 2,
293   //   all from the [0,3) chunk.
294   TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 8});
295 
296   std::vector<uint32_t> IndicesToVisit = {0, 1, 2};
297 
298   LazyRandomTypeCollection Types(GlobalState->TypeArray,
299                                  GlobalState->TypeVector.size(),
300                                  TestState->Offsets);
301 
302   for (uint32_t I : IndicesToVisit) {
303     TypeIndex TI = TypeIndex::fromArrayIndex(I);
304     CVType T = Types.getType(TI);
305     EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks),
306                       Succeeded());
307   }
308 
309   // [0, 8) should be visited.
310   EXPECT_EQ(8u, Types.size());
311   for (uint32_t I = 0; I < 8; ++I)
312     EXPECT_TRUE(ValidateDatabaseRecord(Types, I));
313 
314   // [0, 2]
315   EXPECT_EQ(3u, TestState->Callbacks.count());
316   for (auto I : enumerate(IndicesToVisit))
317     EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value()));
318 }
319 
TEST_F(RandomAccessVisitorTest,InnerChunk)320 TEST_F(RandomAccessVisitorTest, InnerChunk) {
321   // Test that when a request comes from a chunk in the middle of the partial
322   // offsets array, that items from surrounding chunks are not visited or
323   // added to the database.
324   TestState->Offsets = createPartialOffsets(GlobalState->Stream, {0, 4, 9});
325 
326   std::vector<uint32_t> IndicesToVisit = {5, 7};
327 
328   LazyRandomTypeCollection Types(GlobalState->TypeArray,
329                                  GlobalState->TypeVector.size(),
330                                  TestState->Offsets);
331 
332   for (uint32_t I : IndicesToVisit) {
333     TypeIndex TI = TypeIndex::fromArrayIndex(I);
334     CVType T = Types.getType(TI);
335     EXPECT_THAT_ERROR(codeview::visitTypeRecord(T, TI, TestState->Callbacks),
336                       Succeeded());
337   }
338 
339   // [4, 9)
340   EXPECT_EQ(5u, Types.size());
341   for (uint32_t I = 4; I < 9; ++I)
342     EXPECT_TRUE(ValidateDatabaseRecord(Types, I));
343 
344   // 5, 7
345   EXPECT_EQ(2u, TestState->Callbacks.count());
346   for (auto &I : enumerate(IndicesToVisit))
347     EXPECT_TRUE(ValidateVisitedRecord(I.index(), I.value()));
348 }
349 
TEST_F(RandomAccessVisitorTest,CrossChunkName)350 TEST_F(RandomAccessVisitorTest, CrossChunkName) {
351   AppendingTypeTableBuilder Builder(GlobalState->Allocator);
352 
353   // TypeIndex 0
354   ClassRecord Class(TypeRecordKind::Class);
355   Class.Name = "FooClass";
356   Class.Options = ClassOptions::None;
357   Class.MemberCount = 0;
358   Class.Size = 4U;
359   Class.DerivationList = TypeIndex::fromArrayIndex(0);
360   Class.FieldList = TypeIndex::fromArrayIndex(0);
361   Class.VTableShape = TypeIndex::fromArrayIndex(0);
362   TypeIndex IndexZero = Builder.writeLeafType(Class);
363 
364   // TypeIndex 1 refers to type index 0.
365   ModifierRecord Modifier(TypeRecordKind::Modifier);
366   Modifier.ModifiedType = TypeIndex::fromArrayIndex(0);
367   Modifier.Modifiers = ModifierOptions::Const;
368   TypeIndex IndexOne = Builder.writeLeafType(Modifier);
369 
370   // set up a type stream that refers to the above two serialized records.
371   std::vector<CVType> TypeArray = {
372       {Builder.records()[0]},
373       {Builder.records()[1]},
374   };
375   BinaryItemStream<CVType> ItemStream(llvm::support::little);
376   ItemStream.setItems(TypeArray);
377   VarStreamArray<CVType> TypeStream(ItemStream);
378 
379   // Figure out the byte offset of the second item.
380   auto ItemOneIter = TypeStream.begin();
381   ++ItemOneIter;
382 
383   // Set up a partial offsets buffer that contains the first and second items
384   // in separate chunks.
385   std::vector<TypeIndexOffset> TIO;
386   TIO.push_back({IndexZero, ulittle32_t(0u)});
387   TIO.push_back({IndexOne, ulittle32_t(ItemOneIter.offset())});
388   ArrayRef<uint8_t> Buffer(reinterpret_cast<const uint8_t *>(TIO.data()),
389                            TIO.size() * sizeof(TypeIndexOffset));
390 
391   BinaryStreamReader Reader(Buffer, llvm::support::little);
392   FixedStreamArray<TypeIndexOffset> PartialOffsets;
393   ASSERT_THAT_ERROR(Reader.readArray(PartialOffsets, 2), Succeeded());
394 
395   LazyRandomTypeCollection Types(TypeStream, 2, PartialOffsets);
396 
397   StringRef Name = Types.getTypeName(IndexOne);
398   EXPECT_EQ("const FooClass", Name);
399 }
400