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