• 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 <gtest/gtest.h>
17 
18 #include <algorithm>
19 #include <memory>
20 #include <ostream>
21 #include <unordered_set>
22 #include <vector>
23 
24 #include "assembly-emitter.h"
25 #include "assembly-parser.h"
26 #include "libpandabase/utils/utf.h"
27 #include "libpandafile/modifiers.h"
28 #include "runtime/include/class-inl.h"
29 #include "runtime/include/class_linker-inl.h"
30 #include "runtime/include/class_linker.h"
31 #include "runtime/include/coretypes/tagged_value.h"
32 #include "runtime/include/object_header.h"
33 #include "runtime/include/runtime.h"
34 #include "runtime/core/core_class_linker_extension.h"
35 #include "runtime/tests/class_linker_test_extension.h"
36 
37 namespace ark::test {
38 
39 class ClassLinkerTest : public testing::Test {
40 public:
ClassLinkerTest()41     ClassLinkerTest()
42     {
43         // Just for internal allocator
44         RuntimeOptions options;
45         options.SetShouldLoadBootPandaFiles(false);
46         options.SetShouldInitializeIntrinsics(false);
47         options.SetGcType("epsilon");
48         // NOLINTNEXTLINE(readability-magic-numbers)
49         options.SetHeapSizeLimit(64_MB);
50         Runtime::Create(options);
51         thread_ = ark::MTManagedThread::GetCurrent();
52         thread_->ManagedCodeBegin();
53     }
54 
~ClassLinkerTest()55     ~ClassLinkerTest() override
56     {
57         thread_->ManagedCodeEnd();
58         Runtime::Destroy();
59     }
60 
61     NO_COPY_SEMANTIC(ClassLinkerTest);
62     NO_MOVE_SEMANTIC(ClassLinkerTest);
63 
64 protected:
65     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
66     ark::MTManagedThread *thread_;
67 };
68 
CreateClassLinker(ManagedThread * thread)69 static std::unique_ptr<ClassLinker> CreateClassLinker(ManagedThread *thread)
70 {
71     std::vector<std::unique_ptr<ClassLinkerExtension>> extensions;
72     extensions.push_back(std::make_unique<CoreClassLinkerExtension>());
73     auto allocator = thread->GetVM()->GetHeapManager()->GetInternalAllocator();
74     auto classLinker = std::make_unique<ClassLinker>(allocator, std::move(extensions));
75     if (!classLinker->Initialize()) {
76         return nullptr;
77     }
78 
79     return classLinker;
80 }
81 
TEST_F(ClassLinkerTest,TestGetClass)82 TEST_F(ClassLinkerTest, TestGetClass)
83 {
84     pandasm::Parser p;
85 
86     auto source = R"(
87         .function void main() {
88             return.void
89         }
90     )";
91 
92     auto res = p.Parse(source);
93     auto pf = pandasm::AsmEmitter::Emit(res.Value());
94 
95     auto classLinker = CreateClassLinker(thread_);
96     ASSERT_NE(classLinker, nullptr);
97 
98     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
99     auto *ext = classLinker->GetExtension(ctx);
100 
101     auto *pfPtr = pf.get();
102     classLinker->AddPandaFile(std::move(pf));
103 
104     Class *klass;
105 
106     {
107         // Use temporary string to load class. Class loader shouldn't store it.
108         auto descriptor = std::make_unique<PandaString>();
109         klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), descriptor.get()));
110     }
111 
112     PandaString descriptor;
113 
114     EXPECT_EQ(klass, ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor)));
115     EXPECT_EQ(klass->GetBase(), ext->GetClassRoot(ClassRoot::OBJECT));
116     EXPECT_EQ(klass->GetPandaFile(), pfPtr);
117     EXPECT_EQ(klass->GetMethods().size(), 1U);
118     EXPECT_EQ(klass->GetComponentSize(), 0U);
119 }
120 
GetClassesForEnumerateClassesTest()121 std::set<std::string> GetClassesForEnumerateClassesTest()
122 {
123     return {"panda.Object",
124             "panda.String",
125             "panda.Class",
126             "[Lpanda/String;",
127             "u1",
128             "i8",
129             "u8",
130             "i16",
131             "u16",
132             "i32",
133             "u32",
134             "i64",
135             "u64",
136             "f32",
137             "f64",
138             "any",
139             "[Z",
140             "[B",
141             "[H",
142             "[S",
143             "[C",
144             "[I",
145             "[U",
146             "[J",
147             "[Q",
148             "[F",
149             "[D",
150             "[A",
151             "[Lpanda/Class;",
152             "_GLOBAL"};
153 }
154 
TEST_F(ClassLinkerTest,TestEnumerateClasses)155 TEST_F(ClassLinkerTest, TestEnumerateClasses)
156 {
157     pandasm::Parser p;
158 
159     auto source = R"(
160         .function void main() {
161             return.void
162         }
163     )";
164 
165     auto res = p.Parse(source);
166     auto pf = pandasm::AsmEmitter::Emit(res.Value());
167 
168     auto classLinker = CreateClassLinker(thread_);
169     ASSERT_NE(classLinker, nullptr);
170 
171     classLinker->AddPandaFile(std::move(pf));
172 
173     PandaString descriptor;
174 
175     // Load _GLOBAL class
176     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
177     ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
178 
179     std::set<std::string> classes = GetClassesForEnumerateClassesTest();
180 
181     std::set<std::string> loadedClasses;
182 
183     classLinker->EnumerateClasses([&loadedClasses](Class *k) {
184         loadedClasses.emplace(k->GetName());
185         return true;
186     });
187 
188     EXPECT_EQ(loadedClasses, classes);
189 }
190 
TestPrimitiveClassRoot(const ClassLinkerExtension & classLinkerExt,ClassRoot classRoot,panda_file::Type::TypeId typeId)191 static void TestPrimitiveClassRoot(const ClassLinkerExtension &classLinkerExt, ClassRoot classRoot,
192                                    panda_file::Type::TypeId typeId)
193 {
194     std::string msg = "Test with class root ";
195     msg += std::to_string(static_cast<int>(classRoot));
196 
197     Class *klass = classLinkerExt.GetClassRoot(classRoot);
198     ASSERT_NE(klass, nullptr) << msg;
199     EXPECT_EQ(klass->GetBase(), nullptr) << msg;
200     EXPECT_EQ(klass->GetComponentSize(), 0U) << msg;
201     EXPECT_EQ(klass->GetFlags(), 0U) << msg;
202     EXPECT_EQ(klass->GetAccessFlags(), ACC_PUBLIC | ACC_FINAL | ACC_ABSTRACT) << msg;
203     EXPECT_EQ(klass->GetType().GetId(), typeId) << msg;
204     EXPECT_FALSE(klass->IsArrayClass()) << msg;
205     EXPECT_FALSE(klass->IsStringClass()) << msg;
206     EXPECT_TRUE(klass->IsPrimitive()) << msg;
207     EXPECT_TRUE(klass->IsAbstract()) << msg;
208     EXPECT_FALSE(klass->IsClass()) << msg;
209     EXPECT_FALSE(klass->IsInterface()) << msg;
210     EXPECT_FALSE(klass->IsInstantiable()) << msg;
211 }
212 
TestPrimitiveClassRoots(const ClassLinkerExtension & ext)213 static void TestPrimitiveClassRoots(const ClassLinkerExtension &ext)
214 {
215     TestPrimitiveClassRoot(ext, ClassRoot::U1, panda_file::Type::TypeId::U1);
216     TestPrimitiveClassRoot(ext, ClassRoot::I8, panda_file::Type::TypeId::I8);
217     TestPrimitiveClassRoot(ext, ClassRoot::U8, panda_file::Type::TypeId::U8);
218     TestPrimitiveClassRoot(ext, ClassRoot::I16, panda_file::Type::TypeId::I16);
219     TestPrimitiveClassRoot(ext, ClassRoot::U16, panda_file::Type::TypeId::U16);
220     TestPrimitiveClassRoot(ext, ClassRoot::I32, panda_file::Type::TypeId::I32);
221     TestPrimitiveClassRoot(ext, ClassRoot::U32, panda_file::Type::TypeId::U32);
222     TestPrimitiveClassRoot(ext, ClassRoot::I64, panda_file::Type::TypeId::I64);
223     TestPrimitiveClassRoot(ext, ClassRoot::U64, panda_file::Type::TypeId::U64);
224     TestPrimitiveClassRoot(ext, ClassRoot::F32, panda_file::Type::TypeId::F32);
225     TestPrimitiveClassRoot(ext, ClassRoot::F64, panda_file::Type::TypeId::F64);
226 }
227 
GetComponentSize(ClassRoot componentRoot)228 static size_t GetComponentSize(ClassRoot componentRoot)
229 {
230     switch (componentRoot) {
231         case ClassRoot::U1:
232         case ClassRoot::I8:
233         case ClassRoot::U8:
234             return sizeof(uint8_t);
235         case ClassRoot::I16:
236         case ClassRoot::U16:
237             return sizeof(uint16_t);
238         case ClassRoot::I32:
239         case ClassRoot::U32:
240         case ClassRoot::F32:
241             return sizeof(uint32_t);
242         case ClassRoot::I64:
243         case ClassRoot::U64:
244         case ClassRoot::F64:
245             return sizeof(uint64_t);
246         default:
247             UNREACHABLE();
248     }
249 }
250 
TestArrayClassRoot(const ClassLinkerExtension & classLinkerExt,ClassRoot classRoot,ClassRoot componentRoot)251 static void TestArrayClassRoot(const ClassLinkerExtension &classLinkerExt, ClassRoot classRoot, ClassRoot componentRoot)
252 {
253     std::string msg = "Test with class root ";
254     msg += std::to_string(static_cast<int>(classRoot));
255 
256     Class *klass = classLinkerExt.GetClassRoot(classRoot);
257     Class *componentClass = classLinkerExt.GetClassRoot(componentRoot);
258     ASSERT_NE(klass, nullptr) << msg;
259     EXPECT_EQ(klass->GetBase(), classLinkerExt.GetClassRoot(ClassRoot::OBJECT)) << msg;
260     EXPECT_EQ(klass->GetComponentType(), componentClass) << msg;
261     EXPECT_EQ(klass->GetComponentSize(), GetComponentSize(componentRoot)) << msg;
262     EXPECT_EQ(klass->GetFlags(), 0U) << msg;
263     EXPECT_EQ(klass->GetAccessFlags(), ACC_PUBLIC | ACC_FINAL | ACC_ABSTRACT) << msg;
264     EXPECT_EQ(klass->GetType().GetId(), panda_file::Type::TypeId::REFERENCE) << msg;
265     EXPECT_EQ(klass->IsObjectArrayClass(), !componentClass->IsPrimitive()) << msg;
266     EXPECT_TRUE(klass->IsArrayClass()) << msg;
267     EXPECT_FALSE(klass->IsStringClass()) << msg;
268     EXPECT_FALSE(klass->IsPrimitive()) << msg;
269     EXPECT_TRUE(klass->IsAbstract()) << msg;
270     EXPECT_TRUE(klass->IsClass()) << msg;
271     EXPECT_FALSE(klass->IsInterface()) << msg;
272     EXPECT_TRUE(klass->IsInstantiable()) << msg;
273 }
274 
TestArrayClassRoots(const ClassLinkerExtension & ext)275 static void TestArrayClassRoots(const ClassLinkerExtension &ext)
276 {
277     TestArrayClassRoot(ext, ClassRoot::ARRAY_U1, ClassRoot::U1);
278     TestArrayClassRoot(ext, ClassRoot::ARRAY_I8, ClassRoot::I8);
279     TestArrayClassRoot(ext, ClassRoot::ARRAY_U8, ClassRoot::U8);
280     TestArrayClassRoot(ext, ClassRoot::ARRAY_I16, ClassRoot::I16);
281     TestArrayClassRoot(ext, ClassRoot::ARRAY_U16, ClassRoot::U16);
282     TestArrayClassRoot(ext, ClassRoot::ARRAY_I32, ClassRoot::I32);
283     TestArrayClassRoot(ext, ClassRoot::ARRAY_U32, ClassRoot::U32);
284     TestArrayClassRoot(ext, ClassRoot::ARRAY_I64, ClassRoot::I64);
285     TestArrayClassRoot(ext, ClassRoot::ARRAY_U64, ClassRoot::U64);
286     TestArrayClassRoot(ext, ClassRoot::ARRAY_F32, ClassRoot::F32);
287     TestArrayClassRoot(ext, ClassRoot::ARRAY_F64, ClassRoot::F64);
288 }
289 
TEST_F(ClassLinkerTest,TestClassRoots)290 TEST_F(ClassLinkerTest, TestClassRoots)
291 {
292     auto classLinker = CreateClassLinker(thread_);
293     ASSERT_NE(classLinker, nullptr);
294 
295     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
296     auto *ext = classLinker->GetExtension(ctx);
297 
298     Class *classClass = ext->GetClassRoot(ClassRoot::CLASS);
299     ASSERT_NE(classClass, nullptr);
300     EXPECT_EQ(classClass->GetBase(), ext->GetClassRoot(ClassRoot::OBJECT));
301     EXPECT_EQ(classClass->GetComponentSize(), 0U);
302     EXPECT_EQ(classClass->GetFlags(), 0U);
303     EXPECT_EQ(classClass->GetType().GetId(), panda_file::Type::TypeId::REFERENCE);
304     EXPECT_TRUE(classClass->IsClassClass());
305     EXPECT_FALSE(classClass->IsObjectClass());
306     EXPECT_FALSE(classClass->IsArrayClass());
307     EXPECT_FALSE(classClass->IsObjectArrayClass());
308     EXPECT_FALSE(classClass->IsStringClass());
309     EXPECT_FALSE(classClass->IsPrimitive());
310     EXPECT_TRUE(classClass->IsClass());
311     EXPECT_FALSE(classClass->IsInterface());
312 
313     Class *objectClass = ext->GetClassRoot(ClassRoot::OBJECT);
314     ASSERT_NE(objectClass, nullptr);
315     EXPECT_EQ(objectClass->GetBase(), nullptr);
316     EXPECT_EQ(objectClass->GetComponentSize(), 0U);
317     EXPECT_EQ(objectClass->GetFlags(), 0U);
318     EXPECT_EQ(objectClass->GetType().GetId(), panda_file::Type::TypeId::REFERENCE);
319     EXPECT_TRUE(objectClass->IsObjectClass());
320     EXPECT_FALSE(objectClass->IsArrayClass());
321     EXPECT_FALSE(objectClass->IsObjectArrayClass());
322     EXPECT_FALSE(objectClass->IsStringClass());
323     EXPECT_FALSE(objectClass->IsPrimitive());
324     EXPECT_TRUE(objectClass->IsClass());
325     EXPECT_FALSE(objectClass->IsInterface());
326 
327     Class *stringClass = ext->GetClassRoot(ClassRoot::STRING);
328     ASSERT_NE(stringClass, nullptr);
329     EXPECT_EQ(stringClass->GetBase(), objectClass);
330     EXPECT_EQ(stringClass->GetComponentSize(), 0U);
331     EXPECT_EQ(stringClass->GetFlags(), Class::STRING_CLASS);
332     EXPECT_EQ(stringClass->GetType().GetId(), panda_file::Type::TypeId::REFERENCE);
333     EXPECT_FALSE(stringClass->IsObjectClass());
334     EXPECT_FALSE(stringClass->IsArrayClass());
335     EXPECT_FALSE(stringClass->IsObjectArrayClass());
336     EXPECT_TRUE(stringClass->IsStringClass());
337     EXPECT_FALSE(stringClass->IsPrimitive());
338     EXPECT_TRUE(stringClass->IsClass());
339     EXPECT_FALSE(stringClass->IsInterface());
340 
341     TestPrimitiveClassRoots(*ext);
342     TestArrayClassRoots(*ext);
343 }
344 
345 struct FieldData {
346     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
347     std::string name;
348     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
349     size_t size;
350     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
351     size_t offset;
352 
operator ==ark::test::FieldData353     bool operator==(const FieldData &other) const
354     {
355         return name == other.name && size == other.size && offset == other.offset;
356     }
357 
operator <<(std::ostream & os,const FieldData & fieldData)358     friend std::ostream &operator<<(std::ostream &os, const FieldData &fieldData)
359     {
360         return os << "{ name: \"" << fieldData.name << "\", size: " << fieldData.size
361                   << ", offset: " << fieldData.offset << " }";
362     }
363 };
364 
365 struct FieldDataHash {
operator ()ark::test::FieldDataHash366     size_t operator()(const FieldData &fieldData) const
367     {
368         return std::hash<std::string>()(fieldData.name);
369     }
370 };
371 
GetSize(const Field & field)372 size_t GetSize(const Field &field)
373 {
374     size_t size = 0;
375 
376     switch (field.GetTypeId()) {
377         case panda_file::Type::TypeId::U1:
378         case panda_file::Type::TypeId::I8:
379         case panda_file::Type::TypeId::U8: {
380             size = 1;
381             break;
382         }
383         case panda_file::Type::TypeId::I16:
384         case panda_file::Type::TypeId::U16: {
385             size = 2L;
386             break;
387         }
388         case panda_file::Type::TypeId::I32:
389         case panda_file::Type::TypeId::U32:
390         case panda_file::Type::TypeId::F32: {
391             size = 4L;
392             break;
393         }
394         case panda_file::Type::TypeId::I64:
395         case panda_file::Type::TypeId::U64:
396         case panda_file::Type::TypeId::F64: {
397             size = 8L;
398             break;
399         }
400         case panda_file::Type::TypeId::REFERENCE: {
401             size = ClassHelper::OBJECT_POINTER_SIZE;
402             break;
403         }
404         case panda_file::Type::TypeId::TAGGED: {
405             size = coretypes::TaggedValue::TaggedTypeSize();
406             break;
407         }
408         default: {
409             UNREACHABLE();
410             break;
411         }
412     }
413 
414     return size;
415 }
416 
UpdateOffsets(std::vector<FieldData> * fields,size_t offset)417 void UpdateOffsets(std::vector<FieldData> *fields, size_t offset)
418 {
419     for (auto &field : *fields) {
420         offset = AlignUp(offset, field.size);
421         field.offset = offset;
422         offset += field.size;
423     }
424 }
425 
GetFieldLayoutSortedSfields()426 static std::vector<FieldData> GetFieldLayoutSortedSfields()
427 {
428     return {{"sf_ref", ClassHelper::OBJECT_POINTER_SIZE, 0},
429             {"sf_any", coretypes::TaggedValue::TaggedTypeSize(), 0},
430             {"sf_f64", sizeof(double), 0},
431             {"sf_i64", sizeof(int64_t), 0},
432             {"sf_u64", sizeof(uint64_t), 0},
433             {"sf_i32", sizeof(int32_t), 0},
434             {"sf_u32", sizeof(uint32_t), 0},
435             {"sf_f32", sizeof(float), 0},
436             {"sf_i16", sizeof(int16_t), 0},
437             {"sf_u16", sizeof(uint16_t), 0},
438             {"sf_u1", sizeof(uint8_t), 0},
439             {"sf_i8", sizeof(int8_t), 0},
440             {"sf_u8", sizeof(uint8_t), 0}};
441 }
442 
GetFieldLayoutSortedIfields()443 static std::vector<FieldData> GetFieldLayoutSortedIfields()
444 {
445     return {{"if_ref", ClassHelper::OBJECT_POINTER_SIZE, 0},
446             {"if_any", coretypes::TaggedValue::TaggedTypeSize(), 0},
447             {"if_f64", sizeof(double), 0},
448             {"if_i64", sizeof(int64_t), 0},
449             {"if_u64", sizeof(uint64_t), 0},
450             {"if_i32", sizeof(int32_t), 0},
451             {"if_u32", sizeof(uint32_t), 0},
452             {"if_f32", sizeof(float), 0},
453             {"if_i16", sizeof(int16_t), 0},
454             {"if_u16", sizeof(uint16_t), 0},
455             {"if_u1", sizeof(uint8_t), 0},
456             {"if_i8", sizeof(int8_t), 0},
457             {"if_u8", sizeof(uint8_t), 0}};
458 }
459 
GetFieldLayoutSource()460 static std::string GetFieldLayoutSource()
461 {
462     return R"(
463         .record R1 {}
464 
465         .record R2 {
466             # static fields
467 
468             u1  sf_u1  <static>
469             i16 sf_i16 <static>
470             i8  sf_i8  <static>
471             i32 sf_i32 <static>
472             u8  sf_u8  <static>
473             f64 sf_f64 <static>
474             u32 sf_u32 <static>
475             u16 sf_u16 <static>
476             i64 sf_i64 <static>
477             f32 sf_f32 <static>
478             u64 sf_u64 <static>
479             R1  sf_ref <static>
480             any sf_any <static>
481 
482             # instance fields
483 
484             i16 if_i16
485             u1  if_u1
486             i8  if_i8
487             f64 if_f64
488             i32 if_i32
489             u8  if_u8
490             u32 if_u32
491             u16 if_u16
492             f32 if_f32
493             i64 if_i64
494             u64 if_u64
495             R2  if_ref
496             any if_any
497         }
498     )";
499 }
500 
TEST_F(ClassLinkerTest,FieldLayout)501 TEST_F(ClassLinkerTest, FieldLayout)
502 {
503     pandasm::Parser p;
504 
505     auto source = GetFieldLayoutSource();
506 
507     auto res = p.Parse(source);
508     auto pf = pandasm::AsmEmitter::Emit(res.Value());
509 
510     auto classLinker = CreateClassLinker(thread_);
511     ASSERT_NE(classLinker, nullptr);
512 
513     classLinker->AddPandaFile(std::move(pf));
514 
515     PandaString descriptor;
516     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
517     Class *klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R2"), &descriptor));
518     ASSERT_NE(klass, nullptr);
519 
520     std::vector<FieldData> sortedSfields = GetFieldLayoutSortedSfields();
521 
522     std::vector<FieldData> sortedIfields = GetFieldLayoutSortedIfields();
523 
524     size_t offset = klass->GetStaticFieldsOffset();
525     if (!IsAligned<sizeof(double)>(offset + ClassHelper::OBJECT_POINTER_SIZE)) {
526         FieldData data {"sf_i32", sizeof(int32_t), 0};
527         // NOLINTNEXTLINE(bugprone-inaccurate-erase)
528         sortedSfields.erase(std::remove(sortedSfields.begin(), sortedSfields.end(), data));
529         sortedSfields.insert(sortedSfields.cbegin() + 1, data);
530     }
531 
532     UpdateOffsets(&sortedSfields, offset);
533 
534     offset = ObjectHeader::ObjectHeaderSize();
535     if (!IsAligned<sizeof(double)>(offset + ClassHelper::OBJECT_POINTER_SIZE)) {
536         FieldData data {"if_i32", sizeof(int32_t), 0};
537         // NOLINTNEXTLINE(bugprone-inaccurate-erase)
538         sortedIfields.erase(std::remove(sortedIfields.begin(), sortedIfields.end(), data));
539         sortedIfields.insert(sortedIfields.cbegin() + 1, data);
540     }
541 
542     UpdateOffsets(&sortedIfields, offset);
543 
544     auto fieldCmp = [](const FieldData &f1, const FieldData &f2) { return f1.offset < f2.offset; };
545 
546     std::vector<FieldData> sfields;
547     for (const auto &field : klass->GetStaticFields()) {
548         sfields.push_back({utf::Mutf8AsCString(field.GetName().data), GetSize(field), field.GetOffset()});
549     }
550     std::sort(sfields.begin(), sfields.end(), fieldCmp);
551     EXPECT_EQ(sfields, sortedSfields);
552 
553     std::unordered_set<FieldData, FieldDataHash> ifields;
554 
555     for (const auto &field : klass->GetInstanceFields()) {
556         ifields.insert({utf::Mutf8AsCString(field.GetName().data), GetSize(field), field.GetOffset()});
557     }
558 
559     std::unordered_set<FieldData, FieldDataHash> sortedIfieldsSet(sortedIfields.cbegin(), sortedIfields.cend());
560     EXPECT_EQ(ifields, sortedIfieldsSet);
561 }
562 
TEST_F(ClassLinkerTest,ResolveExternalClass)563 TEST_F(ClassLinkerTest, ResolveExternalClass)
564 {
565     uint32_t offset;
566 
567     auto classLinker = CreateClassLinker(thread_);
568     ASSERT_NE(classLinker, nullptr);
569 
570     {
571         pandasm::Parser p;
572 
573         auto source = R"(
574             .record Ext.R <external>
575 
576             .function void main() {
577                 newarr v0, v0, Ext.R[]
578                 return.void
579             }
580         )";
581 
582         auto res = p.Parse(source);
583         ASSERT_TRUE(res);
584         auto pf = pandasm::AsmEmitter::Emit(res.Value());
585 
586         // 0 - "LExt/R;"
587         // 1 - "L_GLOBAL;"
588         // 2 - "[LExt/R;"
589         offset = pf->GetClasses()[2];
590 
591         classLinker->AddPandaFile(std::move(pf));
592     }
593 
594     PandaString descriptor;
595 
596     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
597     auto *klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
598     ASSERT_NE(klass, nullptr);
599 
600     auto *method = klass->GetDirectMethod(utf::CStringAsMutf8("main"));
601     ASSERT_NE(method, nullptr);
602 
603     auto *externalClass = classLinker->GetClass(*method, panda_file::File::EntityId(offset));
604     ASSERT_EQ(externalClass, nullptr);
605 
606     {
607         pandasm::Parser p;
608 
609         auto extSource = R"(
610             .record Ext {}
611             .record Ext.R {}
612         )";
613 
614         auto res = p.Parse(extSource);
615         auto extPf = pandasm::AsmEmitter::Emit(res.Value());
616 
617         classLinker->AddPandaFile(std::move(extPf));
618     }
619 
620     externalClass = classLinker->GetClass(*method, panda_file::File::EntityId(offset));
621     ASSERT_NE(externalClass, nullptr);
622 
623     EXPECT_STREQ(utf::Mutf8AsCString(externalClass->GetDescriptor()),
624                  utf::Mutf8AsCString(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("Ext.R"), 1, &descriptor)));
625 }
626 
TEST_F(ClassLinkerTest,ArrayClass)627 TEST_F(ClassLinkerTest, ArrayClass)
628 {
629     pandasm::Parser p;
630 
631     auto source = R"(
632         .record R {}
633     )";
634 
635     auto res = p.Parse(source);
636     auto pf = pandasm::AsmEmitter::Emit(res.Value());
637 
638     auto classLinker = CreateClassLinker(thread_);
639     ASSERT_NE(classLinker, nullptr);
640 
641     classLinker->AddPandaFile(std::move(pf));
642 
643     PandaString descriptor;
644 
645     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
646     auto *klass = ext->GetClass(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("UnknownClass"), 1, &descriptor));
647     ASSERT_EQ(klass, nullptr);
648 
649     // NOLINTNEXTLINE(readability-magic-numbers)
650     for (size_t i = 0; i < 256U; i++) {
651         auto *cls = ext->GetClass(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("R"), i, &descriptor));
652         ASSERT_NE(cls, nullptr);
653         EXPECT_EQ(utf::Mutf8AsCString(cls->GetDescriptor()), descriptor);
654     }
655 }
656 
GetMethod(ClassLinker * classLinker,const char * className,const char * methodName,const ark::PandaString & signature)657 static Method *GetMethod(ClassLinker *classLinker, const char *className, const char *methodName,
658                          const ark::PandaString &signature)
659 {
660     PandaString descriptor;
661     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
662     auto *klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8(className), &descriptor));
663     auto *method = klass->GetDirectMethod(utf::CStringAsMutf8(methodName));
664     if (signature != method->GetProto().GetSignature()) {
665         return nullptr;
666     }
667     return method;
668 }
669 
GetMethodsSet(Span<Method> methods)670 static std::unordered_set<Method *> GetMethodsSet(Span<Method> methods)
671 {
672     std::unordered_set<Method *> set;
673     for (auto &method : methods) {
674         set.insert(&method);
675     }
676 
677     return set;
678 }
679 
TEST_F(ClassLinkerTest,VTable)680 TEST_F(ClassLinkerTest, VTable)
681 {
682     {
683         pandasm::Parser p;
684 
685         auto source = R"(
686             .record A {}
687 
688             .function void A.f1() {}
689             .function void A.f2(i32 a0) {}
690 
691             .function void A.f3(A a0) {}
692             .function void A.f4(A a0, i32 a1) {}
693         )";
694 
695         auto res = p.Parse(source);
696         auto pf = pandasm::AsmEmitter::Emit(res.Value());
697 
698         auto classLinker = CreateClassLinker(thread_);
699         ASSERT_NE(classLinker, nullptr);
700 
701         classLinker->AddPandaFile(std::move(pf));
702 
703         PandaString descriptor;
704 
705         auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
706         auto *classA = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
707         ASSERT_NE(classA, nullptr);
708 
709         auto smethods = classA->GetStaticMethods();
710         ASSERT_EQ(smethods.size(), 2U);
711 
712         auto vmethods = classA->GetVirtualMethods();
713         ASSERT_EQ(vmethods.size(), 2U);
714 
715         {
716             auto set = GetMethodsSet(smethods);
717             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f1", "()V")), set.cend());
718             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f2", "(I)V")), set.cend());
719         }
720 
721         {
722             auto set = GetMethodsSet(vmethods);
723             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f3", "()V")), set.cend());
724             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f4", "(I)V")), set.cend());
725         }
726 
727         {
728             auto vtable = classA->GetVTable();
729             ASSERT_EQ(vtable.size(), vmethods.size());
730 
731             for (const auto &vmethod : vmethods) {
732                 ASSERT_EQ(vtable[vmethod.GetVTableIndex()], &vmethod);
733             }
734         }
735     }
736 }
737 
TEST_F(ClassLinkerTest,VTableInheritance)738 TEST_F(ClassLinkerTest, VTableInheritance)
739 {
740     {
741         pandasm::Parser p;
742 
743         auto source = R"(
744             .record A {}
745             .record B <extends=A> {}
746             .record C {}
747 
748             .function A A.f1(A a0, i32 a1) {}
749             .function A A.f2(A a0, C a1) {}
750 
751             .function B B.f1(B a0, i64 a1) {}
752             .function B B.f2(B a0, C a1) {}
753         )";
754 
755         auto res = p.Parse(source);
756         auto pf = pandasm::AsmEmitter::Emit(res.Value());
757 
758         auto classLinker = CreateClassLinker(thread_);
759         ASSERT_NE(classLinker, nullptr);
760 
761         classLinker->AddPandaFile(std::move(pf));
762 
763         PandaString descriptor;
764 
765         auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
766         auto *classB = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
767         ASSERT_NE(classB, nullptr);
768 
769         auto *classA = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
770         ASSERT_NE(classA, nullptr);
771 
772         auto vtableB = classB->GetVTable();
773         ASSERT_EQ(vtableB.size(), 4U);
774 
775         auto vtableA = classA->GetVTable();
776         ASSERT_EQ(vtableA.size(), 2U);
777 
778         {
779             auto set = std::unordered_set<Method *> {};
780             for (const auto &m : vtableA) {
781                 set.insert(m);
782             }
783             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f1", "(I)LA;")), set.cend());
784             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f2", "(LC;)LA;")), set.cend());
785         }
786 
787         {
788             auto set = std::unordered_set<Method *> {};
789             for (const auto &m : vtableB) {
790                 set.insert(m);
791             }
792             ASSERT_NE(set.find(GetMethod(classLinker.get(), "A", "f1", "(I)LA;")), set.cend());
793             ASSERT_NE(set.find(GetMethod(classLinker.get(), "B", "f2", "(LC;)LB;")), set.cend());
794             ASSERT_NE(set.find(GetMethod(classLinker.get(), "B", "f1", "(J)LB;")), set.cend());
795         }
796     }
797 }
798 
TEST_F(ClassLinkerTest,PrimitiveClasses)799 TEST_F(ClassLinkerTest, PrimitiveClasses)
800 {
801     auto classLinker = CreateClassLinker(thread_);
802     ASSERT_NE(classLinker, nullptr);
803 
804     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
805     auto *ext = classLinker->GetExtension(ctx);
806 
807     PandaString descriptor;
808 
809     auto type = panda_file::Type(panda_file::Type::TypeId::I32);
810 
811     auto *primitiveClass = ext->GetClass(ClassHelper::GetPrimitiveDescriptor(type, &descriptor));
812     ASSERT_NE(primitiveClass, nullptr);
813     EXPECT_STREQ(utf::Mutf8AsCString(primitiveClass->GetDescriptor()),
814                  utf::Mutf8AsCString(ClassHelper::GetPrimitiveDescriptor(type, &descriptor)));
815 
816     auto *primitiveArrayClass1 = ext->GetClass(ClassHelper::GetPrimitiveArrayDescriptor(type, 1, &descriptor));
817     ASSERT_NE(primitiveArrayClass1, nullptr);
818     EXPECT_STREQ(utf::Mutf8AsCString(primitiveArrayClass1->GetDescriptor()),
819                  utf::Mutf8AsCString(ClassHelper::GetPrimitiveArrayDescriptor(type, 1, &descriptor)));
820 
821     auto *primitiveArrayClass2 = ext->GetClass(ClassHelper::GetPrimitiveArrayDescriptor(type, 2, &descriptor));
822     ASSERT_NE(primitiveArrayClass2, nullptr);
823     EXPECT_STREQ(utf::Mutf8AsCString(primitiveArrayClass2->GetDescriptor()),
824                  utf::Mutf8AsCString(ClassHelper::GetPrimitiveArrayDescriptor(type, 2L, &descriptor)));
825 }
826 
827 class TestClassLinkerContext : public ClassLinkerContext {
828 public:
TestClassLinkerContext(const uint8_t * descriptor,bool needCopyDescriptor,Class * klass,panda_file::SourceLang lang)829     TestClassLinkerContext(const uint8_t *descriptor, bool needCopyDescriptor, Class *klass,
830                            panda_file::SourceLang lang)
831         : ClassLinkerContext(lang), descriptor_(descriptor), needCopyDescriptor_(needCopyDescriptor), klass_(klass)
832     {
833     }
834 
LoadClass(const uint8_t * descriptor,bool needCopyDescriptor,ClassLinkerErrorHandler * errorHandler)835     Class *LoadClass(const uint8_t *descriptor, bool needCopyDescriptor,
836                      [[maybe_unused]] ClassLinkerErrorHandler *errorHandler) override
837     {
838         isSuccess_ = utf::IsEqual(descriptor, descriptor_) && needCopyDescriptor == needCopyDescriptor_;
839         InsertClass(klass_);
840         return klass_;
841     }
842 
IsSuccess() const843     bool IsSuccess() const
844     {
845         return isSuccess_;
846     }
847 
848 private:
849     const uint8_t *descriptor_;
850     bool needCopyDescriptor_ {};
851     Class *klass_;
852     bool isSuccess_ {false};
853 };
854 
855 struct LoadContextTestStruct {
856     Class *classA;
857     Class *classB;
858     Class *classArrayB;
859 };
860 
CheckLoadContext(TestClassLinkerContext & ctx,ClassLinker * classLinker,LoadContextTestStruct & loadContextStruct)861 static void CheckLoadContext(TestClassLinkerContext &ctx, ClassLinker *classLinker,
862                              LoadContextTestStruct &loadContextStruct)
863 {
864     auto *classA = loadContextStruct.classA;
865     auto *classB = loadContextStruct.classB;
866     auto *classArrayB = loadContextStruct.classArrayB;
867     {
868         PandaUnorderedSet<Class *> expected {classB};
869         PandaUnorderedSet<Class *> classes;
870         ctx.EnumerateClasses([&](Class *klass) {
871             classes.insert(klass);
872             return true;
873         });
874 
875         ASSERT_EQ(classes, expected);
876     }
877 
878     {
879         PandaUnorderedSet<Class *> classes;
880         classLinker->EnumerateClasses([&](Class *klass) {
881             classes.insert(klass);
882             return true;
883         });
884 
885         ASSERT_NE(classes.find(classA), classes.cend());
886         ASSERT_EQ(*classes.find(classA), classA);
887 
888         ASSERT_NE(classes.find(classB), classes.cend());
889         ASSERT_EQ(*classes.find(classB), classB);
890 
891         ASSERT_NE(classes.find(classArrayB), classes.cend());
892         ASSERT_EQ(*classes.find(classArrayB), classArrayB);
893     }
894 }
895 
TEST_F(ClassLinkerTest,LoadContext)896 TEST_F(ClassLinkerTest, LoadContext)
897 {
898     pandasm::Parser p;
899 
900     auto source = R"(
901         .record A {}
902         .record B {}
903     )";
904 
905     auto res = p.Parse(source);
906     auto pf = pandasm::AsmEmitter::Emit(res.Value());
907 
908     auto classLinker = CreateClassLinker(thread_);
909     ASSERT_NE(classLinker, nullptr);
910 
911     classLinker->AddPandaFile(std::move(pf));
912 
913     PandaString descriptor;
914     auto *ext = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY);
915     auto *classA = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
916 
917     ASSERT_NE(classA, nullptr);
918     ASSERT_EQ(classA->GetLoadContext()->IsBootContext(), true);
919 
920     auto *classB = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
921 
922     ASSERT_NE(classB, nullptr);
923     ASSERT_EQ(classB->GetLoadContext()->IsBootContext(), true);
924 
925     auto *desc = ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor);
926     TestClassLinkerContext ctx(desc, true, classB, panda_file::SourceLang::PANDA_ASSEMBLY);
927     auto *classBCtx = ext->GetClass(desc, true, &ctx);
928 
929     ASSERT_TRUE(ctx.IsSuccess());
930     ASSERT_EQ(classBCtx, classB);
931 
932     bool isMatched = false;
933     ctx.EnumerateClasses([&isMatched](Class *klass) {
934         isMatched = klass->GetName() == "B";
935         return true;
936     });
937 
938     ASSERT_TRUE(isMatched);
939 
940     auto *classArrayB =
941         classLinker->GetClass(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("B"), 1, &descriptor), true, &ctx);
942 
943     ASSERT_NE(classArrayB, nullptr);
944     ASSERT_EQ(classArrayB->GetLoadContext(), ext->GetBootContext());
945 
946     LoadContextTestStruct loadContextStruct {classA, classB, classArrayB};
947 
948     CheckLoadContext(ctx, classLinker.get(), loadContextStruct);
949 }
950 
CheckAccesses(ClassLinkerExtension * ext)951 static void CheckAccesses(ClassLinkerExtension *ext)
952 {
953     // Global
954     {
955         PandaString descriptor;
956         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("_GLOBAL"), &descriptor));
957         ASSERT_NE(klass, nullptr);
958         auto f = klass->GetClassMethod(utf::CStringAsMutf8("f"));
959         ASSERT_NE(f, nullptr);
960         ASSERT_TRUE(f->IsPublic());
961     }
962 
963     // record A
964     {
965         PandaString descriptor;
966         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
967         ASSERT_NE(klass, nullptr);
968         ASSERT_TRUE(klass->IsPrivate());
969         auto f = klass->GetClassMethod(utf::CStringAsMutf8("f"));
970         ASSERT_NE(f, nullptr);
971         ASSERT_TRUE(f->IsProtected());
972     }
973 
974     // record B
975     {
976         PandaString descriptor;
977         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
978         ASSERT_NE(klass, nullptr);
979         ASSERT_TRUE(klass->IsPublic());
980         auto f = klass->GetClassMethod(utf::CStringAsMutf8("f"));
981         ASSERT_NE(f, nullptr);
982         ASSERT_TRUE(f->IsPrivate());
983 
984         auto i = 0;
985         auto accessPredicates = std::array {&ark::Field::IsPublic, &ark::Field::IsProtected, &ark::Field::IsPrivate};
986         for (const auto &field : klass->GetFields()) {
987             ASSERT_TRUE((field.*accessPredicates[i])());
988             i++;
989         }
990         ASSERT_EQ(i, klass->GetFields().size());
991     }
992 
993     // record C
994     {
995         PandaString descriptor;
996         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("C"), &descriptor));
997         ASSERT_NE(klass, nullptr);
998         ASSERT_TRUE(klass->IsProtected());
999     }
1000 }
1001 
TEST_F(ClassLinkerTest,Accesses)1002 TEST_F(ClassLinkerTest, Accesses)
1003 {
1004     auto source = R"(
1005         .record A <access.record=private> {}
1006 
1007         .record C <access.record=protected> {}
1008 
1009         .record B <access.record=public> {
1010             i32 pub <access.field=public>
1011             i32 prt <access.field=protected>
1012             i32 prv <access.field=private>
1013         }
1014 
1015         .function void f() <access.function=public> {}
1016         .function void A.f() <access.function=protected> {}
1017         .function void B.f() <access.function=private> {}
1018     )";
1019 
1020     auto res = pandasm::Parser {}.Parse(source);
1021     auto pf = pandasm::AsmEmitter::Emit(res.Value());
1022 
1023     auto classLinker = CreateClassLinker(thread_);
1024     ASSERT_NE(classLinker, nullptr);
1025 
1026     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
1027     auto *ext = classLinker->GetExtension(ctx);
1028 
1029     classLinker->AddPandaFile(std::move(pf));
1030 
1031     CheckAccesses(ext);
1032 }
1033 
TEST_F(ClassLinkerTest,Inheritance)1034 TEST_F(ClassLinkerTest, Inheritance)
1035 {
1036     auto source = R"(
1037         .record A {}
1038         .record B <extends=A> {}
1039     )";
1040 
1041     auto res = pandasm::Parser {}.Parse(source);
1042     auto pf = pandasm::AsmEmitter::Emit(res.Value());
1043 
1044     auto classLinker = CreateClassLinker(thread_);
1045     ASSERT_NE(classLinker, nullptr);
1046 
1047     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
1048     auto *ext = classLinker->GetExtension(ctx);
1049 
1050     classLinker->AddPandaFile(std::move(pf));
1051 
1052     PandaString descriptor;
1053 
1054     auto classA = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
1055     ASSERT_NE(classA, nullptr);
1056 
1057     auto classB = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
1058     ASSERT_NE(classB, nullptr);
1059 
1060     ASSERT_EQ(classA->GetBase(), ext->GetClassRoot(ClassRoot::OBJECT));
1061     ASSERT_EQ(classB->GetBase(), classA);
1062 
1063     ASSERT_TRUE(classB->IsSubClassOf(classA));
1064 }
1065 
TEST_F(ClassLinkerTest,IsSubClassOf)1066 TEST_F(ClassLinkerTest, IsSubClassOf)
1067 {
1068     auto source = R"(
1069         .record A {}
1070         .record B <extends=A> {}
1071         .record C <extends=B> {}
1072     )";
1073 
1074     auto res = pandasm::Parser {}.Parse(source);
1075     auto pf = pandasm::AsmEmitter::Emit(res.Value());
1076 
1077     auto classLinker = CreateClassLinker(thread_);
1078     ASSERT_NE(classLinker, nullptr);
1079 
1080     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
1081     auto *ext = classLinker->GetExtension(ctx);
1082 
1083     classLinker->AddPandaFile(std::move(pf));
1084 
1085     PandaString descriptor;
1086 
1087     auto classA = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
1088     ASSERT_NE(classA, nullptr);
1089 
1090     auto classB = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
1091     ASSERT_NE(classB, nullptr);
1092 
1093     auto classC = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("C"), &descriptor));
1094     ASSERT_NE(classC, nullptr);
1095 
1096     ASSERT_TRUE(classA->IsSubClassOf(classA));
1097     ASSERT_FALSE(classA->IsSubClassOf(classB));
1098     ASSERT_FALSE(classA->IsSubClassOf(classC));
1099 
1100     ASSERT_TRUE(classB->IsSubClassOf(classA));
1101     ASSERT_TRUE(classB->IsSubClassOf(classB));
1102     ASSERT_FALSE(classB->IsSubClassOf(classC));
1103 
1104     ASSERT_TRUE(classC->IsSubClassOf(classA));
1105     ASSERT_TRUE(classC->IsSubClassOf(classB));
1106     ASSERT_TRUE(classC->IsSubClassOf(classC));
1107 }
1108 
TEST_F(ClassLinkerTest,Final)1109 TEST_F(ClassLinkerTest, Final)
1110 {
1111     auto source = R"(
1112         .record A <final> {}
1113 
1114         .record B {
1115             i32 fld <final>
1116         }
1117 
1118         .function void B.f(B a0) <final> {}
1119     )";
1120 
1121     auto res = pandasm::Parser {}.Parse(source);
1122     auto pf = pandasm::AsmEmitter::Emit(res.Value());
1123 
1124     auto classLinker = CreateClassLinker(thread_);
1125     ASSERT_NE(classLinker, nullptr);
1126 
1127     LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
1128     auto *ext = classLinker->GetExtension(ctx);
1129 
1130     classLinker->AddPandaFile(std::move(pf));
1131 
1132     // record A
1133     {
1134         PandaString descriptor;
1135         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor));
1136         ASSERT_NE(klass, nullptr);
1137         ASSERT_TRUE(klass->IsFinal());
1138     }
1139 
1140     // record B
1141     {
1142         PandaString descriptor;
1143         auto klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("B"), &descriptor));
1144         ASSERT_NE(klass, nullptr);
1145 
1146         auto f = klass->GetClassMethod(utf::CStringAsMutf8("f"));
1147         ASSERT_NE(f, nullptr);
1148         ASSERT_TRUE(f->IsFinal());
1149 
1150         ASSERT_EQ(klass->GetFields().size(), 1);
1151         for (const auto &field : klass->GetFields()) {
1152             ASSERT_TRUE(field.IsFinal());
1153         }
1154     }
1155 }
1156 
1157 }  // namespace ark::test
1158