1 /**
2 * Copyright (c) 2021-2025 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 File::Header header {};
151 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
152 std::fill(reinterpret_cast<uint8_t *>(&header), reinterpret_cast<uint8_t *>(&header) + sizeof(header), 0);
153 header.magic = File::MAGIC;
154 header.version = {0, 0, 0, 5};
155 header.fileSize = sizeof(File::Header);
156 auto pChecksumData = reinterpret_cast<uint8_t *>(&header.version);
157 auto checksum = adler32(1, pChecksumData, sizeof(header) - offsetof(File::Header, version));
158 header.checksum = checksum;
159 for (uint8_t b : Span<uint8_t>(reinterpret_cast<uint8_t *>(&header), sizeof(header))) {
160 writer.WriteByte(b);
161 }
162 }
163
164 EXPECT_NE(File::Open(fileName), nullptr);
165 }
166
GetPandaFile(std::vector<uint8_t> & data)167 static std::unique_ptr<const File> GetPandaFile(std::vector<uint8_t> &data)
168 {
169 os::mem::ConstBytePtr ptr(reinterpret_cast<std::byte *>(data.data()), data.size(),
170 [](std::byte *, size_t) noexcept {});
171 return File::OpenFromMemory(std::move(ptr));
172 }
173
174 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
TEST(ItemContainer,TestClasses)175 TEST(ItemContainer, TestClasses)
176 {
177 // Write panda file to memory
178
179 ItemContainer container;
180
181 ClassItem *emptyClassItem = container.GetOrCreateClassItem("Foo");
182
183 ClassItem *classItem = container.GetOrCreateClassItem("Bar");
184 classItem->SetAccessFlags(ACC_PUBLIC);
185 classItem->SetSuperClass(emptyClassItem);
186
187 // Add interface
188
189 ClassItem *ifaceItem = container.GetOrCreateClassItem("Iface");
190 ifaceItem->SetAccessFlags(ACC_PUBLIC);
191
192 classItem->AddInterface(ifaceItem);
193
194 // Add method
195
196 StringItem *methodName = container.GetOrCreateStringItem("foo");
197
198 PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
199 std::vector<MethodParamItem> params;
200 ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
201
202 MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
203
204 // Add field
205
206 StringItem *fieldName = container.GetOrCreateStringItem("field");
207 PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
208
209 FieldItem *fieldItem = classItem->AddField(fieldName, fieldType, ACC_PUBLIC);
210
211 // Add runtime annotation
212
213 std::vector<AnnotationItem::Elem> runtimeElems;
214 std::vector<AnnotationItem::Tag> runtimeTags;
215 auto *runtimeAnnotationItem = container.CreateItem<AnnotationItem>(classItem, runtimeElems, runtimeTags);
216
217 classItem->AddRuntimeAnnotation(runtimeAnnotationItem);
218
219 // Add annotation
220
221 std::vector<AnnotationItem::Elem> elems;
222 std::vector<AnnotationItem::Tag> tags;
223 auto *annotationItem = container.CreateItem<AnnotationItem>(classItem, elems, tags);
224
225 classItem->AddAnnotation(annotationItem);
226
227 // Add source file
228
229 StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
230
231 classItem->SetSourceFile(sourceFile);
232
233 MemoryWriter memWriter;
234
235 ASSERT_TRUE(container.Write(&memWriter));
236
237 // Read panda file from memory
238
239 auto data = memWriter.GetData();
240 auto pandaFile = GetPandaFile(data);
241
242 ASSERT_NE(pandaFile, nullptr);
243
244 EXPECT_THAT(pandaFile->GetHeader()->version, ::testing::ElementsAre(0, 0, 0, 5));
245 EXPECT_EQ(pandaFile->GetHeader()->fileSize, memWriter.GetData().size());
246 EXPECT_EQ(pandaFile->GetHeader()->foreignOff, 0U);
247 EXPECT_EQ(pandaFile->GetHeader()->foreignSize, 0U);
248 EXPECT_EQ(pandaFile->GetHeader()->numClasses, 3U);
249 EXPECT_EQ(pandaFile->GetHeader()->classIdxOff, sizeof(File::Header));
250
251 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
252 const auto *classIndex =
253 reinterpret_cast<const uint32_t *>(pandaFile->GetBase() + pandaFile->GetHeader()->classIdxOff);
254 EXPECT_EQ(classIndex[0], classItem->GetOffset());
255 EXPECT_EQ(classIndex[1], emptyClassItem->GetOffset());
256 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
257
258 std::vector<uint8_t> className {'B', 'a', 'r', '\0'};
259 auto classId = pandaFile->GetClassId(className.data());
260 EXPECT_EQ(classId.GetOffset(), classItem->GetOffset());
261
262 ClassDataAccessor classDataAccessor(*pandaFile, classId);
263 EXPECT_EQ(classDataAccessor.GetSuperClassId().GetOffset(), emptyClassItem->GetOffset());
264 EXPECT_EQ(classDataAccessor.GetAccessFlags(), ACC_PUBLIC);
265 EXPECT_EQ(classDataAccessor.GetFieldsNumber(), 1U);
266 EXPECT_EQ(classDataAccessor.GetMethodsNumber(), 1U);
267 EXPECT_EQ(classDataAccessor.GetIfacesNumber(), 1U);
268 EXPECT_TRUE(classDataAccessor.GetSourceFileId().has_value());
269 EXPECT_EQ(classDataAccessor.GetSourceFileId().value().GetOffset(), sourceFile->GetOffset());
270 EXPECT_EQ(classDataAccessor.GetSize(), classItem->GetSize());
271
272 classDataAccessor.EnumerateInterfaces([&](File::EntityId id) {
273 EXPECT_EQ(id.GetOffset(), ifaceItem->GetOffset());
274
275 ClassDataAccessor ifaceClassDataAccessor(*pandaFile, id);
276 EXPECT_EQ(ifaceClassDataAccessor.GetSuperClassId().GetOffset(), 0U);
277 EXPECT_EQ(ifaceClassDataAccessor.GetAccessFlags(), ACC_PUBLIC);
278 EXPECT_EQ(ifaceClassDataAccessor.GetFieldsNumber(), 0U);
279 EXPECT_EQ(ifaceClassDataAccessor.GetMethodsNumber(), 0U);
280 EXPECT_EQ(ifaceClassDataAccessor.GetIfacesNumber(), 0U);
281 EXPECT_FALSE(ifaceClassDataAccessor.GetSourceFileId().has_value());
282 EXPECT_EQ(ifaceClassDataAccessor.GetSize(), ifaceItem->GetSize());
283 });
284
285 classDataAccessor.EnumerateRuntimeAnnotations([&](File::EntityId id) {
286 EXPECT_EQ(id.GetOffset(), runtimeAnnotationItem->GetOffset());
287
288 AnnotationDataAccessor dataAccessor(*pandaFile, id);
289 EXPECT_EQ(dataAccessor.GetAnnotationId().GetOffset(), runtimeAnnotationItem->GetOffset());
290 EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
291 EXPECT_EQ(dataAccessor.GetCount(), 0U);
292 });
293
294 // Annotation is the same as the runtime one, so we deduplicate it
295 EXPECT_FALSE(annotationItem->NeedsEmit());
296 annotationItem = runtimeAnnotationItem;
297
298 classDataAccessor.EnumerateAnnotations([&](File::EntityId id) {
299 EXPECT_EQ(id.GetOffset(), annotationItem->GetOffset());
300
301 AnnotationDataAccessor dataAccessor(*pandaFile, id);
302 EXPECT_EQ(dataAccessor.GetAnnotationId().GetOffset(), annotationItem->GetOffset());
303 EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
304 EXPECT_EQ(dataAccessor.GetCount(), 0U);
305 });
306
307 classDataAccessor.EnumerateFields([&](FieldDataAccessor &dataAccessor) {
308 EXPECT_EQ(dataAccessor.GetFieldId().GetOffset(), fieldItem->GetOffset());
309 EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
310 EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), fieldName->GetOffset());
311 EXPECT_EQ(dataAccessor.GetType(), Type(Type::TypeId::I32).GetFieldEncoding());
312 EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC);
313 EXPECT_FALSE(dataAccessor.GetValue<int32_t>().has_value());
314 EXPECT_EQ(dataAccessor.GetSize(), fieldItem->GetSize());
315
316 dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
317 dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
318 });
319
320 classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
321 EXPECT_FALSE(dataAccessor.IsExternal());
322 EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
323 EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
324 EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
325 EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
326 EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC | ACC_STATIC);
327 EXPECT_FALSE(dataAccessor.GetCodeId().has_value());
328 EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
329 EXPECT_FALSE(dataAccessor.GetRuntimeParamAnnotationId().has_value());
330 EXPECT_FALSE(dataAccessor.GetParamAnnotationId().has_value());
331 EXPECT_FALSE(dataAccessor.GetDebugInfoId().has_value());
332
333 dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
334 dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
335 });
336
337 ClassDataAccessor emptyClassDataAccessor(*pandaFile, File::EntityId(emptyClassItem->GetOffset()));
338 EXPECT_EQ(emptyClassDataAccessor.GetSuperClassId().GetOffset(), 0U);
339 EXPECT_EQ(emptyClassDataAccessor.GetAccessFlags(), 0U);
340 EXPECT_EQ(emptyClassDataAccessor.GetFieldsNumber(), 0U);
341 EXPECT_EQ(emptyClassDataAccessor.GetMethodsNumber(), 0U);
342 EXPECT_EQ(emptyClassDataAccessor.GetIfacesNumber(), 0U);
343 EXPECT_FALSE(emptyClassDataAccessor.GetSourceFileId().has_value());
344 EXPECT_EQ(emptyClassDataAccessor.GetSize(), emptyClassItem->GetSize());
345 }
346
TEST(ItemContainer,TestMethods)347 TEST(ItemContainer, TestMethods)
348 {
349 // Write panda file to memory
350
351 ItemContainer container;
352
353 ClassItem *classItem = container.GetOrCreateClassItem("A");
354 classItem->SetAccessFlags(ACC_PUBLIC);
355
356 StringItem *methodName = container.GetOrCreateStringItem("foo");
357
358 PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
359 std::vector<MethodParamItem> params;
360 ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
361
362 MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
363
364 std::vector<uint8_t> instructions {1, 2, 3, 4};
365 auto *codeItem = container.CreateItem<CodeItem>(0, 2, instructions);
366
367 methodItem->SetCode(codeItem);
368
369 MemoryWriter memWriter;
370
371 ASSERT_TRUE(container.Write(&memWriter));
372
373 // Read panda file from memory
374
375 auto data = memWriter.GetData();
376 auto pandaFile = GetPandaFile(data);
377
378 ASSERT_NE(pandaFile, nullptr);
379
380 ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
381
382 classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
383 EXPECT_FALSE(dataAccessor.IsExternal());
384 EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
385 EXPECT_EQ(dataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
386 EXPECT_EQ(dataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
387 EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
388 EXPECT_EQ(dataAccessor.GetAccessFlags(), ACC_PUBLIC | ACC_STATIC);
389 EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
390
391 auto codeId = dataAccessor.GetCodeId();
392 EXPECT_TRUE(codeId.has_value());
393 EXPECT_EQ(codeId.value().GetOffset(), codeItem->GetOffset());
394
395 CodeDataAccessor codeDataAccessor(*pandaFile, codeId.value());
396 EXPECT_EQ(codeDataAccessor.GetNumVregs(), 0U);
397 EXPECT_EQ(codeDataAccessor.GetNumArgs(), 2U);
398 EXPECT_EQ(codeDataAccessor.GetCodeSize(), instructions.size());
399 EXPECT_THAT(instructions,
400 ::testing::ElementsAreArray(codeDataAccessor.GetInstructions(), codeDataAccessor.GetCodeSize()));
401
402 EXPECT_EQ(codeDataAccessor.GetTriesSize(), 0U);
403 EXPECT_EQ(codeDataAccessor.GetSize(), codeItem->GetSize());
404
405 codeDataAccessor.EnumerateTryBlocks([](const CodeDataAccessor::TryBlock &) {
406 EXPECT_TRUE(false);
407 return false;
408 });
409
410 EXPECT_FALSE(dataAccessor.GetDebugInfoId().has_value());
411
412 EXPECT_FALSE(dataAccessor.GetRuntimeParamAnnotationId().has_value());
413
414 EXPECT_FALSE(dataAccessor.GetParamAnnotationId().has_value());
415
416 dataAccessor.EnumerateRuntimeAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
417
418 dataAccessor.EnumerateAnnotations([](File::EntityId) { EXPECT_TRUE(false); });
419 });
420 }
421
422 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
TestProtos(size_t n)423 void TestProtos(size_t n)
424 {
425 constexpr size_t ELEM_WIDTH = 4;
426 constexpr size_t ELEM_PER16 = 16 / ELEM_WIDTH;
427
428 // Write panda file to memory
429
430 ItemContainer container;
431
432 ClassItem *classItem = container.GetOrCreateClassItem("A");
433 classItem->SetAccessFlags(ACC_PUBLIC);
434
435 StringItem *methodName = container.GetOrCreateStringItem("foo");
436
437 std::vector<Type::TypeId> types {Type::TypeId::VOID, Type::TypeId::I32};
438 std::vector<ClassItem *> refTypes;
439
440 PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
441 std::vector<MethodParamItem> params;
442
443 params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
444
445 for (size_t i = 0; i < ELEM_PER16 * 2U - 2U; i++) {
446 params.emplace_back(container.GetOrCreateClassItem("B"));
447 types.push_back(Type::TypeId::REFERENCE);
448 refTypes.push_back(container.GetOrCreateClassItem("B"));
449 params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F64));
450 types.push_back(Type::TypeId::F64);
451 }
452
453 for (size_t i = 0; i < n; i++) {
454 params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F32));
455 types.push_back(Type::TypeId::F32);
456 }
457
458 ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
459
460 MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
461
462 MemoryWriter memWriter;
463
464 ASSERT_TRUE(container.Write(&memWriter));
465
466 // Read panda file from memory
467
468 auto data = memWriter.GetData();
469 auto pandaFile = GetPandaFile(data);
470
471 ASSERT_NE(pandaFile, nullptr);
472
473 ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
474
475 classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
476 EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
477 EXPECT_EQ(dataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
478
479 ProtoDataAccessor protoDataAccessor(*pandaFile, dataAccessor.GetProtoId());
480 EXPECT_EQ(protoDataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
481
482 size_t num = 0;
483 size_t nref = 0;
484 protoDataAccessor.EnumerateTypes([&](Type t) {
485 EXPECT_EQ(t.GetEncoding(), Type(types[num]).GetEncoding());
486 ++num;
487
488 if (!t.IsPrimitive()) {
489 ++nref;
490 }
491 });
492
493 EXPECT_EQ(num, types.size());
494
495 for (size_t i = 0; i < num - 1; i++) {
496 EXPECT_EQ(protoDataAccessor.GetArgType(i).GetEncoding(), Type(types[i + 1]).GetEncoding());
497 }
498
499 EXPECT_EQ(protoDataAccessor.GetReturnType().GetEncoding(), Type(types[0]).GetEncoding());
500
501 EXPECT_EQ(nref, refTypes.size());
502
503 for (size_t i = 0; i < nref; i++) {
504 EXPECT_EQ(protoDataAccessor.GetReferenceType(0).GetOffset(), refTypes[i]->GetOffset());
505 }
506
507 size_t size = ((num + ELEM_PER16) / ELEM_PER16 + nref) * sizeof(uint16_t);
508
509 EXPECT_EQ(protoDataAccessor.GetSize(), size);
510 EXPECT_EQ(protoDataAccessor.GetSize(), protoItem->GetSize());
511 });
512 }
513
TEST(ItemContainer,TestProtos)514 TEST(ItemContainer, TestProtos)
515 {
516 TestProtos(0);
517 TestProtos(1);
518 TestProtos(2);
519 TestProtos(7);
520 }
521
522 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
TEST(ItemContainer,TestDebugInfo)523 TEST(ItemContainer, TestDebugInfo)
524 {
525 // Write panda file to memory
526
527 ItemContainer container;
528
529 ClassItem *classItem = container.GetOrCreateClassItem("A");
530 classItem->SetAccessFlags(ACC_PUBLIC);
531
532 StringItem *methodName = container.GetOrCreateStringItem("foo");
533
534 PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
535 std::vector<MethodParamItem> params;
536 params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
537 ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
538 MethodItem *methodItem = classItem->AddMethod(methodName, protoItem, ACC_PUBLIC | ACC_STATIC, params);
539
540 StringItem *sourceFileItem = container.GetOrCreateStringItem("<source>");
541 StringItem *sourceCodeItem = container.GetOrCreateStringItem("let a = 1;");
542 StringItem *paramStringItem = container.GetOrCreateStringItem("a0");
543
544 LineNumberProgramItem *lineNumberProgramItem = container.CreateLineNumberProgramItem();
545 auto *debugInfoItem = container.CreateItem<DebugInfoItem>(lineNumberProgramItem);
546 methodItem->SetDebugInfo(debugInfoItem);
547
548 // Add debug info
549
550 container.ComputeLayout();
551
552 std::vector<uint8_t> opcodes {
553 static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_SOURCE_CODE),
554 static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_FILE),
555 static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_PROLOGUE_END),
556 static_cast<uint8_t>(LineNumberProgramItem::Opcode::ADVANCE_PC),
557 static_cast<uint8_t>(LineNumberProgramItem::Opcode::ADVANCE_LINE),
558 static_cast<uint8_t>(LineNumberProgramItem::Opcode::SET_EPILOGUE_BEGIN),
559 static_cast<uint8_t>(LineNumberProgramItem::Opcode::END_SEQUENCE),
560 };
561
562 auto *constantPool = debugInfoItem->GetConstantPool();
563 debugInfoItem->SetLineNumber(5);
564 lineNumberProgramItem->EmitSetSourceCode(constantPool, sourceCodeItem);
565 lineNumberProgramItem->EmitSetFile(constantPool, sourceFileItem);
566 lineNumberProgramItem->EmitPrologueEnd();
567 // NOLINTNEXTLINE(readability-magic-numbers)
568 lineNumberProgramItem->EmitAdvancePc(constantPool, 10);
569 lineNumberProgramItem->EmitAdvanceLine(constantPool, -5);
570 lineNumberProgramItem->EmitEpilogueBegin();
571 lineNumberProgramItem->EmitEnd();
572
573 debugInfoItem->AddParameter(paramStringItem);
574
575 methodItem->SetDebugInfo(debugInfoItem);
576
577 MemoryWriter memWriter;
578
579 ASSERT_TRUE(container.Write(&memWriter));
580
581 // Read panda file from memory
582
583 auto data = memWriter.GetData();
584 auto pandaFile = GetPandaFile(data);
585
586 ASSERT_NE(pandaFile, nullptr);
587
588 ClassDataAccessor classDataAccessor(*pandaFile, File::EntityId(classItem->GetOffset()));
589
590 classDataAccessor.EnumerateMethods([&](MethodDataAccessor &dataAccessor) {
591 EXPECT_EQ(dataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
592 EXPECT_EQ(dataAccessor.GetSize(), methodItem->GetSize());
593
594 auto debugInfoId = dataAccessor.GetDebugInfoId();
595 EXPECT_TRUE(debugInfoId.has_value());
596
597 EXPECT_EQ(debugInfoId.value().GetOffset(), debugInfoItem->GetOffset());
598
599 DebugInfoDataAccessor dda(*pandaFile, debugInfoId.value());
600 EXPECT_EQ(dda.GetDebugInfoId().GetOffset(), debugInfoItem->GetOffset());
601 EXPECT_EQ(dda.GetLineStart(), 5U);
602 EXPECT_EQ(dda.GetNumParams(), params.size());
603
604 dda.EnumerateParameters([&](File::EntityId id) { EXPECT_EQ(id.GetOffset(), paramStringItem->GetOffset()); });
605
606 auto cp = dda.GetConstantPool();
607 EXPECT_EQ(cp.size(), constantPool->size());
608 EXPECT_THAT(*constantPool, ::testing::ElementsAreArray(cp.data(), cp.Size()));
609
610 EXPECT_EQ(helpers::ReadULeb128(&cp), sourceCodeItem->GetOffset());
611 EXPECT_EQ(helpers::ReadULeb128(&cp), sourceFileItem->GetOffset());
612 EXPECT_EQ(helpers::ReadULeb128(&cp), 10U);
613 EXPECT_EQ(helpers::ReadLeb128(&cp), -5);
614
615 const uint8_t *lineNumberProgram = dda.GetLineNumberProgram();
616 EXPECT_EQ(pandaFile->GetIdFromPointer(lineNumberProgram).GetOffset(), lineNumberProgramItem->GetOffset());
617 EXPECT_EQ(lineNumberProgramItem->GetSize(), opcodes.size());
618
619 EXPECT_THAT(opcodes, ::testing::ElementsAreArray(lineNumberProgram, opcodes.size()));
620
621 EXPECT_EQ(dda.GetSize(), debugInfoItem->GetSize());
622 });
623 }
624
TEST(ItemContainer,ForeignItems)625 TEST(ItemContainer, ForeignItems)
626 {
627 ItemContainer container;
628
629 // Create foreign class
630 ForeignClassItem *classItem = container.GetOrCreateForeignClassItem("ForeignClass");
631
632 // Create foreign field
633 StringItem *fieldName = container.GetOrCreateStringItem("foreign_field");
634 PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
635 auto *fieldItem = container.CreateItem<ForeignFieldItem>(classItem, fieldName, fieldType, 0);
636
637 // Create foreign method
638 StringItem *methodName = container.GetOrCreateStringItem("ForeignMethod");
639 PrimitiveTypeItem *retType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
640 std::vector<MethodParamItem> params;
641 params.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
642 ProtoItem *protoItem = container.GetOrCreateProtoItem(retType, params);
643 auto *methodItem = container.CreateItem<ForeignMethodItem>(classItem, methodName, protoItem, 0);
644
645 MemoryWriter memWriter;
646
647 ASSERT_TRUE(container.Write(&memWriter));
648
649 // Read panda file from memory
650
651 auto data = memWriter.GetData();
652 auto pandaFile = GetPandaFile(data);
653
654 ASSERT_NE(pandaFile, nullptr);
655
656 EXPECT_EQ(pandaFile->GetHeader()->foreignOff, classItem->GetOffset());
657
658 size_t foreignSize = classItem->GetSize() + fieldItem->GetSize() + methodItem->GetSize();
659 EXPECT_EQ(pandaFile->GetHeader()->foreignSize, foreignSize);
660
661 ASSERT_TRUE(pandaFile->IsExternal(classItem->GetFileId()));
662
663 MethodDataAccessor methodDataAccessor(*pandaFile, methodItem->GetFileId());
664 EXPECT_EQ(methodDataAccessor.GetMethodId().GetOffset(), methodItem->GetOffset());
665 EXPECT_EQ(methodDataAccessor.GetSize(), methodItem->GetSize());
666 EXPECT_EQ(methodDataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
667 EXPECT_EQ(methodDataAccessor.GetNameId().GetOffset(), methodName->GetOffset());
668 EXPECT_EQ(methodDataAccessor.GetProtoId().GetOffset(), protoItem->GetOffset());
669 EXPECT_TRUE(methodDataAccessor.IsExternal());
670
671 FieldDataAccessor fieldDataAccessor(*pandaFile, fieldItem->GetFileId());
672 EXPECT_EQ(fieldDataAccessor.GetFieldId().GetOffset(), fieldItem->GetOffset());
673 EXPECT_EQ(fieldDataAccessor.GetSize(), fieldItem->GetSize());
674 EXPECT_EQ(fieldDataAccessor.GetClassId().GetOffset(), classItem->GetOffset());
675 EXPECT_EQ(fieldDataAccessor.GetNameId().GetOffset(), fieldName->GetOffset());
676 EXPECT_EQ(fieldDataAccessor.GetType(), fieldType->GetType().GetFieldEncoding());
677 EXPECT_TRUE(fieldDataAccessor.IsExternal());
678 }
679
TEST(ItemContainer,EmptyContainerChecksum)680 TEST(ItemContainer, EmptyContainerChecksum)
681 {
682 using ark::os::file::Mode;
683 using ark::os::file::Open;
684
685 // Write panda file to disk
686 ItemContainer container;
687
688 const std::string fileName = "test_empty_checksum.ark";
689 auto writer = FileWriter(fileName);
690
691 // Initial value of adler32
692 EXPECT_EQ(writer.GetChecksum(), 1);
693 ASSERT_TRUE(container.Write(&writer));
694
695 // At least header was written so the checksum should be changed
696 auto containerChecksum = writer.GetChecksum();
697 EXPECT_NE(containerChecksum, 1);
698
699 // Read panda file from disk
700 auto file = File::Open(fileName);
701 EXPECT_NE(file, nullptr);
702 EXPECT_EQ(file->GetHeader()->checksum, containerChecksum);
703
704 constexpr size_t DATA_OFFSET = 12U;
705 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
706 auto checksum = adler32(1, file->GetBase() + DATA_OFFSET, file->GetHeader()->fileSize - DATA_OFFSET);
707 EXPECT_EQ(file->GetHeader()->checksum, checksum);
708 }
709
TEST(ItemContainer,ContainerChecksum)710 TEST(ItemContainer, ContainerChecksum)
711 {
712 using ark::os::file::Mode;
713 using ark::os::file::Open;
714
715 uint32_t emptyChecksum = 0;
716 {
717 ItemContainer container;
718 const std::string fileName = "test_checksum_empty.ark";
719 auto writer = FileWriter(fileName);
720 ASSERT_TRUE(container.Write(&writer));
721 emptyChecksum = writer.GetChecksum();
722 }
723 ASSERT(emptyChecksum != 0);
724
725 // Create not empty container
726 ItemContainer container;
727 container.GetOrCreateClassItem("C");
728
729 const std::string fileName = "test_checksum.ark";
730 auto writer = FileWriter(fileName);
731
732 ASSERT_TRUE(container.Write(&writer));
733
734 // This checksum must be different from the empty one (collision may happen though)
735 auto containerChecksum = writer.GetChecksum();
736 EXPECT_NE(emptyChecksum, containerChecksum);
737
738 // Read panda file from disk
739 auto file = File::Open(fileName);
740 EXPECT_NE(file, nullptr);
741 EXPECT_EQ(file->GetHeader()->checksum, containerChecksum);
742
743 constexpr size_t DATA_OFFSET = 12U;
744 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
745 auto checksum = adler32(1, file->GetBase() + DATA_OFFSET, file->GetHeader()->fileSize - DATA_OFFSET);
746 EXPECT_EQ(file->GetHeader()->checksum, checksum);
747 }
748
749 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
TEST(ItemContainer,TestProfileGuidedRelayout)750 TEST(ItemContainer, TestProfileGuidedRelayout)
751 {
752 ItemContainer container;
753
754 // Add classes
755 ClassItem *emptyClassItem = container.GetOrCreateClassItem("LTest;");
756 ClassItem *classItemA = container.GetOrCreateClassItem("LAA;");
757 classItemA->SetSuperClass(emptyClassItem);
758 ClassItem *classItemB = container.GetOrCreateClassItem("LBB;");
759
760 // Add method1
761 StringItem *methodName1 = container.GetOrCreateStringItem("foo1");
762 PrimitiveTypeItem *retType1 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
763 std::vector<MethodParamItem> params1;
764 ProtoItem *protoItem1 = container.GetOrCreateProtoItem(retType1, params1);
765 MethodItem *methodItem1 = classItemA->AddMethod(methodName1, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
766 // Set code_1
767 std::vector<uint8_t> instructions1 {1, 2, 3, 4};
768 auto *codeItem1 = container.CreateItem<CodeItem>(0, 2, instructions1);
769 methodItem1->SetCode(codeItem1);
770 codeItem1->AddMethod(methodItem1);
771
772 // Add method2
773 StringItem *methodName2 = container.GetOrCreateStringItem("foo2");
774 PrimitiveTypeItem *retType2 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
775 std::vector<MethodParamItem> params2;
776 ProtoItem *protoItem2 = container.GetOrCreateProtoItem(retType2, params2);
777 MethodItem *methodItem2 = classItemB->AddMethod(methodName2, protoItem2, ACC_PUBLIC | ACC_STATIC, params2);
778 // Set code_2
779 std::vector<uint8_t> instructions2 {5, 6, 7, 8};
780 auto *codeItem2 = container.CreateItem<CodeItem>(0, 2, instructions2);
781 methodItem2->SetCode(codeItem2);
782 codeItem2->AddMethod(methodItem2);
783
784 // Add method_3
785 StringItem *methodName3 = container.GetOrCreateStringItem("foo3");
786 auto *methodItem3 = emptyClassItem->AddMethod(methodName3, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
787 // Set code_3
788 std::vector<uint8_t> instructions3 {3, 4, 5, 6};
789 auto *codeItem3 = container.CreateItem<CodeItem>(0, 2, instructions3);
790 methodItem3->SetCode(codeItem3);
791 codeItem3->AddMethod(methodItem3);
792
793 // Add method_4
794 StringItem *methodName4 = container.GetOrCreateStringItem("foo4");
795 auto *methodItem4 = emptyClassItem->AddMethod(methodName4, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
796 // Set code. method_4 and method_3 share code_item_3
797 methodItem4->SetCode(codeItem3);
798 codeItem3->AddMethod(methodItem4);
799
800 // Add field
801 StringItem *fieldName = container.GetOrCreateStringItem("test_field");
802 PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
803 classItemA->AddField(fieldName, fieldType, ACC_PUBLIC);
804
805 // Add source file
806 StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
807 classItemA->SetSourceFile(sourceFile);
808
809 constexpr std::string_view PRIMITIVE_TYPE_ITEM = "primitive_type_item";
810 constexpr std::string_view PROTO_ITEM = "proto_item";
811 constexpr std::string_view END_ITEM = "end_item";
812
813 // Items before PGO
814 const auto &items = container.GetItems();
815 auto item = items.begin();
816 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
817 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "Test");
818 item++;
819 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
820 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "AA");
821 item++;
822 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
823 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "BB");
824 item++;
825 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
826 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo1");
827 item++;
828 EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
829 item++;
830 EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
831 item++;
832 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
833 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo2");
834 item++;
835 EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
836 item++;
837 EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
838 item++;
839 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
840 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo3");
841 item++;
842 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
843 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo4");
844 item++;
845 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
846 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "test_field");
847 item++;
848 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
849 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "source_file");
850 item++;
851 EXPECT_EQ((*item)->GetName(), END_ITEM);
852 item++;
853 EXPECT_EQ((*item)->GetName(), END_ITEM);
854 item++;
855 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
856 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "AA::foo1");
857 item++;
858 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
859 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "BB::foo2");
860 item++;
861 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
862 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "Test::foo3");
863 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[1], "Test::foo4");
864 item++;
865 EXPECT_EQ((*item)->GetName(), END_ITEM);
866 item++;
867 EXPECT_EQ((*item)->GetName(), END_ITEM);
868 item++;
869 EXPECT_EQ(item, items.end());
870
871 // Prepare profile data
872 std::string profilePath = "TestProfileGuidedRelayout_profile_test_data.txt";
873 std::ofstream testFile;
874 testFile.open(profilePath);
875 testFile << "string_item:test_field" << std::endl;
876 testFile << "class_item:BB" << std::endl;
877 testFile << "code_item:BB::foo2" << std::endl;
878 testFile << "code_item:Test::foo4" << std::endl;
879 testFile.close();
880
881 // Run PGO
882 ark::panda_file::pgo::ProfileOptimizer profileOpt;
883 profileOpt.SetProfilePath(profilePath);
884 container.ReorderItems(&profileOpt);
885
886 // Items after PGO
887 item = items.begin();
888 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
889 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "test_field");
890 item++;
891 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
892 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo1");
893 item++;
894 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
895 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo2");
896 item++;
897 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
898 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo3");
899 item++;
900 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
901 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "foo4");
902 item++;
903 EXPECT_EQ((*item)->GetName(), STRING_ITEM);
904 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "source_file");
905 item++;
906 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
907 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "BB");
908 item++;
909 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
910 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "Test");
911 item++;
912 EXPECT_EQ((*item)->GetName(), CLASS_ITEM);
913 EXPECT_EQ(ark::panda_file::pgo::ProfileOptimizer::GetNameInfo(*item), "AA");
914 item++;
915 EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
916 item++;
917 EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
918 item++;
919 EXPECT_EQ((*item)->GetName(), PRIMITIVE_TYPE_ITEM);
920 item++;
921 EXPECT_EQ((*item)->GetName(), PROTO_ITEM);
922 item++;
923 EXPECT_EQ((*item)->GetName(), END_ITEM);
924 item++;
925 EXPECT_EQ((*item)->GetName(), END_ITEM);
926 item++;
927 EXPECT_EQ((*item)->GetName(), END_ITEM);
928 item++;
929 EXPECT_EQ((*item)->GetName(), END_ITEM);
930 item++;
931 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
932 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "BB::foo2");
933 item++;
934 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
935 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "Test::foo3");
936 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[1], "Test::foo4");
937 item++;
938 EXPECT_EQ((*item)->GetName(), CODE_ITEM);
939 EXPECT_EQ(static_cast<CodeItem *>((*item).get())->GetMethodNames()[0], "AA::foo1");
940 item++;
941 EXPECT_EQ(item, items.end());
942 }
943
944 // CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
TEST(ItemContainer,GettersTest)945 TEST(ItemContainer, GettersTest)
946 {
947 ItemContainer container;
948
949 ClassItem *emptyClassItem = container.GetOrCreateClassItem("Foo");
950
951 ClassItem *classItem = container.GetOrCreateClassItem("Bar");
952 classItem->SetAccessFlags(ACC_PUBLIC);
953 classItem->SetSuperClass(emptyClassItem);
954
955 // Add methods
956
957 StringItem *methodName1 = container.GetOrCreateStringItem("foo1");
958
959 PrimitiveTypeItem *retType1 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID);
960 std::vector<MethodParamItem> params1;
961 params1.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32));
962 ProtoItem *protoItem1 = container.GetOrCreateProtoItem(retType1, params1);
963
964 classItem->AddMethod(methodName1, protoItem1, ACC_PUBLIC | ACC_STATIC, params1);
965
966 StringItem *methodName2 = container.GetOrCreateStringItem("foo2");
967
968 PrimitiveTypeItem *retType2 = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
969 std::vector<MethodParamItem> params2;
970 params2.emplace_back(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::F32));
971 ProtoItem *protoItem2 = container.GetOrCreateProtoItem(retType2, params2);
972
973 classItem->AddMethod(methodName2, protoItem2, ACC_PUBLIC | ACC_STATIC, params2);
974
975 // Add field
976
977 StringItem *fieldName = container.GetOrCreateStringItem("field");
978 PrimitiveTypeItem *fieldType = container.GetOrCreatePrimitiveTypeItem(Type::TypeId::I32);
979
980 classItem->AddField(fieldName, fieldType, ACC_PUBLIC);
981
982 // Add source file
983
984 StringItem *sourceFile = container.GetOrCreateStringItem("source_file");
985
986 classItem->SetSourceFile(sourceFile);
987
988 // Read items from container
989
990 ASSERT_TRUE(container.GetItems().size() == 15);
991
992 std::map<std::string, panda_file::BaseClassItem *> *classMap = container.GetClassMap();
993 ASSERT_TRUE(classMap != nullptr && classMap->size() == 2);
994 auto it = classMap->find("Bar");
995 ASSERT_TRUE(it != classMap->end());
996
997 std::unordered_map<std::string, StringItem *> *stringMap = container.GetStringMap();
998 ASSERT_TRUE(stringMap != nullptr && stringMap->size() == 4);
999 auto sit0 = stringMap->find("field");
1000 auto sit1 = stringMap->find("source_file");
1001 auto sit2 = stringMap->find("foo1");
1002 auto sit3 = stringMap->find("foo2");
1003 ASSERT_TRUE(sit0 != stringMap->end() && sit1 != stringMap->end() && sit2 != stringMap->end() &&
1004 sit3 != stringMap->end());
1005
1006 std::unordered_map<Type::TypeId, PrimitiveTypeItem *> *primitiveTypeMap = container.GetPrimitiveTypeMap();
1007 ASSERT_TRUE(primitiveTypeMap != nullptr && primitiveTypeMap->size() == 3);
1008 auto pit0 = primitiveTypeMap->find(Type::TypeId::F32);
1009 auto pit1 = primitiveTypeMap->find(Type::TypeId::I32);
1010 auto pit2 = primitiveTypeMap->find(Type::TypeId::VOID);
1011 ASSERT_TRUE(pit0 != primitiveTypeMap->end() && pit1 != primitiveTypeMap->end() && pit2 != primitiveTypeMap->end());
1012
1013 auto *rclassItem = static_cast<panda_file::ClassItem *>(it->second);
1014 std::string methodName;
1015 std::function<bool(BaseItem *)> testMethod = [&](BaseItem *method) {
1016 auto *methodItem = static_cast<panda_file::MethodItem *>(method);
1017 ASSERT(methodItem != nullptr && methodItem->GetItemType() == ItemTypes::METHOD_ITEM);
1018 methodName = methodItem->GetNameItem()->GetData();
1019 methodName.pop_back(); // remove '\0'
1020 ASSERT(methodName == "foo1" || methodName == "foo2");
1021 return true;
1022 };
1023
1024 using std::placeholders::_1;
1025 panda_file::BaseItem::VisitorCallBack cbMethod = testMethod;
1026 rclassItem->VisitMethods(cbMethod);
1027
1028 std::string fName;
1029 std::function<bool(BaseItem *)> testField = [&](BaseItem *field) {
1030 auto *fieldItem = static_cast<panda_file::FieldItem *>(field);
1031 ASSERT(fieldItem != nullptr && fieldItem->GetItemType() == ItemTypes::FIELD_ITEM);
1032 ASSERT(fieldItem->GetBaseItemType() == ItemTypes::ANNOTATION_ITEM);
1033 fieldItem->SetBaseItemType(ItemTypes::FIELD_ITEM);
1034 ASSERT(fieldItem->GetBaseItemType() == ItemTypes::FIELD_ITEM);
1035 fName = fieldItem->GetNameItem()->GetData();
1036 fName.pop_back(); // remove '\0'
1037 ASSERT(fName == "field");
1038 return true;
1039 };
1040
1041 panda_file::BaseItem::VisitorCallBack cbField = testField;
1042 rclassItem->VisitFields(cbField);
1043 }
1044
TEST(ItemContainer,AnnotationDeduplication)1045 TEST(ItemContainer, AnnotationDeduplication)
1046 {
1047 ItemContainer container;
1048
1049 auto annot = container.GetOrCreateClassItem("Annot");
1050 annot->SetAccessFlags(ACC_PUBLIC | ACC_ANNOTATION);
1051 auto annot1 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1052 std::vector<AnnotationItem::Tag> {});
1053 auto annot2 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1054 std::vector<AnnotationItem::Tag> {});
1055
1056 auto str = container.GetOrCreateStringItem("Abc");
1057
1058 auto glb = container.GetOrCreateGlobalClassItem();
1059 auto proto = container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1060 auto meth = glb->AddMethod(container.GetOrCreateStringItem("foo"), proto, ACC_STATIC | ACC_PUBLIC,
1061 std::vector<MethodParamItem> {});
1062 meth->AddAnnotation(annot1);
1063 meth->AddAnnotation(annot2);
1064
1065 std::vector<uint8_t> ins = {(uint8_t)BytecodeInstruction::Opcode::LDA_STR_ID32, 0, 0, 0, 0,
1066 (uint8_t)BytecodeInstruction::Opcode::RETURN_VOID};
1067 meth->SetCode(container.CreateItem<CodeItem>(0, 0, ins));
1068
1069 container.ComputeLayout();
1070 for (size_t i = 0; i < 4; i++) {
1071 // NOLINTBEGIN(readability-magic-numbers)
1072 meth->GetCode()->GetInstructions()->at(1 + i) =
1073 static_cast<uint8_t>((str->GetFileId().GetOffset() >> (i * 8)) & 0xffU);
1074 // NOLINTEND(readability-magic-numbers)
1075 }
1076 container.DeduplicateItems(false);
1077
1078 ASSERT_TRUE(!annot1->NeedsEmit() || !annot2->NeedsEmit());
1079
1080 container.ComputeLayout();
1081 for (size_t i = 0; i < 4; i++) {
1082 // NOLINTBEGIN(readability-magic-numbers)
1083 ASSERT_EQ(meth->GetCode()->GetInstructions()->at(1 + i),
1084 static_cast<uint8_t>((str->GetFileId().GetOffset() >> (i * 8)) & 0xffU));
1085 // NOLINTEND(readability-magic-numbers)
1086 }
1087 }
1088
TEST(ItemContainer,AnnotationDeduplicationReading)1089 TEST(ItemContainer, AnnotationDeduplicationReading)
1090 {
1091 ItemContainer container;
1092
1093 auto annot = container.GetOrCreateClassItem("Annot");
1094 annot->SetAccessFlags(ACC_PUBLIC | ACC_ANNOTATION);
1095 auto annot1 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1096 std::vector<AnnotationItem::Tag> {});
1097 auto annot2 = container.CreateItem<AnnotationItem>(annot, std::vector<AnnotationItem::Elem> {},
1098 std::vector<AnnotationItem::Tag> {});
1099
1100 auto glb = container.GetOrCreateGlobalClassItem();
1101
1102 glb->AddAnnotation(annot1);
1103 glb->AddAnnotation(annot2);
1104
1105 size_t size = container.ComputeLayout();
1106
1107 container.DeduplicateAnnotations();
1108 ASSERT_FALSE(annot1->NeedsEmit() && annot2->NeedsEmit());
1109 ASSERT_EQ(glb->GetAnnotations()->at(0), glb->GetAnnotations()->at(1));
1110 container.DeduplicateAnnotations();
1111
1112 std::vector<uint8_t> memBuf(size);
1113 MemoryBufferWriter writer {memBuf.data(), memBuf.size()};
1114 ASSERT_EQ(glb->GetAnnotations()->at(0), glb->GetAnnotations()->at(1));
1115 container.Write(&writer);
1116
1117 auto emptyDeleter = +[](std::byte *, size_t) noexcept {};
1118 auto reader = FileReader(File::OpenFromMemory(
1119 os::mem::ConstBytePtr(reinterpret_cast<std::byte *>(memBuf.data()), memBuf.size(), emptyDeleter)));
1120 ASSERT_TRUE(reader.ReadContainer());
1121
1122 auto annots = reader.GetContainerPtr()->GetOrCreateGlobalClassItem()->GetAnnotations();
1123 ASSERT_EQ(annots->size(), 2);
1124 ASSERT_EQ(annots->at(0), annots->at(1));
1125 auto annot3 = reader.GetContainerPtr()->CreateItem<AnnotationItem>(
1126 reader.GetContainerPtr()->GetOrCreateClassItem("Annot"), std::vector<AnnotationItem::Elem> {},
1127 std::vector<AnnotationItem::Tag> {});
1128 annots->insert(annots->begin(), annot3);
1129 reader.GetContainerPtr()->DeduplicateItems(true);
1130 reader.GetContainerPtr()->DeduplicateItems(true);
1131 }
1132
TEST(ItemContainer,CodeSizeZeroShouldFatal)1133 TEST(ItemContainer, CodeSizeZeroShouldFatal)
1134 {
1135 EXPECT_DEATH(
1136 {
1137 ItemContainer container;
1138 auto *klass = container.GetOrCreateClassItem("DeathTest_ZeroCodeSize");
1139 auto *methodName = container.GetOrCreateStringItem("test_method_zero");
1140 auto *proto =
1141 container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1142 auto *method =
1143 klass->AddMethod(methodName, proto, ACC_PUBLIC | ACC_STATIC, std::vector<MethodParamItem> {});
1144
1145 std::vector<uint8_t> code {};
1146 auto *codeItem = container.CreateItem<CodeItem>(0, 0, code);
1147 method->SetCode(codeItem);
1148
1149 CodeItem::CatchBlock cb(method, nullptr, 0);
1150 CodeItem::TryBlock tryBlock(0, 1, {cb});
1151 codeItem->AddTryBlock(tryBlock);
1152
1153 MemoryWriter writer;
1154 ASSERT_TRUE(container.Write(&writer));
1155 auto data = writer.GetData();
1156
1157 auto pandaFile = GetPandaFile(data);
1158 ASSERT_NE(pandaFile, nullptr);
1159
1160 ClassDataAccessor cda(*pandaFile, File::EntityId(klass->GetOffset()));
1161 cda.EnumerateMethods([&](MethodDataAccessor &mda) {
1162 CodeDataAccessor codeData(*pandaFile, mda.GetCodeId().value());
1163 codeData.EnumerateTryBlocks([](const CodeDataAccessor::TryBlock &) { return true; });
1164 });
1165 },
1166 ".*");
1167 }
1168
TEST(ItemContainer,StartPcTooBigShouldFatal)1169 TEST(ItemContainer, StartPcTooBigShouldFatal)
1170 {
1171 EXPECT_DEATH(
1172 {
1173 ItemContainer container;
1174 auto *klass = container.GetOrCreateClassItem("DeathTestKlass");
1175 auto *methodName = container.GetOrCreateStringItem("test_method");
1176 auto *proto =
1177 container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1178 auto *method =
1179 klass->AddMethod(methodName, proto, ACC_PUBLIC | ACC_STATIC, std::vector<MethodParamItem> {});
1180
1181 std::vector<uint8_t> instructions(4, 0);
1182 auto *codeItem = container.CreateItem<CodeItem>(0, 0, instructions);
1183 method->SetCode(codeItem);
1184
1185 CodeItem::CatchBlock cb(method, nullptr, 0);
1186 CodeItem::TryBlock tryBlock(4, 1, std::vector {cb});
1187 codeItem->AddTryBlock(tryBlock);
1188
1189 MemoryWriter writer;
1190 ASSERT_TRUE(container.Write(&writer));
1191 auto data = writer.GetData();
1192
1193 auto pandaFile = GetPandaFile(data);
1194 ASSERT_NE(pandaFile, nullptr);
1195
1196 ClassDataAccessor cda(*pandaFile, File::EntityId(klass->GetOffset()));
1197 cda.EnumerateMethods([&](MethodDataAccessor &mda) {
1198 CodeDataAccessor codeData(*pandaFile, mda.GetCodeId().value());
1199 codeData.EnumerateTryBlocks([&](const CodeDataAccessor::TryBlock &) { return true; });
1200 });
1201 },
1202 ".*");
1203 }
1204
TEST(ItemContainer,LengthTooBigShouldFatal)1205 TEST(ItemContainer, LengthTooBigShouldFatal)
1206 {
1207 EXPECT_DEATH(
1208 {
1209 ItemContainer container;
1210 auto *klass = container.GetOrCreateClassItem("DeathTest_LengthTooBig");
1211 auto *methodName = container.GetOrCreateStringItem("test_method_len");
1212 auto *proto =
1213 container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1214 auto *method =
1215 klass->AddMethod(methodName, proto, ACC_PUBLIC | ACC_STATIC, std::vector<MethodParamItem> {});
1216
1217 std::vector<uint8_t> code(4, 0);
1218 auto *codeItem = container.CreateItem<CodeItem>(0, 0, code);
1219 method->SetCode(codeItem);
1220
1221 CodeItem::CatchBlock cb(method, nullptr, 0);
1222 CodeItem::TryBlock tryBlock(0, 5, {cb});
1223 codeItem->AddTryBlock(tryBlock);
1224
1225 MemoryWriter writer;
1226 ASSERT_TRUE(container.Write(&writer));
1227 auto data = writer.GetData();
1228
1229 auto pandaFile = GetPandaFile(data);
1230 ASSERT_NE(pandaFile, nullptr);
1231
1232 ClassDataAccessor cda(*pandaFile, File::EntityId(klass->GetOffset()));
1233 cda.EnumerateMethods([&](MethodDataAccessor &mda) {
1234 CodeDataAccessor codeData(*pandaFile, mda.GetCodeId().value());
1235 codeData.EnumerateTryBlocks([](const CodeDataAccessor::TryBlock &) { return true; });
1236 });
1237 },
1238 ".*");
1239 }
1240
1241 class FakeCatchBlock : public ark::panda_file::CodeItem::CatchBlock {
1242 public:
1243 using CodeItem::CatchBlock::CatchBlock;
1244
CalculateSize() const1245 size_t CalculateSize() const override
1246 {
1247 return std::numeric_limits<size_t>::max();
1248 }
1249
Write(Writer * writer)1250 bool Write(Writer *writer) override
1251 {
1252 writer->Write<uint32_t>(0);
1253 return true;
1254 }
1255
GetItemType() const1256 ItemTypes GetItemType() const override
1257 {
1258 return ItemTypes::CATCH_BLOCK_ITEM;
1259 }
1260
IsValid() const1261 bool IsValid() const
1262 {
1263 return true;
1264 }
1265 };
1266
TriggerFatalTryBlock()1267 void TriggerFatalTryBlock()
1268 {
1269 ItemContainer container;
1270 auto *klass = container.GetOrCreateClassItem("DeathTestFatal");
1271 auto *methodName = container.GetOrCreateStringItem("test_fatal");
1272 auto *proto = container.GetOrCreateProtoItem(container.GetOrCreatePrimitiveTypeItem(Type::TypeId::VOID), {});
1273 auto *method = klass->AddMethod(methodName, proto, ACC_PUBLIC | ACC_STATIC, std::vector<MethodParamItem> {});
1274
1275 // NOLINTNEXTLINE(readability-identifier-naming)
1276 constexpr uint32_t kCodeSize = 2;
1277 std::vector<uint8_t> code(kCodeSize, 0);
1278 auto *codeItem = container.CreateItem<CodeItem>(0, 0, code);
1279 method->SetCode(codeItem);
1280
1281 CodeItem::CatchBlock cb(method, nullptr, 0);
1282 // NOLINTNEXTLINE(readability-identifier-naming)
1283 constexpr uint32_t kTryBlockLength = 10;
1284 CodeItem::TryBlock tryBlock(0, kTryBlockLength, {cb});
1285 codeItem->AddTryBlock(tryBlock);
1286
1287 MemoryWriter writer;
1288 ASSERT_TRUE(container.Write(&writer));
1289 auto buf = writer.GetData();
1290
1291 auto pandaFile = GetPandaFile(buf);
1292 ASSERT_NE(pandaFile, nullptr);
1293
1294 ClassDataAccessor cda(*pandaFile, File::EntityId(klass->GetOffset()));
1295 cda.EnumerateMethods([&](MethodDataAccessor &mda) {
1296 CodeDataAccessor codeData(*pandaFile, mda.GetCodeId().value());
1297 codeData.EnumerateTryBlocks([](const CodeDataAccessor::TryBlock &) { return true; });
1298 });
1299 }
1300
TEST(ItemContainer,TryBlockDeclaredSizeOverflowShouldFatal)1301 TEST(ItemContainer, TryBlockDeclaredSizeOverflowShouldFatal)
1302 {
1303 EXPECT_DEATH(TriggerFatalTryBlock(), ".*");
1304 }
1305
CreateNormalPandaFile()1306 static std::vector<uint8_t> CreateNormalPandaFile()
1307 {
1308 ark::panda_file::ItemContainer container;
1309
1310 ark::panda_file::ClassItem *classItem = container.GetOrCreateClassItem("TestClass");
1311 classItem->SetAccessFlags(ark::ACC_PUBLIC);
1312
1313 ark::panda_file::LiteralArrayItem *literalArray = container.GetOrCreateLiteralArrayItem("test_literal");
1314
1315 std::vector<ark::panda_file::LiteralItem> literalItems;
1316
1317 // NOLINTNEXTLINE(readability-magic-numbers)
1318 constexpr uint32_t LITERAL_VALUE = 42U;
1319 literalItems.emplace_back(LITERAL_VALUE);
1320 literalArray->AddItems(literalItems);
1321 ark::panda_file::MemoryWriter writer;
1322 EXPECT_TRUE(container.Write(&writer));
1323 return writer.GetData();
1324 }
1325
CreateCorruptedPandaFile()1326 static std::vector<uint8_t> CreateCorruptedPandaFile()
1327 {
1328 auto normalData = CreateNormalPandaFile();
1329
1330 auto *header = reinterpret_cast<ark::panda_file::File::Header *>(normalData.data());
1331 const int literalArrayOffset = 1000;
1332 header->literalarrayIdxOff = header->fileSize + literalArrayOffset;
1333 auto checksumSize = sizeof(ark::panda_file::File::Header::checksum);
1334 auto fileContentOffset = ark::panda_file::File::MAGIC_SIZE + checksumSize;
1335
1336 // need to modify the checksum or can not pass the ValidateChecksum
1337 uint8_t *pData = &normalData[fileContentOffset];
1338 uint32_t calChecksum = adler32(1, pData, header->fileSize - fileContentOffset);
1339 header->checksum = calChecksum;
1340
1341 return normalData;
1342 }
1343
TEST(ItemContainer,LiteralDataAccessorTest)1344 TEST(ItemContainer, LiteralDataAccessorTest)
1345 {
1346 auto data = CreateCorruptedPandaFile();
1347 auto pandaFile = GetPandaFile(data);
1348 ASSERT_NE(pandaFile, nullptr);
1349
1350 File::EntityId literalArraysId = pandaFile->GetLiteralArraysId();
1351 ark::Logger::InitializeStdLogging(ark::Logger::Level::FATAL,
1352 ark::Logger::ComponentMask().set(ark::Logger::Component::PANDAFILE));
1353 EXPECT_DEATH({ std::make_unique<ark::panda_file::LiteralDataAccessor>(*pandaFile, literalArraysId); },
1354 ".*Literal data size is greater than file size.*|.*");
1355 }
1356
1357 } // namespace ark::panda_file::test
1358