• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "annotation_data_accessor.h"
17 #include "class_data_accessor-inl.h"
18 #include "code_data_accessor-inl.h"
19 #include "debug_data_accessor-inl.h"
20 #include "field_data_accessor-inl.h"
21 #include "file.h"
22 #include "file_item_container.h"
23 #include "file_reader.h"
24 #include "file_writer.h"
25 #include "helpers.h"
26 #include "method_data_accessor-inl.h"
27 #include "method_handle_data_accessor.h"
28 #include "modifiers.h"
29 #include "os/file.h"
30 #include "proto_data_accessor-inl.h"
31 #include "pgo.h"
32 #include "value.h"
33 
34 #include "zlib.h"
35 
36 #include <cstddef>
37 
38 #include <memory>
39 #include <vector>
40 
41 #include <gtest/gtest.h>
42 #include <gmock/gmock.h>
43 
44 namespace ark::panda_file::test {
45 
TEST(ItemContainer,DeduplicationTest)46 TEST(ItemContainer, DeduplicationTest)
47 {
48     ItemContainer container;
49 
50     StringItem *stringItem = container.GetOrCreateStringItem("1");
51     EXPECT_EQ(stringItem, container.GetOrCreateStringItem("1"));
52 
53     ClassItem *classItem = container.GetOrCreateClassItem("1");
54     EXPECT_EQ(classItem, container.GetOrCreateClassItem("1"));
55 
56     ValueItem *intItem = container.GetOrCreateIntegerValueItem(1);
57     EXPECT_EQ(intItem, container.GetOrCreateIntegerValueItem(1));
58 
59     ValueItem *longItem = container.GetOrCreateLongValueItem(1);
60     EXPECT_EQ(longItem, container.GetOrCreateLongValueItem(1));
61     EXPECT_NE(longItem, intItem);
62 
63     ValueItem *floatItem = container.GetOrCreateFloatValueItem(1.0);
64     EXPECT_EQ(floatItem, container.GetOrCreateFloatValueItem(1.0));
65     EXPECT_NE(floatItem, intItem);
66     EXPECT_NE(floatItem, longItem);
67 
68     ValueItem *doubleItem = container.GetOrCreateDoubleValueItem(1.0);
69     EXPECT_EQ(doubleItem, container.GetOrCreateDoubleValueItem(1.0));
70     EXPECT_NE(doubleItem, intItem);
71     EXPECT_NE(doubleItem, longItem);
72     EXPECT_NE(doubleItem, floatItem);
73 }
74 
TEST(ItemContainer,TestFileOpen)75 TEST(ItemContainer, TestFileOpen)
76 {
77     using ark::os::file::Mode;
78     using ark::os::file::Open;
79 
80     // Write panda file to disk
81     ItemContainer container;
82 
83     const std::string fileName = "test_file_open.panda";
84     auto writer = FileWriter(fileName);
85 
86     ASSERT_TRUE(container.Write(&writer));
87 
88     // Read panda file from disk
89     EXPECT_NE(File::Open(fileName), nullptr);
90 }
91 
TEST(ItemContainer,TestFileFormatVersionTooOld)92 TEST(ItemContainer, TestFileFormatVersionTooOld)
93 {
94     const std::string fileName = "test_file_format_version_too_old.abc";
95     {
96         ItemContainer container;
97         auto writer = FileWriter(fileName);
98 
99         File::Header header {};
100         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
101         std::fill(reinterpret_cast<uint8_t *>(&header), reinterpret_cast<uint8_t *>(&header) + sizeof(header), 0);
102         header.magic = File::MAGIC;
103 
104         auto old = std::array<uint8_t, File::VERSION_SIZE>(MIN_VERSION);
105         --old[3];
106 
107         header.version = old;
108         header.fileSize = sizeof(File::Header);
109 
110         for (uint8_t b : Span<uint8_t>(reinterpret_cast<uint8_t *>(&header), sizeof(header))) {
111             writer.WriteByte(b);
112         }
113     }
114 
115     EXPECT_EQ(File::Open(fileName), nullptr);
116 }
117 
TEST(ItemContainer,TestFileFormatVersionTooNew)118 TEST(ItemContainer, TestFileFormatVersionTooNew)
119 {
120     const std::string fileName = "test_file_format_version_too_new.abc";
121     {
122         ItemContainer container;
123         auto writer = FileWriter(fileName);
124 
125         File::Header header {};
126         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
127         std::fill(reinterpret_cast<uint8_t *>(&header), reinterpret_cast<uint8_t *>(&header) + sizeof(header), 0);
128         header.magic = File::MAGIC;
129 
130         auto newArr = std::array<uint8_t, File::VERSION_SIZE>(VERSION);
131         ++newArr[3];
132 
133         header.version = newArr;
134         header.fileSize = sizeof(File::Header);
135 
136         for (uint8_t b : Span<uint8_t>(reinterpret_cast<uint8_t *>(&header), sizeof(header))) {
137             writer.WriteByte(b);
138         }
139     }
140 
141     EXPECT_EQ(File::Open(fileName), nullptr);
142 }
143 
TEST(ItemContainer,TestFileFormatVersionValid)144 TEST(ItemContainer, TestFileFormatVersionValid)
145 {
146     const std::string fileName = "test_file_format_version_valid.abc";
147     {
148         ItemContainer container;
149         auto writer = FileWriter(fileName);
150 
151         File::Header header {};
152         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
153         std::fill(reinterpret_cast<uint8_t *>(&header), reinterpret_cast<uint8_t *>(&header) + sizeof(header), 0);
154         header.magic = File::MAGIC;
155         header.version = {0, 0, 0, 5};
156         header.fileSize = sizeof(File::Header);
157 
158         for (uint8_t b : Span<uint8_t>(reinterpret_cast<uint8_t *>(&header), sizeof(header))) {
159             writer.WriteByte(b);
160         }
161     }
162 
163     EXPECT_NE(File::Open(fileName), nullptr);
164 }
165 
GetPandaFile(std::vector<uint8_t> & data)166 static std::unique_ptr<const File> GetPandaFile(std::vector<uint8_t> &data)
167 {
168     os::mem::ConstBytePtr ptr(reinterpret_cast<std::byte *>(data.data()), data.size(),
169                               [](std::byte *, size_t) noexcept {});
170     return File::OpenFromMemory(std::move(ptr));
171 }
172 
TEST(ItemContainer,TestClasses)173 TEST(ItemContainer, TestClasses)
174 {
175     // Write panda file to memory
176 
177     ItemContainer container;
178 
179     ClassItem *emptyClassItem = container.GetOrCreateClassItem("Foo");
180 
181     ClassItem *classItem = container.GetOrCreateClassItem("Bar");
182     classItem->SetAccessFlags(ACC_PUBLIC);
183     classItem->SetSuperClass(emptyClassItem);
184 
185     // Add interface
186 
187     ClassItem *ifaceItem = container.GetOrCreateClassItem("Iface");
188     ifaceItem->SetAccessFlags(ACC_PUBLIC);
189 
190     classItem->AddInterface(ifaceItem);
191 
192     // Add method
193 
194     StringItem *methodName = container.GetOrCreateStringItem("foo");
195 
196     PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
197     std::vector<MethodParamItem> params;
198     ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
199 
200     MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
201 
202     // Add field
203 
204     StringItem *fieldName = container.GetOrCreateStringItem("field");
205     PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
206 
207     FieldItem *fieldItem = classItem->AddField(fieldName, fieldType, ACC_PUBLIC);
208 
209     // Add runtime annotation
210 
211     std::vector<AnnotationItem::Elem> runtimeElems;
212     std::vector<AnnotationItem::Tag> runtimeTags;
213     auto *runtimeAnnotationItem = container.CreateItem<AnnotationItem>(classItem, runtimeElems, runtimeTags);
214 
215     classItem->AddRuntimeAnnotation(runtimeAnnotationItem);
216 
217     // Add annotation
218 
219     std::vector<AnnotationItem::Elem> elems;
220     std::vector<AnnotationItem::Tag> tags;
221     auto *annotationItem = container.CreateItem<AnnotationItem>(classItem, elems, tags);
222 
223     classItem->AddAnnotation(annotationItem);
224 
225     // Add source file
226 
227     StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
228 
229     classItem->SetSourceFile(sourceFile);
230 
231     MemoryWriter memWriter;
232 
233     ASSERT_TRUE(container.Write(&memWriter));
234 
235     // Read panda file from memory
236 
237     auto data = memWriter.GetData();
238     auto pandaFile = GetPandaFile(data);
239 
240     ASSERT_NE(pandaFile, nullptr);
241 
242     EXPECT_THAT(pandaFile->GetHeader()->version, ::testing::ElementsAre(0, 0, 0, 5));
243     EXPECT_EQ(pandaFile->GetHeader()->fileSize, memWriter.GetData().size());
244     EXPECT_EQ(pandaFile->GetHeader()->foreignOff, 0U);
245     EXPECT_EQ(pandaFile->GetHeader()->foreignSize, 0U);
246     EXPECT_EQ(pandaFile->GetHeader()->numClasses, 3U);
247     EXPECT_EQ(pandaFile->GetHeader()->classIdxOff, sizeof(File::Header));
248 
249     // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
250     const auto *classIndex =
251         reinterpret_cast<const uint32_t *>(pandaFile->GetBase() + pandaFile->GetHeader()->classIdxOff);
252     EXPECT_EQ(classIndex[0], classItem->GetOffset());
253     EXPECT_EQ(classIndex[1], emptyClassItem->GetOffset());
254     // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
255 
256     std::vector<uint8_t> className {'B', 'a', 'r', '\0'};
257     auto classId = pandaFile->GetClassId(className.data());
258     EXPECT_EQ(classId.GetOffset(), classItem->GetOffset());
259 
260     ClassDataAccessor classDataAccessor(*pandaFile, classId);
261     EXPECT_EQ(classDataAccessor.GetSuperClassId().GetOffset(), emptyClassItem->GetOffset());
262     EXPECT_EQ(classDataAccessor.GetAccessFlags(), ACC_PUBLIC);
263     EXPECT_EQ(classDataAccessor.GetFieldsNumber(), 1U);
264     EXPECT_EQ(classDataAccessor.GetMethodsNumber(), 1U);
265     EXPECT_EQ(classDataAccessor.GetIfacesNumber(), 1U);
266     EXPECT_TRUE(classDataAccessor.GetSourceFileId().has_value());
267     EXPECT_EQ(classDataAccessor.GetSourceFileId().value().GetOffset(), sourceFile->GetOffset());
268     EXPECT_EQ(classDataAccessor.GetSize(), classItem->GetSize());
269 
270     classDataAccessor.EnumerateInterfaces([&](File::EntityId id) {
271         EXPECT_EQ(id.GetOffset(), ifaceItem->GetOffset());
272 
273         ClassDataAccessor ifaceClassDataAccessor(*pandaFile, id);
274         EXPECT_EQ(ifaceClassDataAccessor.GetSuperClassId().GetOffset(), 0U);
275         EXPECT_EQ(ifaceClassDataAccessor.GetAccessFlags(), ACC_PUBLIC);
276         EXPECT_EQ(ifaceClassDataAccessor.GetFieldsNumber(), 0U);
277         EXPECT_EQ(ifaceClassDataAccessor.GetMethodsNumber(), 0U);
278         EXPECT_EQ(ifaceClassDataAccessor.GetIfacesNumber(), 0U);
279         EXPECT_FALSE(ifaceClassDataAccessor.GetSourceFileId().has_value());
280         EXPECT_EQ(ifaceClassDataAccessor.GetSize(), ifaceItem->GetSize());
281     });
282 
283     classDataAccessor.EnumerateRuntimeAnnotations([&](File::EntityId id) {
284         EXPECT_EQ(id.GetOffset(), runtimeAnnotationItem->GetOffset());
285 
286         AnnotationDataAccessor dataAccessor(*pandaFile, id);
287         EXPECT_EQ(dataAccessor.GetAnnotationId().GetOffset(), runtimeAnnotationItem->GetOffset());
288         EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
289         EXPECT_EQ(dataAccessor.GetCount(), 0U);
290     });
291 
292     // Annotation is the same as the runtime one, so we deduplicate it
293     EXPECT_FALSE(annotationItem->NeedsEmit());
294     annotationItem = runtimeAnnotationItem;
295 
296     classDataAccessor.EnumerateAnnotations([&](File::EntityId id) {
297         EXPECT_EQ(id.GetOffset(), annotationItem->GetOffset());
298 
299         AnnotationDataAccessor dataAccessor(*pandaFile, id);
300         EXPECT_EQ(dataAccessor.GetAnnotationId().GetOffset(), annotationItem->GetOffset());
301         EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
302         EXPECT_EQ(dataAccessor.GetCount(), 0U);
303     });
304 
305     classDataAccessor.EnumerateFields([&](FieldDataAccessor &dataAccessor) {
306         EXPECT_EQ(dataAccessor.GetFieldId().GetOffset(), fieldItem->GetOffset());
307         EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
308         EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), fieldName->GetOffset());
309         EXPECT_EQ(dataAccessor.GetType(), Type(Type::TypeId::I32).GetFieldEncoding());
310         EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC);
311         EXPECT_FALSE(dataAccessor.GetValue<int32_t>().has_value());
312         EXPECT_EQ(dataAccessor.GetSize(), fieldItem->GetSize());
313 
314         dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
315         dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
316     });
317 
318     classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
319         EXPECT_FALSE(dataAccessor.IsExternal());
320         EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
321         EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
322         EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
323         EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
324         EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC | ACC_STATIC);
325         EXPECT_FALSE(dataAccessor.GetCodeId().has_value());
326         EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
327         EXPECT_FALSE(dataAccessor.GetRuntimeParamAnnotationId().has_value());
328         EXPECT_FALSE(dataAccessor.GetParamAnnotationId().has_value());
329         EXPECT_FALSE(dataAccessor.GetDebugInfoId().has_value());
330 
331         dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
332         dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
333     });
334 
335     ClassDataAccessor emptyClassDataAccessor(*pandaFile, File::EntityId(emptyClassItem->GetOffset()));
336     EXPECT_EQ(emptyClassDataAccessor.GetSuperClassId().GetOffset(), 0U);
337     EXPECT_EQ(emptyClassDataAccessor.GetAccessFlags(), 0U);
338     EXPECT_EQ(emptyClassDataAccessor.GetFieldsNumber(), 0U);
339     EXPECT_EQ(emptyClassDataAccessor.GetMethodsNumber(), 0U);
340     EXPECT_EQ(emptyClassDataAccessor.GetIfacesNumber(), 0U);
341     EXPECT_FALSE(emptyClassDataAccessor.GetSourceFileId().has_value());
342     EXPECT_EQ(emptyClassDataAccessor.GetSize(), emptyClassItem->GetSize());
343 }
344 
TEST(ItemContainer,TestMethods)345 TEST(ItemContainer, TestMethods)
346 {
347     // Write panda file to memory
348 
349     ItemContainer container;
350 
351     ClassItem *classItem = container.GetOrCreateClassItem("A");
352     classItem->SetAccessFlags(ACC_PUBLIC);
353 
354     StringItem *methodName = container.GetOrCreateStringItem("foo");
355 
356     PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
357     std::vector<MethodParamItem> params;
358     ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
359 
360     MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
361 
362     std::vector<uint8_t> instructions {1, 2, 3, 4};
363     auto *codeItem = container.CreateItem<CodeItem>(0, 2, instructions);
364 
365     methodItem->SetCode(codeItem);
366 
367     MemoryWriter memWriter;
368 
369     ASSERT_TRUE(container.Write(&memWriter));
370 
371     // Read panda file from memory
372 
373     auto data = memWriter.GetData();
374     auto pandaFile = GetPandaFile(data);
375 
376     ASSERT_NE(pandaFile, nullptr);
377 
378     ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
379 
380     classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
381         EXPECT_FALSE(dataAccessor.IsExternal());
382         EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
383         EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
384         EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
385         EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
386         EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC | ACC_STATIC);
387         EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
388 
389         auto codeId = dataAccessor.GetCodeId();
390         EXPECT_TRUE(codeId.has_value());
391         EXPECT_EQ(codeId.value().GetOffset(), codeItem->GetOffset());
392 
393         CodeDataAccessor codeDataAccessor(*pandaFile, codeId.value());
394         EXPECT_EQ(codeDataAccessor.GetNumVregs(), 0U);
395         EXPECT_EQ(codeDataAccessor.GetNumArgs(), 2U);
396         EXPECT_EQ(codeDataAccessor.GetCodeSize(), instructions.size());
397         EXPECT_THAT(instructions,
398                     ::testing::ElementsAreArray(codeDataAccessor.GetInstructions(), codeDataAccessor.GetCodeSize()));
399 
400         EXPECT_EQ(codeDataAccessor.GetTriesSize(), 0U);
401         EXPECT_EQ(codeDataAccessor.GetSize(), codeItem->GetSize());
402 
403         codeDataAccessor.EnumerateTryBlocks([](const CodeDataAccessor::TryBlock &) {
404             EXPECT_TRUE(false);
405             return false;
406         });
407 
408         EXPECT_FALSE(dataAccessor.GetDebugInfoId().has_value());
409 
410         EXPECT_FALSE(dataAccessor.GetRuntimeParamAnnotationId().has_value());
411 
412         EXPECT_FALSE(dataAccessor.GetParamAnnotationId().has_value());
413 
414         dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
415 
416         dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
417     });
418 }
419 
TestProtos(size_t n)420 void TestProtos(size_t n)
421 {
422     constexpr size_t ELEM_WIDTH = 4;
423     constexpr size_t ELEM_PER16 = 16 / ELEM_WIDTH;
424 
425     // Write panda file to memory
426 
427     ItemContainer container;
428 
429     ClassItem *classItem = container.GetOrCreateClassItem("A");
430     classItem->SetAccessFlags(ACC_PUBLIC);
431 
432     StringItem *methodName = container.GetOrCreateStringItem("foo");
433 
434     std::vector<Type::TypeId> types {Type::TypeId::VOID, Type::TypeId::I32};
435     std::vector<ClassItem *> refTypes;
436 
437     PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
438     std::vector<MethodParamItem> params;
439 
440     params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
441 
442     for (size_t i = 0; i < ELEM_PER16 * 2U - 2U; i++) {
443         params.emplace_back(container.GetOrCreateClassItem("B"));
444         types.push_back(Type::TypeId::REFERENCE);
445         refTypes.push_back(container.GetOrCreateClassItem("B"));
446         params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F64));
447         types.push_back(Type::TypeId::F64);
448     }
449 
450     for (size_t i = 0; i < n; i++) {
451         params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F32));
452         types.push_back(Type::TypeId::F32);
453     }
454 
455     ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
456 
457     MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
458 
459     MemoryWriter memWriter;
460 
461     ASSERT_TRUE(container.Write(&memWriter));
462 
463     // Read panda file from memory
464 
465     auto data = memWriter.GetData();
466     auto pandaFile = GetPandaFile(data);
467 
468     ASSERT_NE(pandaFile, nullptr);
469 
470     ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
471 
472     classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
473         EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
474         EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
475 
476         ProtoDataAccessor protoDataAccessor(*pandaFile, dataAccessor.GetProtoId());
477         EXPECT_EQ(protoDataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
478 
479         size_t num = 0;
480         size_t nref = 0;
481         protoDataAccessor.EnumerateTypes([&](Type t) {
482             EXPECT_EQ(t.GetEncoding(), Type(types[num]).GetEncoding());
483             ++num;
484 
485             if (!t.IsPrimitive()) {
486                 ++nref;
487             }
488         });
489 
490         EXPECT_EQ(num, types.size());
491 
492         for (size_t i = 0; i < num - 1; i++) {
493             EXPECT_EQ(protoDataAccessor.GetArgType(i).GetEncoding(), Type(types[i + 1]).GetEncoding());
494         }
495 
496         EXPECT_EQ(protoDataAccessor.GetReturnType().GetEncoding(), Type(types[0]).GetEncoding());
497 
498         EXPECT_EQ(nref, refTypes.size());
499 
500         for (size_t i = 0; i < nref; i++) {
501             EXPECT_EQ(protoDataAccessor.GetReferenceType(0).GetOffset(), refTypes[i]->GetOffset());
502         }
503 
504         size_t size = ((num + ELEM_PER16) / ELEM_PER16 + nref) * sizeof(uint16_t);
505 
506         EXPECT_EQ(protoDataAccessor.GetSize(), size);
507         EXPECT_EQ(protoDataAccessor.GetSize(), protoItem->GetSize());
508     });
509 }
510 
TEST(ItemContainer,TestProtos)511 TEST(ItemContainer, TestProtos)
512 {
513     TestProtos(0);
514     TestProtos(1);
515     TestProtos(2);
516     TestProtos(7);
517 }
518 
TEST(ItemContainer,TestDebugInfo)519 TEST(ItemContainer, TestDebugInfo)
520 {
521     // Write panda file to memory
522 
523     ItemContainer container;
524 
525     ClassItem *classItem = container.GetOrCreateClassItem("A");
526     classItem->SetAccessFlags(ACC_PUBLIC);
527 
528     StringItem *methodName = container.GetOrCreateStringItem("foo");
529 
530     PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
531     std::vector<MethodParamItem> params;
532     params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
533     ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
534     MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
535 
536     StringItem *sourceFileItem = container.GetOrCreateStringItem("<source>");
537     StringItem *sourceCodeItem = container.GetOrCreateStringItem("let a = 1;");
538     StringItem *paramStringItem = container.GetOrCreateStringItem("a0");
539 
540     LineNumberProgramItem *lineNumberProgramItem = container.CreateLineNumberProgramItem();
541     auto *debugInfoItem = container.CreateItem<DebugInfoItem>(lineNumberProgramItem);
542     methodItem->SetDebugInfo(debugInfoItem);
543 
544     // Add debug info
545 
546     container.ComputeLayout();
547 
548     std::vector<uint8_t> opcodes {
549         static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_SOURCE_CODE),
550         static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_FILE),
551         static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_PROLOGUE_END),
552         static_cast<uint8_t>(LineNumberProgramItem::Opcode::ADVANCE_PC),
553         static_cast<uint8_t>(LineNumberProgramItem::Opcode::ADVANCE_LINE),
554         static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_EPILOGUE_BEGIN),
555         static_cast<uint8_t>(LineNumberProgramItem::Opcode::END_SEQUENCE),
556     };
557 
558     auto *constantPool = debugInfoItem->GetConstantPool();
559     debugInfoItem->SetLineNumber(5);
560     lineNumberProgramItem->EmitSetSourceCode(constantPool, sourceCodeItem);
561     lineNumberProgramItem->EmitSetFile(constantPool, sourceFileItem);
562     lineNumberProgramItem->EmitPrologueEnd();
563     // NOLINTNEXTLINE(readability-magic-numbers)
564     lineNumberProgramItem->EmitAdvancePc(constantPool, 10);
565     lineNumberProgramItem->EmitAdvanceLine(constantPool, -5);
566     lineNumberProgramItem->EmitEpilogueBegin();
567     lineNumberProgramItem->EmitEnd();
568 
569     debugInfoItem->AddParameter(paramStringItem);
570 
571     methodItem->SetDebugInfo(debugInfoItem);
572 
573     MemoryWriter memWriter;
574 
575     ASSERT_TRUE(container.Write(&memWriter));
576 
577     // Read panda file from memory
578 
579     auto data = memWriter.GetData();
580     auto pandaFile = GetPandaFile(data);
581 
582     ASSERT_NE(pandaFile, nullptr);
583 
584     ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
585 
586     classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
587         EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
588         EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
589 
590         auto debugInfoId = dataAccessor.GetDebugInfoId();
591         EXPECT_TRUE(debugInfoId.has_value());
592 
593         EXPECT_EQ(debugInfoId.value().GetOffset(), debugInfoItem->GetOffset());
594 
595         DebugInfoDataAccessor dda(*pandaFile, debugInfoId.value());
596         EXPECT_EQ(dda.GetDebugInfoId().GetOffset(), debugInfoItem->GetOffset());
597         EXPECT_EQ(dda.GetLineStart(), 5U);
598         EXPECT_EQ(dda.GetNumParams(), params.size());
599 
600         dda.EnumerateParameters([&](File::EntityId id) { EXPECT_EQ(id.GetOffset(), paramStringItem->GetOffset()); });
601 
602         auto cp = dda.GetConstantPool();
603         EXPECT_EQ(cp.size(), constantPool->size());
604         EXPECT_THAT(*constantPool, ::testing::ElementsAreArray(cp.data(), cp.Size()));
605 
606         EXPECT_EQ(helpers::ReadULeb128(&cp), sourceCodeItem->GetOffset());
607         EXPECT_EQ(helpers::ReadULeb128(&cp), sourceFileItem->GetOffset());
608         EXPECT_EQ(helpers::ReadULeb128(&cp), 10U);
609         EXPECT_EQ(helpers::ReadLeb128(&cp), -5);
610 
611         const uint8_t *lineNumberProgram = dda.GetLineNumberProgram();
612         EXPECT_EQ(pandaFile->GetIdFromPointer(lineNumberProgram).GetOffset(), lineNumberProgramItem->GetOffset());
613         EXPECT_EQ(lineNumberProgramItem->GetSize(), opcodes.size());
614 
615         EXPECT_THAT(opcodes, ::testing::ElementsAreArray(lineNumberProgram, opcodes.size()));
616 
617         EXPECT_EQ(dda.GetSize(), debugInfoItem->GetSize());
618     });
619 }
620 
TEST(ItemContainer,ForeignItems)621 TEST(ItemContainer, ForeignItems)
622 {
623     ItemContainer container;
624 
625     // Create foreign class
626     ForeignClassItem *classItem = container.GetOrCreateForeignClassItem("ForeignClass");
627 
628     // Create foreign field
629     StringItem *fieldName = container.GetOrCreateStringItem("foreign_field");
630     PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
631     auto *fieldItem = container.CreateItem<ForeignFieldItem>(classItem, fieldName, fieldType);
632 
633     // Create foreign method
634     StringItem *methodName = container.GetOrCreateStringItem("ForeignMethod");
635     PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
636     std::vector<MethodParamItem> params;
637     params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
638     ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
639     auto *methodItem = container.CreateItem<ForeignMethodItem>(classItem, methodName, protoItem, 0);
640 
641     MemoryWriter memWriter;
642 
643     ASSERT_TRUE(container.Write(&memWriter));
644 
645     // Read panda file from memory
646 
647     auto data = memWriter.GetData();
648     auto pandaFile = GetPandaFile(data);
649 
650     ASSERT_NE(pandaFile, nullptr);
651 
652     EXPECT_EQ(pandaFile->GetHeader()->foreignOff, classItem->GetOffset());
653 
654     size_t foreignSize = classItem->GetSize() + fieldItem->GetSize() + methodItem->GetSize();
655     EXPECT_EQ(pandaFile->GetHeader()->foreignSize, foreignSize);
656 
657     ASSERT_TRUE(pandaFile->IsExternal(classItem->GetFileId()));
658 
659     MethodDataAccessor methodDataAccessor(*pandaFile, methodItem->GetFileId());
660     EXPECT_EQ(methodDataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
661     EXPECT_EQ(methodDataAccessor.GetSize(), methodItem->GetSize());
662     EXPECT_EQ(methodDataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
663     EXPECT_EQ(methodDataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
664     EXPECT_EQ(methodDataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
665     EXPECT_TRUE(methodDataAccessor.IsExternal());
666 
667     FieldDataAccessor fieldDataAccessor(*pandaFile, fieldItem->GetFileId());
668     EXPECT_EQ(fieldDataAccessor.GetFieldId().GetOffset(), fieldItem->GetOffset());
669     EXPECT_EQ(fieldDataAccessor.GetSize(), fieldItem->GetSize());
670     EXPECT_EQ(fieldDataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
671     EXPECT_EQ(fieldDataAccessor.GetNameId().GetOffset(), fieldName->GetOffset());
672     EXPECT_EQ(fieldDataAccessor.GetType(), fieldType->GetType().GetFieldEncoding());
673     EXPECT_TRUE(fieldDataAccessor.IsExternal());
674 }
675 
TEST(ItemContainer,EmptyContainerChecksum)676 TEST(ItemContainer, EmptyContainerChecksum)
677 {
678     using ark::os::file::Mode;
679     using ark::os::file::Open;
680 
681     // Write panda file to disk
682     ItemContainer container;
683 
684     const std::string fileName = "test_empty_checksum.ark";
685     auto writer = FileWriter(fileName);
686 
687     // Initial value of adler32
688     EXPECT_EQ(writer.GetChecksum(), 1);
689     ASSERT_TRUE(container.Write(&writer));
690 
691     // At least header was written so the checksum should be changed
692     auto containerChecksum = writer.GetChecksum();
693     EXPECT_NE(containerChecksum, 1);
694 
695     // Read panda file from disk
696     auto file = File::Open(fileName);
697     EXPECT_NE(file, nullptr);
698     EXPECT_EQ(file->GetHeader()->checksum, containerChecksum);
699 
700     constexpr size_t DATA_OFFSET = 12U;
701     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
702     auto checksum = adler32(1, file->GetBase() + DATA_OFFSET, file->GetHeader()->fileSize - DATA_OFFSET);
703     EXPECT_EQ(file->GetHeader()->checksum, checksum);
704 }
705 
TEST(ItemContainer,ContainerChecksum)706 TEST(ItemContainer, ContainerChecksum)
707 {
708     using ark::os::file::Mode;
709     using ark::os::file::Open;
710 
711     uint32_t emptyChecksum = 0;
712     {
713         ItemContainer container;
714         const std::string fileName = "test_checksum_empty.ark";
715         auto writer = FileWriter(fileName);
716         ASSERT_TRUE(container.Write(&writer));
717         emptyChecksum = writer.GetChecksum();
718     }
719     ASSERT(emptyChecksum != 0);
720 
721     // Create not empty container
722     ItemContainer container;
723     container.GetOrCreateClassItem("C");
724 
725     const std::string fileName = "test_checksum.ark";
726     auto writer = FileWriter(fileName);
727 
728     ASSERT_TRUE(container.Write(&writer));
729 
730     // This checksum must be different from the empty one (collision may happen though)
731     auto containerChecksum = writer.GetChecksum();
732     EXPECT_NE(emptyChecksum, containerChecksum);
733 
734     // Read panda file from disk
735     auto file = File::Open(fileName);
736     EXPECT_NE(file, nullptr);
737     EXPECT_EQ(file->GetHeader()->checksum, containerChecksum);
738 
739     constexpr size_t DATA_OFFSET = 12U;
740     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
741     auto checksum = adler32(1, file->GetBase() + DATA_OFFSET, file->GetHeader()->fileSize - DATA_OFFSET);
742     EXPECT_EQ(file->GetHeader()->checksum, checksum);
743 }
744 
TEST(ItemContainer,TestProfileGuidedRelayout)745 TEST(ItemContainer, TestProfileGuidedRelayout)
746 {
747     ItemContainer container;
748 
749     // Add classes
750     ClassItem *emptyClassItem = container.GetOrCreateClassItem("LTest;");
751     ClassItem *classItemA = container.GetOrCreateClassItem("LAA;");
752     classItemA->SetSuperClass(emptyClassItem);
753     ClassItem *classItemB = container.GetOrCreateClassItem("LBB;");
754 
755     // Add method1
756     StringItem *methodName1 = container.GetOrCreateStringItem("foo1");
757     PrimitiveTypeItem *retType1 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
758     std::vector<MethodParamItem> params1;
759     ProtoItem *protoItem1 = container.GetOrCreateProtoItem(retType1, params1);
760     MethodItem *methodItem1 = classItemA->AddMethod(methodName1, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
761     // Set code_1
762     std::vector<uint8_t> instructions1 {1, 2, 3, 4};
763     auto *codeItem1 = container.CreateItem<CodeItem>(0, 2, instructions1);
764     methodItem1->SetCode(codeItem1);
765     codeItem1->AddMethod(methodItem1);
766 
767     // Add method2
768     StringItem *methodName2 = container.GetOrCreateStringItem("foo2");
769     PrimitiveTypeItem *retType2 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
770     std::vector<MethodParamItem> params2;
771     ProtoItem *protoItem2 = container.GetOrCreateProtoItem(retType2, params2);
772     MethodItem *methodItem2 = classItemB->AddMethod(methodName2, protoItem2, ACC_PUBLIC | ACC_STATIC, params2);
773     // Set code_2
774     std::vector<uint8_t> instructions2 {5, 6, 7, 8};
775     auto *codeItem2 = container.CreateItem<CodeItem>(0, 2, instructions2);
776     methodItem2->SetCode(codeItem2);
777     codeItem2->AddMethod(methodItem2);
778 
779     // Add method_3
780     StringItem *methodName3 = container.GetOrCreateStringItem("foo3");
781     auto *methodItem3 = emptyClassItem->AddMethod(methodName3, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
782     // Set code_3
783     std::vector<uint8_t> instructions3 {3, 4, 5, 6};
784     auto *codeItem3 = container.CreateItem<CodeItem>(0, 2, instructions3);
785     methodItem3->SetCode(codeItem3);
786     codeItem3->AddMethod(methodItem3);
787 
788     // Add method_4
789     StringItem *methodName4 = container.GetOrCreateStringItem("foo4");
790     auto *methodItem4 = emptyClassItem->AddMethod(methodName4, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
791     // Set code. method_4 and method_3 share code_item_3
792     methodItem4->SetCode(codeItem3);
793     codeItem3->AddMethod(methodItem4);
794 
795     // Add field
796     StringItem *fieldName = container.GetOrCreateStringItem("test_field");
797     PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
798     classItemA->AddField(fieldName, fieldType, ACC_PUBLIC);
799 
800     // Add source file
801     StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
802     classItemA->SetSourceFile(sourceFile);
803 
804     constexpr std::string_view PRIMITIVE_TYPE_ITEM = "primitive_type_item";
805     constexpr std::string_view PROTO_ITEM = "proto_item";
806     constexpr std::string_view END_ITEM = "end_item";
807 
808     // Items before PGO
809     const auto &items = container.GetItems();
810     auto item = items.begin();
811     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
812     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "Test");
813     item++;
814     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
815     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "AA");
816     item++;
817     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
818     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "BB");
819     item++;
820     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
821     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo1");
822     item++;
823     EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
824     item++;
825     EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
826     item++;
827     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
828     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo2");
829     item++;
830     EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
831     item++;
832     EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
833     item++;
834     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
835     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo3");
836     item++;
837     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
838     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo4");
839     item++;
840     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
841     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "test_field");
842     item++;
843     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
844     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "source_file");
845     item++;
846     EXPECT_EQ((*item)->GetName(), END_ITEM);
847     item++;
848     EXPECT_EQ((*item)->GetName(), END_ITEM);
849     item++;
850     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
851     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "AA::foo1");
852     item++;
853     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
854     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "BB::foo2");
855     item++;
856     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
857     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "Test::foo3");
858     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[1], "Test::foo4");
859     item++;
860     EXPECT_EQ((*item)->GetName(), END_ITEM);
861     item++;
862     EXPECT_EQ((*item)->GetName(), END_ITEM);
863     item++;
864     EXPECT_EQ(item, items.end());
865 
866     // Prepare profile data
867     std::string profilePath = "TestProfileGuidedRelayout_profile_test_data.txt";
868     std::ofstream testFile;
869     testFile.open(profilePath);
870     testFile << "string_item:test_field" << std::endl;
871     testFile << "class_item:BB" << std::endl;
872     testFile << "code_item:BB::foo2" << std::endl;
873     testFile << "code_item:Test::foo4" << std::endl;
874     testFile.close();
875 
876     // Run PGO
877     ark::panda_file::pgo::ProfileOptimizer profileOpt;
878     profileOpt.SetProfilePath(profilePath);
879     container.ReorderItems(&profileOpt);
880 
881     // Items after PGO
882     item = items.begin();
883     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
884     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "test_field");
885     item++;
886     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
887     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo1");
888     item++;
889     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
890     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo2");
891     item++;
892     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
893     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo3");
894     item++;
895     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
896     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo4");
897     item++;
898     EXPECT_EQ((*item)->GetName(), STRING_ITEM);
899     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "source_file");
900     item++;
901     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
902     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "BB");
903     item++;
904     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
905     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "Test");
906     item++;
907     EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
908     EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "AA");
909     item++;
910     EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
911     item++;
912     EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
913     item++;
914     EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
915     item++;
916     EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
917     item++;
918     EXPECT_EQ((*item)->GetName(), END_ITEM);
919     item++;
920     EXPECT_EQ((*item)->GetName(), END_ITEM);
921     item++;
922     EXPECT_EQ((*item)->GetName(), END_ITEM);
923     item++;
924     EXPECT_EQ((*item)->GetName(), END_ITEM);
925     item++;
926     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
927     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "BB::foo2");
928     item++;
929     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
930     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "Test::foo3");
931     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[1], "Test::foo4");
932     item++;
933     EXPECT_EQ((*item)->GetName(), CODE_ITEM);
934     EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "AA::foo1");
935     item++;
936     EXPECT_EQ(item, items.end());
937 }
938 
TEST(ItemContainer,GettersTest)939 TEST(ItemContainer, GettersTest)
940 {
941     ItemContainer container;
942 
943     ClassItem *emptyClassItem = container.GetOrCreateClassItem("Foo");
944 
945     ClassItem *classItem = container.GetOrCreateClassItem("Bar");
946     classItem->SetAccessFlags(ACC_PUBLIC);
947     classItem->SetSuperClass(emptyClassItem);
948 
949     // Add methods
950 
951     StringItem *methodName1 = container.GetOrCreateStringItem("foo1");
952 
953     PrimitiveTypeItem *retType1 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
954     std::vector<MethodParamItem> params1;
955     params1.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
956     ProtoItem *protoItem1 = container.GetOrCreateProtoItem(retType1, params1);
957 
958     classItem->AddMethod(methodName1, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
959 
960     StringItem *methodName2 = container.GetOrCreateStringItem("foo2");
961 
962     PrimitiveTypeItem *retType2 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
963     std::vector<MethodParamItem> params2;
964     params2.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F32));
965     ProtoItem *protoItem2 = container.GetOrCreateProtoItem(retType2, params2);
966 
967     classItem->AddMethod(methodName2, protoItem2, ACC_PUBLIC | ACC_STATIC, params2);
968 
969     // Add field
970 
971     StringItem *fieldName = container.GetOrCreateStringItem("field");
972     PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
973 
974     classItem->AddField(fieldName, fieldType, ACC_PUBLIC);
975 
976     // Add source file
977 
978     StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
979 
980     classItem->SetSourceFile(sourceFile);
981 
982     // Read items from container
983 
984     ASSERT_TRUE(container.GetItems().size() == 15);
985 
986     std::map<std::string, panda_file::BaseClassItem *> *classMap = container.GetClassMap();
987     ASSERT_TRUE(classMap != nullptr && classMap->size() == 2);
988     auto it = classMap->find("Bar");
989     ASSERT_TRUE(it != classMap->end());
990 
991     std::unordered_map<std::string, StringItem *> *stringMap = container.GetStringMap();
992     ASSERT_TRUE(stringMap != nullptr && stringMap->size() == 4);
993     auto sit0 = stringMap->find("field");
994     auto sit1 = stringMap->find("source_file");
995     auto sit2 = stringMap->find("foo1");
996     auto sit3 = stringMap->find("foo2");
997     ASSERT_TRUE(sit0 != stringMap->end() && sit1 != stringMap->end() && sit2 != stringMap->end() &&
998                 sit3 != stringMap->end());
999 
1000     std::unordered_map<Type::TypeId, PrimitiveTypeItem *> *primitiveTypeMap = container.GetPrimitiveTypeMap();
1001     ASSERT_TRUE(primitiveTypeMap != nullptr && primitiveTypeMap->size() == 3);
1002     auto pit0 = primitiveTypeMap->find(Type::TypeId::F32);
1003     auto pit1 = primitiveTypeMap->find(Type::TypeId::I32);
1004     auto pit2 = primitiveTypeMap->find(Type::TypeId::VOID);
1005     ASSERT_TRUE(pit0 != primitiveTypeMap->end() && pit1 != primitiveTypeMap->end() && pit2 != primitiveTypeMap->end());
1006 
1007     auto *rclassItem = static_cast<panda_file::ClassItem *>(it->second);
1008     std::string methodName;
1009     std::function<bool(BaseItem *)> testMethod = [&](BaseItem *method) {
1010         auto *methodItem = static_cast<panda_file::MethodItem *>(method);
1011         ASSERT(methodItem != nullptr && methodItem->GetItemType() == ItemTypes::METHOD_ITEM);
1012         methodName = methodItem->GetNameItem()->GetData();
1013         methodName.pop_back();  // remove '\0'
1014         ASSERT(methodName == "foo1" || methodName == "foo2");
1015         return true;
1016     };
1017 
1018     using std::placeholders::_1;
1019     panda_file::BaseItem::VisitorCallBack cbMethod = testMethod;
1020     rclassItem->VisitMethods(cbMethod);
1021 
1022     std::string fName;
1023     std::function<bool(BaseItem *)> testField = [&](BaseItem *field) {
1024         auto *fieldItem = static_cast<panda_file::FieldItem *>(field);
1025         ASSERT(fieldItem != nullptr && fieldItem->GetItemType() == ItemTypes::FIELD_ITEM);
1026         fName = fieldItem->GetNameItem()->GetData();
1027         fName.pop_back();  // remove '\0'
1028         ASSERT(fName == "field");
1029         return true;
1030     };
1031 
1032     panda_file::BaseItem::VisitorCallBack cbField = testField;
1033     rclassItem->VisitFields(cbField);
1034 }
1035 
TEST(ItemContainer,AnnotationDeduplication)1036 TEST(ItemContainer, AnnotationDeduplication)
1037 {
1038     ItemContainer container;
1039 
1040     auto annot = container.GetOrCreateClassItem("Annot");
1041     annot->SetAccessFlags(ACC_PUBLIC | ACC_ANNOTATION);
1042     auto annot1 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1043                                                        std::vector<AnnotationItem::Tag> {});
1044     auto annot2 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1045                                                        std::vector<AnnotationItem::Tag> {});
1046 
1047     auto str = container.GetOrCreateStringItem("Abc");
1048 
1049     auto glb = container.GetOrCreateGlobalClassItem();
1050     auto proto = container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1051     auto meth = glb->AddMethod(container.GetOrCreateStringItem("foo"), proto, ACC_STATIC | ACC_PUBLIC,
1052                                std::vector<MethodParamItem> {});
1053     meth->AddAnnotation(annot1);
1054     meth->AddAnnotation(annot2);
1055 
1056     std::vector<uint8_t> ins = {(uint8_t)BytecodeInstruction::Opcode::LDA_STR_ID32, 0, 0, 0, 0,
1057                                 (uint8_t)BytecodeInstruction::Opcode::RETURN_VOID};
1058     meth->SetCode(container.CreateItem<CodeItem>(0, 0, ins));
1059 
1060     container.ComputeLayout();
1061     for (size_t i = 0; i < 4; i++) {
1062         // NOLINTBEGIN(readability-magic-numbers)
1063         meth->GetCode()->GetInstructions()->at(1 + i) =
1064             static_cast<uint8_t>((str->GetFileId().GetOffset() >> (i * 8)) & 0xffU);
1065         // NOLINTEND(readability-magic-numbers)
1066     }
1067     container.DeduplicateItems(false);
1068 
1069     ASSERT_TRUE(!annot1->NeedsEmit() || !annot2->NeedsEmit());
1070 
1071     container.ComputeLayout();
1072     for (size_t i = 0; i < 4; i++) {
1073         // NOLINTBEGIN(readability-magic-numbers)
1074         ASSERT_EQ(meth->GetCode()->GetInstructions()->at(1 + i),
1075                   static_cast<uint8_t>((str->GetFileId().GetOffset() >> (i * 8)) & 0xffU));
1076         // NOLINTEND(readability-magic-numbers)
1077     }
1078 }
1079 
TEST(ItemContainer,AnnotationDeduplicationReading)1080 TEST(ItemContainer, AnnotationDeduplicationReading)
1081 {
1082     ItemContainer container;
1083 
1084     auto annot = container.GetOrCreateClassItem("Annot");
1085     annot->SetAccessFlags(ACC_PUBLIC | ACC_ANNOTATION);
1086     auto annot1 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1087                                                        std::vector<AnnotationItem::Tag> {});
1088     auto annot2 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1089                                                        std::vector<AnnotationItem::Tag> {});
1090 
1091     auto glb = container.GetOrCreateGlobalClassItem();
1092 
1093     glb->AddAnnotation(annot1);
1094     glb->AddAnnotation(annot2);
1095 
1096     size_t size = container.ComputeLayout();
1097 
1098     container.DeduplicateAnnotations();
1099     ASSERT_FALSE(annot1->NeedsEmit() && annot2->NeedsEmit());
1100     ASSERT_EQ(glb->GetAnnotations()->at(0), glb->GetAnnotations()->at(1));
1101     container.DeduplicateAnnotations();
1102 
1103     std::vector<uint8_t> memBuf(size);
1104     MemoryBufferWriter writer {memBuf.data(), memBuf.size()};
1105     ASSERT_EQ(glb->GetAnnotations()->at(0), glb->GetAnnotations()->at(1));
1106     container.Write(&writer);
1107 
1108     auto emptyDeleter = +[](std::byte *, size_t) noexcept {};
1109     auto reader = FileReader(File::OpenFromMemory(
1110         os::mem::ConstBytePtr(reinterpret_cast<std::byte *>(memBuf.data()), memBuf.size(), emptyDeleter)));
1111     ASSERT_TRUE(reader.ReadContainer());
1112 
1113     auto annots = reader.GetContainerPtr()->GetOrCreateGlobalClassItem()->GetAnnotations();
1114     ASSERT_EQ(annots->size(), 2);
1115     ASSERT_EQ(annots->at(0), annots->at(1));
1116     auto annot3 = reader.GetContainerPtr()->CreateItem<AnnotationItem>(
1117         reader.GetContainerPtr()->GetOrCreateClassItem("Annot"), std::vector<AnnotationItem::Elem> {},
1118         std::vector<AnnotationItem::Tag> {});
1119     annots->insert(annots->begin(), annot3);
1120     reader.GetContainerPtr()->DeduplicateItems(true);
1121     reader.GetContainerPtr()->DeduplicateItems(true);
1122 }
1123 
1124 }  // namespace ark::panda_file::test
1125