1 /**
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <gtest/gtest.h>
17 #include <cstdint>
18
19 #include "types/ets_object.h"
20
21 #include "ets_vm.h"
22 #include "ets_coroutine.h"
23 #include "ets_class_linker_extension.h"
24 #include "assembly-emitter.h"
25 #include "assembly-parser.h"
26
27 // NOLINTBEGIN(readability-magic-numbers)
28
29 namespace ark::ets::test {
30
31 class EtsObjectTest : public testing::Test {
32 public:
EtsObjectTest()33 EtsObjectTest()
34 {
35 options_.SetShouldLoadBootPandaFiles(true);
36 options_.SetShouldInitializeIntrinsics(false);
37 options_.SetCompilerEnableJit(false);
38 options_.SetGcType("epsilon");
39 options_.SetLoadRuntimes({"ets"});
40
41 auto stdlib = std::getenv("PANDA_STD_LIB");
42 if (stdlib == nullptr) {
43 std::cerr << "PANDA_STD_LIB env variable should be set and point to mock_stdlib.abc" << std::endl;
44 std::abort();
45 }
46 options_.SetBootPandaFiles({stdlib});
47
48 Runtime::Create(options_);
49
50 SetClassesPandasmSources();
51 }
52
~EtsObjectTest()53 ~EtsObjectTest() override
54 {
55 Runtime::Destroy();
56 }
57
58 NO_COPY_SEMANTIC(EtsObjectTest);
59 NO_MOVE_SEMANTIC(EtsObjectTest);
60
SetUp()61 void SetUp() override
62 {
63 coroutine_ = EtsCoroutine::GetCurrent();
64 coroutine_->ManagedCodeBegin();
65 }
66
TearDown()67 void TearDown() override
68 {
69 coroutine_->ManagedCodeEnd();
70 }
71
72 void SetClassesPandasmSources();
73 EtsClass *GetTestClass(std::string className);
74
75 private:
76 RuntimeOptions options_;
77 EtsCoroutine *coroutine_ = nullptr;
78 std::unordered_map<std::string, const char *> sources_;
79
80 protected:
GetSources()81 const std::unordered_map<std::string, const char *> &GetSources()
82 {
83 return sources_;
84 }
85 };
86
SetClassesPandasmSources()87 void EtsObjectTest::SetClassesPandasmSources()
88 {
89 const char *source = R"(
90 .language eTS
91 .record Rectangle {
92 i32 Width
93 f32 Height
94 i64 Color <static>
95 }
96 )";
97 sources_["Rectangle"] = source;
98
99 source = R"(
100 .language eTS
101 .record Triangle {
102 i32 firSide
103 i32 secSide
104 i32 thirdSide
105 i64 Color <static>
106 }
107 )";
108 sources_["Triangle"] = source;
109
110 source = R"(
111 .language eTS
112 .record Foo {
113 i32 member
114 }
115 .record Bar {
116 Foo foo1
117 Foo foo2
118 }
119 )";
120 sources_["Foo"] = source;
121 sources_["Bar"] = source;
122 }
123
GetTestClass(std::string className)124 EtsClass *EtsObjectTest::GetTestClass(std::string className)
125 {
126 std::unordered_map<std::string, const char *> sources = GetSources();
127 pandasm::Parser p;
128
129 auto res = p.Parse(sources[className]);
130 auto pf = pandasm::AsmEmitter::Emit(res.Value());
131
132 auto classLinker = Runtime::GetCurrent()->GetClassLinker();
133 classLinker->AddPandaFile(std::move(pf));
134
135 className.insert(0, 1, 'L');
136 className.push_back(';');
137
138 EtsClass *klass = coroutine_->GetPandaVM()->GetClassLinker()->GetClass(className.c_str());
139 ASSERT(klass);
140 return klass;
141 }
142
TEST_F(EtsObjectTest,GetClass)143 TEST_F(EtsObjectTest, GetClass)
144 {
145 EtsClass *klass = nullptr;
146 EtsObject *obj = nullptr;
147
148 for (const auto &it : GetSources()) {
149 klass = GetTestClass(it.first);
150 obj = EtsObject::Create(klass);
151 ASSERT_EQ(klass, obj->GetClass());
152 }
153 }
154
TEST_F(EtsObjectTest,SetClass)155 TEST_F(EtsObjectTest, SetClass)
156 {
157 EtsClass *klass1 = GetTestClass("Rectangle");
158 EtsClass *klass2 = GetTestClass("Triangle");
159 EtsObject *obj = EtsObject::Create(klass1);
160 ASSERT_EQ(obj->GetClass(), klass1);
161 obj->SetClass(klass2);
162 ASSERT_EQ(obj->GetClass(), klass2);
163 }
164
TEST_F(EtsObjectTest,IsInstanceOf)165 TEST_F(EtsObjectTest, IsInstanceOf)
166 {
167 EtsClass *klass1 = GetTestClass("Rectangle");
168 EtsClass *klass2 = GetTestClass("Triangle");
169 EtsObject *obj1 = EtsObject::Create(klass1);
170 EtsObject *obj2 = EtsObject::Create(klass2);
171
172 ASSERT_TRUE(obj1->IsInstanceOf(klass1));
173 ASSERT_TRUE(obj2->IsInstanceOf(klass2));
174 ASSERT_FALSE(obj1->IsInstanceOf(klass2));
175 ASSERT_FALSE(obj2->IsInstanceOf(klass1));
176 }
177
TEST_F(EtsObjectTest,GetAndSetFieldObject)178 TEST_F(EtsObjectTest, GetAndSetFieldObject)
179 {
180 EtsClass *barKlass = GetTestClass("Bar");
181 EtsClass *fooKlass = GetTestClass("Foo");
182
183 EtsObject *barObj = EtsObject::Create(barKlass);
184 EtsObject *fooObj1 = EtsObject::Create(fooKlass);
185 EtsObject *fooObj2 = EtsObject::Create(fooKlass);
186
187 EtsField *foo1Field = barKlass->GetFieldIDByName("foo1");
188 EtsField *foo2Field = barKlass->GetFieldIDByName("foo2");
189
190 barObj->SetFieldObject(foo1Field, fooObj1);
191 barObj->SetFieldObject(foo2Field, fooObj2);
192 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj1);
193 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj2);
194
195 EtsObject *res = barObj->GetAndSetFieldObject(foo2Field->GetOffset(), fooObj1, std::memory_order_relaxed);
196 ASSERT_EQ(res, fooObj2); // returned pointer was in foo2_field
197 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj1); // now in foo2_field is pointer to Foo_obj1
198
199 res = barObj->GetAndSetFieldObject(foo1Field->GetOffset(), fooObj2, std::memory_order_relaxed);
200 ASSERT_EQ(res, fooObj1);
201 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj2);
202 }
203
TEST_F(EtsObjectTest,SetAndGetFieldObject)204 TEST_F(EtsObjectTest, SetAndGetFieldObject)
205 {
206 EtsClass *barKlass = GetTestClass("Bar");
207 EtsClass *fooKlass = GetTestClass("Foo");
208
209 EtsObject *barObj = EtsObject::Create(barKlass);
210 EtsObject *fooObj1 = EtsObject::Create(fooKlass);
211 EtsObject *fooObj2 = EtsObject::Create(fooKlass);
212
213 EtsField *foo1Field = barKlass->GetFieldIDByName("foo1");
214 EtsField *foo2Field = barKlass->GetFieldIDByName("foo2");
215
216 barObj->SetFieldObject(foo1Field, fooObj1);
217 barObj->SetFieldObject(foo2Field, fooObj2);
218 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj1);
219 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj2);
220
221 barObj->SetFieldObject(foo1Field, fooObj2);
222 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj2);
223 barObj->SetFieldObject(foo2Field, fooObj1);
224 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj1);
225 }
226
TEST_F(EtsObjectTest,SetAndGetFieldPrimitive)227 TEST_F(EtsObjectTest, SetAndGetFieldPrimitive)
228 {
229 EtsClass *klass = GetTestClass("Rectangle");
230 EtsObject *obj = EtsObject::Create(klass);
231 EtsField *field = klass->GetFieldIDByName("Width");
232 int32_t testNmb1 = 77;
233 obj->SetFieldPrimitive<int32_t>(field, testNmb1);
234 ASSERT_EQ(obj->GetFieldPrimitive<int32_t>(field), testNmb1);
235
236 field = klass->GetFieldIDByName("Height");
237 float testNmb2 = 111.11;
238 obj->SetFieldPrimitive<float>(field, testNmb2);
239 ASSERT_EQ(obj->GetFieldPrimitive<float>(field), testNmb2);
240 }
241
TEST_F(EtsObjectTest,CompareAndSetFieldPrimitive)242 TEST_F(EtsObjectTest, CompareAndSetFieldPrimitive)
243 {
244 EtsClass *klass = GetTestClass("Rectangle");
245 EtsObject *obj = EtsObject::Create(klass);
246 EtsField *field = klass->GetFieldIDByName("Width");
247 int32_t firNmb = 134;
248 int32_t secNmb = 12;
249 obj->SetFieldPrimitive(field, firNmb);
250 obj->CompareAndSetFieldPrimitive(field->GetOffset(), firNmb, secNmb, std::memory_order_relaxed, true);
251 ASSERT_EQ(obj->GetFieldPrimitive<int32_t>(field), secNmb);
252 }
253
TEST_F(EtsObjectTest,CompareAndSetFieldObject)254 TEST_F(EtsObjectTest, CompareAndSetFieldObject)
255 {
256 EtsClass *barKlass = GetTestClass("Bar");
257 EtsClass *fooKlass = GetTestClass("Foo");
258
259 EtsObject *barObj = EtsObject::Create(barKlass);
260 EtsObject *fooObj1 = EtsObject::Create(fooKlass);
261 EtsObject *fooObj2 = EtsObject::Create(fooKlass);
262
263 EtsField *foo1Field = barKlass->GetFieldIDByName("foo1");
264 EtsField *foo2Field = barKlass->GetFieldIDByName("foo2");
265
266 barObj->SetFieldObject(foo1Field, fooObj1);
267 barObj->SetFieldObject(foo2Field, fooObj2);
268 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj1);
269 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj2);
270
271 ASSERT_TRUE(
272 barObj->CompareAndSetFieldObject(foo1Field->GetOffset(), fooObj1, fooObj2, std::memory_order_relaxed, true));
273 ASSERT_EQ(barObj->GetFieldObject(foo1Field), fooObj2);
274
275 ASSERT_TRUE(
276 barObj->CompareAndSetFieldObject(foo2Field->GetOffset(), fooObj2, fooObj1, std::memory_order_relaxed, true));
277 ASSERT_EQ(barObj->GetFieldObject(foo2Field), fooObj1);
278 }
279
TEST_F(EtsObjectTest,GetHashCode_SingleThread)280 TEST_F(EtsObjectTest, GetHashCode_SingleThread)
281 {
282 // create class for testing
283 EtsClass *barKlass = GetTestClass("Bar");
284 EtsObject *obj = EtsObject::Create(barKlass);
285
286 // after creation EtsObject should have no hash code
287 ASSERT_FALSE(obj->IsHashed());
288
289 // First we will test work with hash only
290 // after getting hash it should be generated
291 auto hash = obj->GetHashCode();
292 ASSERT_TRUE(obj->IsHashed());
293 ASSERT_EQ(hash, obj->GetHashCode());
294
295 // next we will test usage of ets object state table
296 // for this we should set interop hash
297 static constexpr uint32_t INTEROP_INDEX_VALUE = 42U;
298 obj->SetInteropIndex(INTEROP_INDEX_VALUE);
299 // switched hashed to used info
300 ASSERT_TRUE(obj->IsUsedInfo());
301 ASSERT_TRUE(obj->IsHashed());
302 ASSERT_TRUE(obj->HasInteropIndex());
303 // after this getting hash should works correct
304 ASSERT_EQ(hash, obj->GetHashCode());
305 ASSERT_EQ(INTEROP_INDEX_VALUE, obj->GetInteropIndex());
306
307 // next we should test droping of interop hash
308 obj->DropInteropIndex();
309 ASSERT_TRUE(obj->IsUsedInfo());
310 ASSERT_TRUE(obj->IsHashed());
311 ASSERT_FALSE(obj->HasInteropIndex());
312 ASSERT_EQ(hash, obj->GetHashCode());
313
314 // finally we deflect object
315 EtsCoroutine::GetCurrent()->GetVM()->FreeInternalResources();
316 ASSERT_TRUE(obj->IsHashed());
317 ASSERT_EQ(hash, obj->GetHashCode());
318 }
319
TEST_F(EtsObjectTest,EtsMarkWordTest)320 TEST_F(EtsObjectTest, EtsMarkWordTest)
321 {
322 // create mark word for testing using object
323 EtsClass *barKlass = GetTestClass("Bar");
324 EtsObject *obj = EtsObject::Create(barKlass);
325 auto markWord = obj->GetMark();
326 ASSERT_EQ(markWord.GetState(), EtsMarkWord::STATE_UNLOCKED);
327
328 // CC-OFFNXT(G.NAM.03) project code style
329 static constexpr uint32_t HASH_TO_DECODE = 12345U;
330 auto hashedMarkWord = markWord.DecodeFromHash(HASH_TO_DECODE);
331 ASSERT_EQ(hashedMarkWord.GetState(), EtsMarkWord::STATE_HASHED);
332 ASSERT_EQ(hashedMarkWord.GetHash(), HASH_TO_DECODE);
333
334 // CC-OFFNXT(G.NAM.03) project code style
335 static constexpr uint32_t INTEROP_INDEX_TO_DECODE = 42U;
336 auto markWordWithInteropIndex = markWord.DecodeFromInteropIndex(INTEROP_INDEX_TO_DECODE);
337 ASSERT_EQ(markWordWithInteropIndex.GetState(), EtsMarkWord::STATE_HAS_INTEROP_INDEX);
338 ASSERT_EQ(markWordWithInteropIndex.GetInteropIndex(), INTEROP_INDEX_TO_DECODE);
339
340 // CC-OFFNXT(G.NAM.03) project code style
341 static constexpr uint32_t USE_INFO_ID_TO_DECODE = 664U;
342 auto markWordWithInfo = markWord.DecodeFromInfo(USE_INFO_ID_TO_DECODE);
343 ASSERT_EQ(markWordWithInfo.GetState(), EtsMarkWord::STATE_USE_INFO);
344 ASSERT_EQ(markWordWithInfo.GetInfoId(), USE_INFO_ID_TO_DECODE);
345 }
346
TEST_F(EtsObjectTest,SetGetAndDropInteropIndex_SingleThread)347 TEST_F(EtsObjectTest, SetGetAndDropInteropIndex_SingleThread)
348 {
349 // create class for testing
350 EtsClass *barKlass = GetTestClass("Bar");
351 EtsObject *obj = EtsObject::Create(barKlass);
352 // after creation EtsObject should have no hash code
353 ASSERT_FALSE(obj->IsHashed());
354 // we should set interop hash by object method
355 // CC-OFFNXT(G.NAM.03) project code style
356 static constexpr uint32_t INTEROP_INDEX_VALUE = 42U;
357 obj->SetInteropIndex(INTEROP_INDEX_VALUE);
358 ASSERT_TRUE(obj->HasInteropIndex());
359 ASSERT_EQ(obj->GetInteropIndex(), INTEROP_INDEX_VALUE);
360
361 // Next test of interop hash droping
362 obj->DropInteropIndex();
363 ASSERT_FALSE(obj->IsHashed());
364 ASSERT_FALSE(obj->HasInteropIndex());
365
366 // Next test of info table usage
367 obj->SetInteropIndex(INTEROP_INDEX_VALUE);
368 auto hash = obj->GetHashCode();
369 // object still should be hashed
370 ASSERT_TRUE(obj->IsUsedInfo());
371 ASSERT_TRUE(obj->IsHashed());
372 ASSERT_TRUE(obj->HasInteropIndex());
373 // after this getting interop hash should works correct
374 ASSERT_EQ(hash, obj->GetHashCode());
375 ASSERT_EQ(INTEROP_INDEX_VALUE, obj->GetInteropIndex());
376
377 // next we should test droping of interop hash
378 obj->DropInteropIndex();
379 ASSERT_TRUE(obj->IsUsedInfo());
380 ASSERT_TRUE(obj->IsHashed());
381 ASSERT_FALSE(obj->HasInteropIndex());
382 ASSERT_EQ(hash, obj->GetHashCode());
383
384 // finally we deflect object
385 EtsCoroutine::GetCurrent()->GetVM()->FreeInternalResources();
386 ASSERT_TRUE(obj->IsHashed());
387 ASSERT_TRUE(obj->IsHashed());
388 ASSERT_EQ(hash, obj->GetHashCode());
389 }
390
TEST_F(EtsObjectTest,InteropIndex_MultiThread)391 TEST_F(EtsObjectTest, InteropIndex_MultiThread)
392 {
393 // CC-OFFNXT(G.NAM.03) project code style
394 static constexpr uint32_t ITERATION_COUNT = 1000;
395 for (size_t i = 0; i < ITERATION_COUNT; i++) {
396 // create class for testing
397 EtsClass *barKlass = GetTestClass("Bar");
398 EtsObject *obj = EtsObject::Create(barKlass);
399 std::atomic_bool finish = false;
400 auto interopHashGetter = [obj, &finish, coro = EtsCoroutine::GetCurrent()] {
401 EtsCoroutine::SetCurrent(coro);
402 // CC-OFFNXT(G.NAM.03) project code style
403 static constexpr uint32_t INTEROP_INDEX = 42U;
404 obj->SetInteropIndex(INTEROP_INDEX);
405 while (!finish) {
406 ASSERT_EQ(INTEROP_INDEX, obj->GetInteropIndex());
407 }
408 obj->DropInteropIndex();
409 ASSERT_TRUE(obj->IsHashed());
410 ASSERT_FALSE(obj->HasInteropIndex());
411 };
412
413 auto interopThread = std::thread(interopHashGetter);
414 auto hash = obj->GetHashCode();
415 ASSERT_EQ(hash, obj->GetHashCode());
416 finish = true;
417 interopThread.join();
418 }
419 }
420
TEST_F(EtsObjectTest,EtsHash_MultiThread)421 TEST_F(EtsObjectTest, EtsHash_MultiThread)
422 {
423 // CC-OFFNXT(G.NAM.03) project code style
424 static constexpr uint32_t ITERATION_COUNT = 1000;
425 for (size_t i = 0; i < ITERATION_COUNT; i++) {
426 // create class for testing
427 EtsClass *barKlass = GetTestClass("Bar");
428 EtsObject *obj = EtsObject::Create(barKlass);
429 std::atomic_bool finish = false;
430 auto interopHashGetter = [obj, &finish, coro = EtsCoroutine::GetCurrent()] {
431 EtsCoroutine::SetCurrent(coro);
432 auto hash = obj->GetHashCode();
433 while (!finish) {
434 ASSERT_EQ(hash, obj->GetHashCode());
435 }
436 ASSERT_TRUE(obj->HasInteropIndex());
437 };
438
439 auto interopThread = std::thread(interopHashGetter);
440 // CC-OFFNXT(G.NAM.03) project code style
441 static constexpr uint32_t INTEROP_INDEX = 42U;
442 obj->SetInteropIndex(INTEROP_INDEX);
443 ASSERT_EQ(INTEROP_INDEX, obj->GetInteropIndex());
444 finish = true;
445 interopThread.join();
446 obj->DropInteropIndex();
447 ASSERT_TRUE(obj->IsUsedInfo());
448 EtsCoroutine::GetCurrent()->GetVM()->FreeInternalResources();
449 ASSERT_TRUE(obj->IsHashed());
450 }
451 }
452
453 } // namespace ark::ets::test
454
455 // NOLINTEND(readability-magic-numbers)
456