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