1 /**
2 * Copyright (c) 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 "plugins/ets/runtime/ets_class_linker_context.h"
17
18 #include "ets_platform_types.h"
19 #include "include/class.h"
20 #include "plugins/ets/runtime/ets_coroutine.h"
21 #include "plugins/ets/runtime/ets_vm.h"
22 #include "plugins/ets/runtime/types/ets_abc_file.h"
23 #include "plugins/ets/runtime/types/ets_abc_runtime_linker.h"
24 #include "plugins/ets/runtime/types/ets_method.h"
25 #include "plugins/ets/runtime/types/ets_string.h"
26
27 namespace ark::ets {
28
29 namespace {
30
31 class DecoratorErrorHandler final : public ClassLinkerErrorHandler {
32 public:
DecoratorErrorHandler(ClassLinkerErrorHandler * errorHandler)33 explicit DecoratorErrorHandler(ClassLinkerErrorHandler *errorHandler) : errorHandler_(errorHandler) {}
34
OnError(ClassLinker::Error error,const PandaString & message)35 void OnError(ClassLinker::Error error, const PandaString &message) override
36 {
37 lastError_.emplace(error, message);
38 }
39
HasError() const40 bool HasError() const
41 {
42 return lastError_.has_value();
43 }
44
GetErrorCode() const45 ClassLinker::Error GetErrorCode() const
46 {
47 ASSERT(HasError());
48 return lastError_->first;
49 }
50
PropagateError()51 void PropagateError()
52 {
53 ASSERT(HasError());
54 if (errorHandler_ != nullptr) {
55 errorHandler_->OnError(lastError_->first, lastError_->second);
56 }
57 }
58
ClearError()59 void ClearError()
60 {
61 lastError_.reset();
62 }
63
64 private:
65 ClassLinkerErrorHandler *errorHandler_ {nullptr};
66 std::optional<std::pair<ClassLinker::Error, PandaString>> lastError_;
67 };
68
ReportClassNotFound(const uint8_t * descriptor,ClassLinkerErrorHandler * errorHandler)69 void ReportClassNotFound(const uint8_t *descriptor, ClassLinkerErrorHandler *errorHandler)
70 {
71 if (errorHandler != nullptr) {
72 PandaStringStream ss;
73 ss << "Cannot find class " << descriptor;
74 errorHandler->OnError(ClassLinker::Error::CLASS_NOT_FOUND, ss.str());
75 }
76 }
77
LoadFromBootContext(const uint8_t * descriptor,DecoratorErrorHandler & errorHandler,ClassLinkerContext * ctx)78 Class *LoadFromBootContext(const uint8_t *descriptor, DecoratorErrorHandler &errorHandler, ClassLinkerContext *ctx)
79 {
80 ASSERT(ctx->IsBootContext());
81 auto *klass = Runtime::GetCurrent()->GetClassLinker()->GetClass(descriptor, true, ctx, &errorHandler);
82 if (errorHandler.HasError()) {
83 if (errorHandler.GetErrorCode() == ClassLinker::Error::CLASS_NOT_FOUND) {
84 // Clear the error in order to delegate resolution.
85 errorHandler.ClearError();
86 } else {
87 // Report errors occurred during class loading.
88 errorHandler.PropagateError();
89 }
90 }
91 return klass;
92 }
93
TryLoadingClassInChain(const uint8_t * descriptor,DecoratorErrorHandler & errorHandler,ClassLinkerContext * ctx,Class ** klass)94 bool TryLoadingClassInChain(const uint8_t *descriptor, DecoratorErrorHandler &errorHandler, ClassLinkerContext *ctx,
95 Class **klass)
96 {
97 ASSERT(ctx != nullptr);
98 ASSERT(klass != nullptr);
99 ASSERT(*klass == nullptr);
100
101 if (ctx->IsBootContext()) {
102 // No need to load by managed code, even if error occurred during loading.
103 *klass = LoadFromBootContext(descriptor, errorHandler, ctx);
104 return true;
105 }
106
107 // All non-boot contexts are represented by EtsClassLinkerContext.
108 auto *etsLinkerContext = reinterpret_cast<EtsClassLinkerContext *>(ctx);
109 auto *runtimeLinker = etsLinkerContext->GetRuntimeLinker();
110 ASSERT(runtimeLinker != nullptr);
111 if (runtimeLinker->GetClass() != PlatformTypes()->coreAbcRuntimeLinker &&
112 runtimeLinker->GetClass() != PlatformTypes()->memoryRuntimeLinker) {
113 // Must call managed implementation.
114 return false;
115 }
116
117 auto *abcRuntimeLinker = EtsAbcRuntimeLinker::FromEtsObject(runtimeLinker);
118 auto *parentLinker = abcRuntimeLinker->GetParentLinker();
119 ASSERT(parentLinker != nullptr);
120 auto *parentContext = parentLinker->GetClassLinkerContext();
121 if (!TryLoadingClassInChain(descriptor, errorHandler, parentContext, klass)) {
122 // Chain resolution failed, must call managed implementation.
123 ASSERT(*klass == nullptr);
124 return false;
125 }
126
127 // No need to load by managed code, even if error occurred during loading.
128 if (*klass == nullptr && !errorHandler.HasError()) {
129 *klass = etsLinkerContext->FindAndLoadClass(descriptor, &errorHandler);
130 }
131 return true;
132 }
133
134 /// @brief Walks through RuntimeLinker chain and enumerates all panda files
TryEnumeratePandaFilesInChain(const ClassLinkerContext * ctx,const std::function<bool (const panda_file::File &)> & cb)135 bool TryEnumeratePandaFilesInChain(const ClassLinkerContext *ctx,
136 const std::function<bool(const panda_file::File &)> &cb)
137 {
138 ASSERT(ctx != nullptr);
139
140 if (ctx->IsBootContext()) {
141 ctx->EnumeratePandaFiles(cb);
142 return true;
143 }
144
145 // All non-boot contexts are represented by EtsClassLinkerContext
146 auto *etsLinkerContext = reinterpret_cast<const EtsClassLinkerContext *>(ctx);
147 auto *runtimeLinker = etsLinkerContext->GetRuntimeLinker();
148 ASSERT(runtimeLinker != nullptr);
149 if (!runtimeLinker->IsInstanceOf(PlatformTypes()->coreAbcRuntimeLinker)) {
150 // Unexpected behavior, cannot walk through chain
151 return false;
152 }
153
154 auto *abcRuntimeLinker = EtsAbcRuntimeLinker::FromEtsObject(runtimeLinker);
155 auto *parentLinker = abcRuntimeLinker->GetParentLinker();
156 ASSERT(parentLinker != nullptr);
157 auto *parentContext = parentLinker->GetClassLinkerContext();
158 if (!TryEnumeratePandaFilesInChain(parentContext, cb)) {
159 return false;
160 }
161
162 parentContext->EnumeratePandaFiles(cb);
163 return true;
164 }
165
166 } // namespace
167
LoadClass(const uint8_t * descriptor,bool needCopyDescriptor,ClassLinkerErrorHandler * errorHandler)168 Class *EtsClassLinkerContext::LoadClass(const uint8_t *descriptor, [[maybe_unused]] bool needCopyDescriptor,
169 ClassLinkerErrorHandler *errorHandler)
170 {
171 auto *klass = FindClass(descriptor);
172 if (klass != nullptr) {
173 return klass;
174 }
175
176 ASSERT(!PandaVM::GetCurrent()->GetGC()->IsGCRunning() || PandaVM::GetCurrent()->GetMutatorLock()->HasLock());
177
178 // Try loading the class without invoking managed code.
179 auto succeeded = TryLoadingClassFromNative(descriptor, errorHandler, &klass);
180 if (succeeded) {
181 return klass;
182 }
183
184 auto *coro = EtsCoroutine::GetCurrent();
185 if (coro == nullptr || !coro->IsManagedCode()) {
186 // Do not invoke managed code from non-managed threads (e.g. JIT or AOT).
187 ReportClassNotFound(descriptor, errorHandler);
188 return nullptr;
189 }
190
191 auto clsName = ClassHelper::GetName(descriptor);
192 auto etsClsName = EtsString::CreateFromMUtf8(clsName.c_str());
193 const auto *runtimeLinker = GetRuntimeLinker();
194 ASSERT(runtimeLinker != nullptr);
195 ASSERT(etsClsName != nullptr);
196 std::array args {Value(runtimeLinker->GetCoreType()), Value(etsClsName->GetCoreType()), Value(ETS_TRUE)};
197
198 auto *loadClass = runtimeLinker->GetClass()->GetInstanceMethod("loadClass", "Lstd/core/String;Z:Lstd/core/Class;");
199 ASSERT(loadClass != nullptr);
200 auto res = loadClass->GetPandaMethod()->Invoke(coro, args.data());
201 if (!coro->HasPendingException()) {
202 return Class::FromClassObject(res.GetAs<ObjectHeader *>());
203 }
204
205 ReportClassNotFound(descriptor, errorHandler);
206 return nullptr;
207 }
208
EnumeratePandaFilesImpl(const std::function<bool (const panda_file::File &)> & cb) const209 void EtsClassLinkerContext::EnumeratePandaFilesImpl(const std::function<bool(const panda_file::File &)> &cb) const
210 {
211 ASSERT(PandaEtsVM::GetCurrent()->GetMutatorLock()->HasLock());
212 auto *runtimeLinker = GetRuntimeLinker();
213 ASSERT(runtimeLinker != nullptr);
214 if (!runtimeLinker->IsInstanceOf(PlatformTypes()->coreAbcRuntimeLinker)) {
215 return;
216 }
217 auto *contextAbcFiles = EtsAbcRuntimeLinker::FromEtsObject(runtimeLinker)->GetAbcFiles();
218 ASSERT(contextAbcFiles != nullptr);
219 for (size_t i = 0, end = contextAbcFiles->GetLength(); i < end; ++i) {
220 auto *pf = EtsAbcFile::FromEtsObject(contextAbcFiles->Get(i))->GetPandaFile();
221 if (!cb(*pf)) {
222 break;
223 }
224 }
225 }
226
EnumeratePandaFiles(const std::function<bool (const panda_file::File &)> & cb) const227 void EtsClassLinkerContext::EnumeratePandaFiles(const std::function<bool(const panda_file::File &)> &cb) const
228 {
229 EnumeratePandaFilesImpl(cb);
230 }
231
EnumeratePandaFilesInChain(const std::function<bool (const panda_file::File &)> & cb) const232 void EtsClassLinkerContext::EnumeratePandaFilesInChain(const std::function<bool(const panda_file::File &)> &cb) const
233 {
234 [[maybe_unused]] bool succeeded = TryEnumeratePandaFilesInChain(this, cb);
235 // `RuntimeLinker` chain must be always traversed correctly,
236 // because `AbcRuntimeLinker` is the only base class for all user-defined linkers
237 ASSERT(succeeded);
238 }
239
FindAndLoadClass(const uint8_t * descriptor,ClassLinkerErrorHandler * errorHandler)240 Class *EtsClassLinkerContext::FindAndLoadClass(const uint8_t *descriptor, ClassLinkerErrorHandler *errorHandler)
241 {
242 Class *klass = nullptr;
243 EnumeratePandaFilesImpl([this, &klass, descriptor, errorHandler](const auto &pf) {
244 auto classId = pf.GetClassId(descriptor);
245 if (!classId.IsValid() || pf.IsExternal(classId)) {
246 return true;
247 }
248 klass = Runtime::GetCurrent()->GetClassLinker()->LoadClass(pf, classId, this, errorHandler);
249 return false;
250 });
251 return klass;
252 }
253
TryLoadingClassFromNative(const uint8_t * descriptor,ClassLinkerErrorHandler * errorHandler,Class ** klass)254 bool EtsClassLinkerContext::TryLoadingClassFromNative(const uint8_t *descriptor, ClassLinkerErrorHandler *errorHandler,
255 Class **klass)
256 {
257 ASSERT(klass != nullptr);
258 ASSERT(*klass == nullptr);
259
260 DecoratorErrorHandler handler(errorHandler);
261 auto succeeded = TryLoadingClassInChain(descriptor, handler, this, klass);
262 if (handler.HasError()) {
263 // Report errors occurred during class loading
264 ASSERT(*klass == nullptr);
265 handler.PropagateError();
266 } else if (succeeded && *klass == nullptr) {
267 ReportClassNotFound(descriptor, errorHandler);
268 }
269 return succeeded;
270 }
271
272 } // namespace ark::ets
273