1 /**
2 * Copyright (c) 2024-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 <array>
18
19 #include "libpandabase/os/mutex.h"
20 #include "runtime/include/thread_scopes.h"
21 #include "runtime/include/gc_task.h"
22 #include "runtime/tests/test_utils.h"
23 #include "plugins/ets/runtime/ets_vm.h"
24 #include "plugins/ets/runtime/ets_coroutine.h"
25 #include "plugins/ets/runtime/ets_handle.h"
26 #include "plugins/ets/runtime/ets_handle_scope.h"
27 #include "plugins/ets/runtime/types/ets_class.h"
28 #include "plugins/ets/runtime/types/ets_array.h"
29 #include "plugins/ets/runtime/types/ets_finalizable_weak_ref.h"
30 #include "plugins/ets/tests/runtime/types/ets_test_mirror_classes.h"
31
32 namespace ark::ets::test {
33
34 class EtsFinalizableWeakRefTest : public testing::Test {
35 public:
EtsFinalizableWeakRefTest()36 EtsFinalizableWeakRefTest()
37 {
38 RuntimeOptions options;
39 options.SetShouldLoadBootPandaFiles(true);
40 options.SetShouldInitializeIntrinsics(false);
41 options.SetCompilerEnableJit(false);
42 options.SetGcType("g1-gc");
43 options.SetLoadRuntimes({"ets"});
44 options.SetUseTlabForAllocations(false);
45 options.SetGcTriggerType("debug");
46 options.SetGcDebugTriggerStart(std::numeric_limits<int>::max());
47 options.SetExplicitConcurrentGcEnabled(false);
48
49 auto stdlib = std::getenv("PANDA_STD_LIB");
50 if (stdlib == nullptr) {
51 std::cerr << "PANDA_STD_LIB env variable should be set and point to mock_stdlib.abc" << std::endl;
52 std::abort();
53 }
54 options.SetBootPandaFiles({stdlib});
55
56 Runtime::Create(options);
57 EtsCoroutine *coroutine = EtsCoroutine::GetCurrent();
58 vm_ = coroutine->GetPandaVM();
59 }
60
~EtsFinalizableWeakRefTest()61 ~EtsFinalizableWeakRefTest() override
62 {
63 Runtime::Destroy();
64 }
65
66 NO_COPY_SEMANTIC(EtsFinalizableWeakRefTest);
67 NO_MOVE_SEMANTIC(EtsFinalizableWeakRefTest);
68
CompareMemberOffsets(EtsClass * klass,const std::vector<MirrorFieldInfo> & members)69 static void CompareMemberOffsets(EtsClass *klass, const std::vector<MirrorFieldInfo> &members)
70 {
71 ASSERT_NE(nullptr, klass);
72 ASSERT_EQ(members.size(), klass->GetInstanceFieldsNumber());
73
74 // Check both EtsPromise and ark::Class<Promise> has the same number of fields
75 // and at the same offsets
76 for (const MirrorFieldInfo &memb : members) {
77 EtsField *field = klass->GetFieldIDByName(memb.Name());
78 ASSERT_NE(nullptr, field);
79 ASSERT_EQ(memb.Offset(), field->GetOffset())
80 << "Offsets of the field '" << memb.Name() << "' are different";
81 }
82 }
83
GetFinalizableWeakRefMembers()84 static std::vector<MirrorFieldInfo> GetFinalizableWeakRefMembers()
85 {
86 return std::vector<MirrorFieldInfo> {
87 MIRROR_FIELD_INFO(EtsFinalizableWeakRef, referent_, "referent"),
88 MIRROR_FIELD_INFO(EtsFinalizableWeakRef, prev_, "prevRef"),
89 MIRROR_FIELD_INFO(EtsFinalizableWeakRef, next_, "nextRef"),
90 MIRROR_FIELD_INFO(EtsFinalizableWeakRef, finalizerPtr_, "finalizerPtr"),
91 MIRROR_FIELD_INFO(EtsFinalizableWeakRef, finalizerArgPtr_, "finalizerArgPtr")};
92 }
93
AllocObjectInYoung()94 static ObjectHeader *AllocObjectInYoung()
95 {
96 auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
97 return reinterpret_cast<ObjectHeader *>(
98 coretypes::String::CreateEmptyString(ctx, Runtime::GetCurrent()->GetPandaVM()));
99 }
100
TriggerGC()101 static void TriggerGC()
102 {
103 auto *coro = EtsCoroutine::GetCurrent();
104 auto *obj = AllocObjectInYoung();
105 EtsHandleScope arrayScope(coro);
106 VMHandle<ObjectHeader> hObj(EtsCoroutine::GetCurrent(), obj);
107 while (hObj.GetPtr() == obj) {
108 AllocObjectInYoung();
109 }
110 }
111
112 protected:
113 PandaEtsVM *vm_ = nullptr; // NOLINT(misc-non-private-member-variables-in-classes)
114 };
115
TEST_F(EtsFinalizableWeakRefTest,FinalizableWeakRefMemoryLayout)116 TEST_F(EtsFinalizableWeakRefTest, FinalizableWeakRefMemoryLayout)
117 {
118 EtsClass *finalizableWeakRefClass = PlatformTypes(vm_)->coreFinalizableWeakRef;
119 CompareMemberOffsets(finalizableWeakRefClass, GetFinalizableWeakRefMembers());
120 }
121
122 template <size_t EVENT_COUNT>
123 class TestEvent {
124 public:
Finalize(void * arg)125 static void Finalize(void *arg)
126 {
127 auto *finalizer = reinterpret_cast<TestEvent *>(arg);
128 finalizer->Fire();
129 }
130
IsHappened() const131 bool IsHappened() const
132 {
133 return happened_;
134 }
135
Wait()136 void Wait()
137 {
138 os::memory::LockHolder lh(mutex_);
139 while (!happened_) {
140 cv_.Wait(&mutex_);
141 }
142 }
143
Fire()144 void Fire()
145 {
146 os::memory::LockHolder lh(mutex_);
147 if (++eventCount_ == EVENT_COUNT) {
148 happened_ = true;
149 cv_.Signal();
150 }
151 }
152
153 private:
154 size_t eventCount_ = 0;
155 std::atomic<bool> happened_ = false;
156 os::memory::Mutex mutex_;
157 os::memory::ConditionVariable cv_;
158 };
159
TEST_F(EtsFinalizableWeakRefTest,RegisterFinalizerTest)160 TEST_F(EtsFinalizableWeakRefTest, RegisterFinalizerTest)
161 {
162 // Initialize
163 auto *coro = EtsCoroutine::GetCurrent();
164 ScopedManagedCodeThread managedScope(coro);
165 static constexpr size_t EVENT_COUNT = 10U;
166 TestEvent<EVENT_COUNT> event;
167 // Create referents
168 {
169 auto *objectClass = vm_->GetClassLinker()->GetClassRoot(EtsClassRoot::OBJECT);
170 static constexpr size_t ARRAY_SIZE = 1024U;
171 EtsHandleScope arrayScope(coro);
172 std::array<EtsHandle<EtsObject>, EVENT_COUNT> arrayHandles;
173 for (auto &handle : arrayHandles) {
174 handle = EtsHandle<EtsObject>(coro, EtsObjectArray::Create(objectClass, ARRAY_SIZE)->AsObject());
175 vm_->RegisterFinalizerForObject(coro, handle, TestEvent<EVENT_COUNT>::Finalize, &event);
176 }
177
178 // Trigger GC
179 TriggerGC();
180
181 // Check finalizer was not called because referent is alive
182 ASSERT(!event.IsHappened());
183 }
184 // Referents are dead
185
186 // Trigger Full GC to handle references
187 vm_->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::OOM_CAUSE));
188
189 // Check that finalizer was called
190 event.Wait();
191 ASSERT(event.IsHappened());
192 }
193
194 } // namespace ark::ets::test
195