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 #ifndef PANDA_PLUGINS_ETS_RUNTIME_STUBS_INL_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_STUBS_INL_H
18
19 #include "plugins/ets/runtime/ets_coroutine.h"
20 #include "plugins/ets/runtime/types/ets_object.h"
21 #include "plugins/ets/runtime/ets_stubs.h"
22 #include "plugins/ets/runtime/ets_exceptions.h"
23 #include "libpandafile/proto_data_accessor-inl.h"
24
25 namespace ark::ets {
26
27 static constexpr const char *GETTER_PREFIX = "<get>";
28 static constexpr const char *SETTER_PREFIX = "<set>";
29
EtsReferenceNullish(EtsCoroutine * coro,EtsObject * ref)30 ALWAYS_INLINE inline bool EtsReferenceNullish(EtsCoroutine *coro, EtsObject *ref)
31 {
32 ASSERT(coro != nullptr);
33 return ref == nullptr || ref == EtsObject::FromCoreType(coro->GetNullValue());
34 }
35
IsReferenceNullish(EtsCoroutine * coro,EtsObject * ref)36 ALWAYS_INLINE inline bool IsReferenceNullish(EtsCoroutine *coro, EtsObject *ref)
37 {
38 ASSERT(coro != nullptr);
39 return ref == nullptr || ref == EtsObject::FromCoreType(coro->GetNullValue());
40 }
41
42 template <bool IS_STRICT>
EtsReferenceEquals(EtsCoroutine * coro,EtsObject * ref1,EtsObject * ref2)43 ALWAYS_INLINE inline bool EtsReferenceEquals(EtsCoroutine *coro, EtsObject *ref1, EtsObject *ref2)
44 {
45 if (UNLIKELY(ref1 == ref2)) {
46 return true;
47 }
48
49 if (IsReferenceNullish(coro, ref1) || IsReferenceNullish(coro, ref2)) {
50 if constexpr (IS_STRICT) {
51 return false;
52 } else {
53 return IsReferenceNullish(coro, ref1) && IsReferenceNullish(coro, ref2);
54 }
55 }
56
57 ASSERT(ref1 != nullptr);
58 ASSERT(ref2 != nullptr);
59 if (LIKELY(!(ref1->GetClass()->IsValueTyped() && ref2->GetClass()->IsValueTyped()))) {
60 return false;
61 }
62 return EtsValueTypedEquals(coro, ref1, ref2);
63 }
64
EtsReferenceTypeof(EtsCoroutine * coro,EtsObject * ref)65 ALWAYS_INLINE inline EtsString *EtsReferenceTypeof(EtsCoroutine *coro, EtsObject *ref)
66 {
67 return EtsGetTypeof(coro, ref);
68 }
69
EtsIstrue(EtsCoroutine * coro,EtsObject * obj)70 ALWAYS_INLINE inline bool EtsIstrue(EtsCoroutine *coro, EtsObject *obj)
71 {
72 return EtsGetIstrue(coro, obj);
73 }
74
75 // CC-OFFNXT(C_RULE_ID_INLINE_FUNCTION_SIZE) Perf critical common runtime code stub
GetMethodOwnerClassInFrames(EtsCoroutine * coro,uint32_t depth)76 inline EtsClass *GetMethodOwnerClassInFrames(EtsCoroutine *coro, uint32_t depth)
77 {
78 auto stack = StackWalker::Create(coro);
79 for (uint32_t i = 0; i < depth && stack.HasFrame(); ++i) {
80 stack.NextFrame();
81 }
82 if (UNLIKELY(!stack.HasFrame())) {
83 return nullptr;
84 }
85 auto method = stack.GetMethod();
86 if (UNLIKELY(method == nullptr)) {
87 return nullptr;
88 }
89 return EtsClass::FromRuntimeClass(method->GetClass());
90 }
91
IsValidByType(EtsCoroutine * coro,ark::Class * argClass,Field * metaField,bool isGetter)92 ALWAYS_INLINE inline void IsValidByType(EtsCoroutine *coro, ark::Class *argClass, Field *metaField, bool isGetter)
93 {
94 auto sourceClass = isGetter ? argClass : metaField->ResolveTypeClass();
95 auto targetClass = isGetter ? metaField->ResolveTypeClass() : argClass;
96 if (UNLIKELY(!targetClass->IsAssignableFrom(sourceClass))) {
97 auto errorMsg = targetClass->GetName() + " cannot be cast to " + sourceClass->GetName();
98 ThrowEtsException(coro,
99 utf::Mutf8AsCString(Runtime::GetCurrent()
100 ->GetLanguageContext(panda_file::SourceLang::ETS)
101 .GetClassCastExceptionClassDescriptor()),
102 errorMsg);
103 }
104 }
105
IsValidAccessorByName(EtsCoroutine * coro,Field * metaField,Method * resolved,bool isGetter)106 ALWAYS_INLINE inline void IsValidAccessorByName(EtsCoroutine *coro, Field *metaField, Method *resolved, bool isGetter)
107 {
108 auto pf = resolved->GetPandaFile();
109 auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
110 panda_file::ProtoDataAccessor pda(*pf, panda_file::MethodDataAccessor::GetProtoId(*pf, resolved->GetFileId()));
111 auto argClass = classLinker->GetClass(*pf, pda.GetReferenceType(0), resolved->GetClass()->GetLoadContext());
112 IsValidByType(coro, argClass, metaField, isGetter);
113 }
114
IsValidFieldByName(EtsCoroutine * coro,Field * metaField,Field * resolved,bool isGetter)115 ALWAYS_INLINE inline void IsValidFieldByName(EtsCoroutine *coro, Field *metaField, Field *resolved, bool isGetter)
116 {
117 auto pf = resolved->GetPandaFile();
118 auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
119 auto argClass = classLinker->GetClass(*pf, panda_file::FieldDataAccessor::GetTypeId(*pf, resolved->GetFileId()),
120 resolved->GetClass()->GetLoadContext());
121 IsValidByType(coro, argClass, metaField, isGetter);
122 }
123
124 template <bool IS_GETTER>
LookUpException(ark::Class * klass,Field * rawField)125 inline void LookUpException(ark::Class *klass, Field *rawField)
126 {
127 auto type = IS_GETTER ? "getter" : "setter";
128 auto errorMsg = "Class " + ark::ConvertToString(klass->GetName()) + " does not have field and " +
129 ark::ConvertToString(type) + " with name " + utf::Mutf8AsCString(rawField->GetName().data);
130 ThrowEtsException(EtsCoroutine::GetCurrent(),
131 panda_file_items::class_descriptors::LINKER_UNRESOLVED_FIELD_ERROR.data(), errorMsg);
132 }
133
LookUpException(ark::Class * klass,ark::Method * rawMethod)134 inline void LookUpException(ark::Class *klass, ark::Method *rawMethod)
135 {
136 auto rawMethodName = (rawMethod == nullptr) ? "null" : utf::Mutf8AsCString(rawMethod->GetName().data);
137 auto errorMsg =
138 "Class " + ark::ConvertToString(klass->GetName()) + " does not have method with name " + rawMethodName;
139 ThrowEtsException(EtsCoroutine::GetCurrent(),
140 panda_file_items::class_descriptors::LINKER_UNRESOLVED_METHOD_ERROR.data(), errorMsg);
141 }
142
143 template <bool IS_GETTER>
GetFieldByName(InterpreterCache::Entry * entry,ark::Method * method,Field * rawField,const uint8_t * address,ark::Class * klass)144 ALWAYS_INLINE Field *GetFieldByName(InterpreterCache::Entry *entry, ark::Method *method, Field *rawField,
145 const uint8_t *address, ark::Class *klass)
146 {
147 auto *res = entry->item;
148 auto resUint = reinterpret_cast<uint64_t>(res);
149 auto fieldPtr = reinterpret_cast<Field *>(resUint & ~METHOD_FLAG_MASK);
150 bool cacheExists = res != nullptr && ((resUint & METHOD_FLAG_MASK) == 1);
151 Class *current = klass;
152 Field *field = nullptr;
153 while (current != nullptr) {
154 if (cacheExists && (fieldPtr->GetClass() == current)) {
155 return fieldPtr;
156 }
157 ASSERT(rawField != nullptr);
158 field = LookupFieldByName(rawField->GetName(), current);
159 if (field == nullptr) {
160 current = current->GetBase();
161 continue;
162 }
163 if (field->GetTypeId() == panda_file::Type::TypeId::REFERENCE) {
164 IsValidFieldByName(EtsCoroutine::GetCurrent(), rawField, field, IS_GETTER);
165 }
166 *entry = {address, method, static_cast<void *>(field)};
167 return field;
168 }
169 return field;
170 }
171
172 template <panda_file::Type::TypeId FIELD_TYPE, bool IS_GETTER>
GetAccessorByName(InterpreterCache::Entry * entry,ark::Method * method,Field * rawField,const uint8_t * address,ark::Class * klass)173 ALWAYS_INLINE inline ark::Method *GetAccessorByName(InterpreterCache::Entry *entry, ark::Method *method,
174 Field *rawField, const uint8_t *address, ark::Class *klass)
175 {
176 auto *res = entry->item;
177 auto resUint = reinterpret_cast<uint64_t>(res);
178 bool cacheExists = res != nullptr && ((resUint & METHOD_FLAG_MASK) == 1);
179 auto methodPtr = reinterpret_cast<Method *>(resUint & ~METHOD_FLAG_MASK);
180 ark::Method *callee = nullptr;
181 Class *current = klass;
182 while (current != nullptr) {
183 if (cacheExists && (methodPtr->GetClass() == klass)) {
184 return methodPtr;
185 }
186 if constexpr (IS_GETTER) {
187 callee = LookupGetterByName<FIELD_TYPE>(rawField->GetName(), current);
188 } else {
189 callee = LookupSetterByName<FIELD_TYPE>(rawField->GetName(), current);
190 }
191 if (callee == nullptr) {
192 current = current->GetBase();
193 continue;
194 }
195 if constexpr (FIELD_TYPE == panda_file::Type::TypeId::REFERENCE) {
196 IsValidAccessorByName(EtsCoroutine::GetCurrent(), rawField, callee, IS_GETTER);
197 }
198 auto mUint = reinterpret_cast<uint64_t>(callee);
199 *entry = {address, method, reinterpret_cast<Method *>(mUint | METHOD_FLAG_MASK)};
200 return callee;
201 }
202 return callee;
203 }
204
205 template <bool IS_GETTER>
CheckAccessorNameMatch(Span<const uint8_t> name,Span<const uint8_t> method)206 ALWAYS_INLINE inline bool CheckAccessorNameMatch(Span<const uint8_t> name, Span<const uint8_t> method)
207 {
208 std::string_view methodStr(utf::Mutf8AsCString(method.data()), method.size());
209 std::string_view nameStr(utf::Mutf8AsCString(name.data()), name.size());
210 std::string_view prefix = IS_GETTER ? GETTER_PREFIX : SETTER_PREFIX;
211 if (methodStr.size() - nameStr.size() != prefix.size() || (methodStr.substr(0, prefix.size()) != prefix)) {
212 return false;
213 }
214 return methodStr.substr(prefix.size()) == nameStr;
215 }
216
LookupFieldByName(panda_file::File::StringData name,const ark::Class * klass)217 ALWAYS_INLINE inline Field *LookupFieldByName(panda_file::File::StringData name, const ark::Class *klass)
218 {
219 for (auto &f : klass->GetInstanceFields()) {
220 if (name == f.GetName()) {
221 return &f;
222 }
223 }
224 return nullptr;
225 }
226
227 template <panda_file::Type::TypeId FIELD_TYPE>
LookupGetterByName(panda_file::File::StringData name,const ark::Class * klass)228 ALWAYS_INLINE inline ark::Method *LookupGetterByName(panda_file::File::StringData name, const ark::Class *klass)
229 {
230 Span<const uint8_t> nameSpan((name.data), utf::Mutf8Size(name.data));
231 for (auto &m : klass->GetMethods()) {
232 Span<const uint8_t> methodSpan(m.GetName().data, utf::Mutf8Size(m.GetName().data));
233 if (!CheckAccessorNameMatch<true>(nameSpan, methodSpan)) {
234 continue;
235 }
236 auto retType = m.GetReturnType();
237 if (retType.IsVoid()) {
238 continue;
239 }
240 if (m.GetNumArgs() != 1) {
241 continue;
242 }
243 if (!m.GetArgType(0).IsReference()) {
244 continue;
245 }
246 if constexpr (FIELD_TYPE == panda_file::Type::TypeId::REFERENCE) {
247 if (retType.IsPrimitive()) {
248 continue;
249 }
250 return &m;
251 }
252
253 if (retType.IsReference()) {
254 continue;
255 }
256 if constexpr (panda_file::Type(FIELD_TYPE).GetBitWidth() == coretypes::INT64_BITS) {
257 if (retType.GetBitWidth() != coretypes::INT64_BITS) {
258 continue;
259 }
260 } else {
261 if (retType.GetBitWidth() > coretypes::INT32_BITS) {
262 continue;
263 }
264 }
265 return &m;
266 }
267 return nullptr;
268 }
269
270 template <panda_file::Type::TypeId FIELD_TYPE>
LookupSetterByName(panda_file::File::StringData name,const ark::Class * klass)271 ALWAYS_INLINE inline ark::Method *LookupSetterByName(panda_file::File::StringData name, const ark::Class *klass)
272 {
273 Span<const uint8_t> nameSpan((name.data), utf::Mutf8Size(name.data));
274 for (auto &m : klass->GetMethods()) {
275 Span<const uint8_t> methodSpan(m.GetName().data, utf::Mutf8Size(m.GetName().data));
276 if (!CheckAccessorNameMatch<false>(nameSpan, methodSpan)) {
277 continue;
278 }
279 if (m.IsStatic() || !m.GetReturnType().IsVoid() || m.GetNumArgs() != 2U || !m.GetArgType(0).IsReference()) {
280 continue;
281 }
282 if constexpr (FIELD_TYPE == panda_file::Type::TypeId::REFERENCE) {
283 if (m.GetArgType(1).IsPrimitive()) {
284 continue;
285 }
286 return &m;
287 }
288
289 auto arg1 = m.GetArgType(1);
290 if (arg1.IsReference()) {
291 continue;
292 }
293 if constexpr (panda_file::Type(FIELD_TYPE).GetBitWidth() == coretypes::INT64_BITS) {
294 if (arg1.GetBitWidth() != coretypes::INT64_BITS) {
295 continue;
296 }
297 } else {
298 if (arg1.GetBitWidth() > coretypes::INT32_BITS) {
299 continue;
300 }
301 }
302 return &m;
303 }
304 return nullptr;
305 }
306
GetMethodFromCache(ETSStubCacheInfo const & cache,ark::Class * klass)307 ALWAYS_INLINE inline Method *GetMethodFromCache(ETSStubCacheInfo const &cache, ark::Class *klass)
308 {
309 auto *res = cache.GetItem();
310 auto resUint = reinterpret_cast<uint64_t>(res);
311 bool cacheExists = (res != nullptr) && ((resUint & METHOD_FLAG_MASK) == 1);
312 auto methodPtr = reinterpret_cast<Method *>(resUint & ~METHOD_FLAG_MASK);
313 if (LIKELY(cacheExists && (methodPtr->GetClass()->IsAssignableFrom(klass)))) {
314 auto methods = klass->GetVTable();
315 return methods[methodPtr->GetVTableIndex()];
316 }
317 return nullptr;
318 }
319
320 // CC-OFFNXT(C_RULE_ID_INLINE_FUNCTION_SIZE) Perf critical common runtime code stub
321 // CC-OFFNXT(G.FUD.06) perf critical
MethodIsSupertypeOf(ClassLinker * linker,Method * superm,Method * subm)322 ALWAYS_INLINE inline bool MethodIsSupertypeOf(ClassLinker *linker, Method *superm, Method *subm)
323 {
324 Method::ProtoId const &super = superm->GetProtoId();
325 Method::ProtoId const &sub = subm->GetProtoId();
326
327 auto subPDA = panda_file::ProtoDataAccessor(sub.GetPandaFile(), sub.GetEntityId());
328 auto superPDA = panda_file::ProtoDataAccessor(super.GetPandaFile(), super.GetEntityId());
329 if (superPDA.GetNumElements() != subPDA.GetNumElements()) {
330 return false;
331 }
332 if (superPDA.GetReturnType() != subPDA.GetReturnType()) {
333 return false;
334 }
335
336 uint32_t numArgs = subPDA.GetNumArgs();
337
338 for (uint32_t i = 0, refIdx = 0; i < numArgs; ++i) {
339 if (superPDA.GetArgType(i) != subPDA.GetArgType(i)) {
340 return false;
341 }
342 if (superPDA.GetArgType(i).IsReference()) {
343 auto subRef = linker->GetClass(*subm, subPDA.GetReferenceType(refIdx));
344 if (UNLIKELY(subRef == nullptr)) {
345 return false;
346 }
347 auto superRef = linker->GetClass(*superm, superPDA.GetReferenceType(refIdx));
348 if (UNLIKELY(superRef == nullptr)) {
349 return false;
350 }
351 if (!subRef->IsAssignableFrom(superRef)) {
352 return false;
353 }
354 refIdx++;
355 }
356 }
357 return true;
358 }
359
360 // CC-OFFNXT(C_RULE_ID_INLINE_FUNCTION_SIZE) Perf critical common runtime code stub
361 // CC-OFFNXT(G.FUD.06) perf critical
ResolveCompatibleVMethodInClass(EtsCoroutine * coro,const ark::Class * klass,Method * lookupTarget)362 ALWAYS_INLINE inline Method *ResolveCompatibleVMethodInClass(EtsCoroutine *coro, const ark::Class *klass,
363 Method *lookupTarget)
364 {
365 auto linker = Runtime::GetCurrent()->GetClassLinker();
366
367 // CC-OFFNXT(C_RULE_ID_POINTER_DECLARE_FOLLOW_NAME) project code style
368 // CC-OFFNXT(G.FMT.14-CPP,G.FMT.10-CPP) project code style
369 auto lookupFromIndex = [coro, linker, klass, lookupTarget](size_t from) -> Method * {
370 auto methods = klass->GetVTable();
371 for (size_t idx = from; idx < methods.size(); ++idx) {
372 auto vmethod = methods[idx];
373 if (LIKELY(lookupTarget->GetName() != vmethod->GetName())) {
374 continue;
375 }
376 if (MethodIsSupertypeOf(linker, vmethod, lookupTarget)) {
377 ASSERT(vmethod->GetVTableIndex() == idx);
378 return vmethod;
379 }
380 if (UNLIKELY(coro->HasPendingException())) {
381 return nullptr;
382 }
383 }
384 return nullptr;
385 };
386
387 size_t vtStart = 0;
388
389 Method *matched = lookupFromIndex(vtStart);
390 if (matched == nullptr) {
391 // not found
392 return nullptr;
393 }
394 if (lookupFromIndex(matched->GetVTableIndex() + 1) != nullptr) {
395 // inconsistent
396 return nullptr;
397 }
398 return matched;
399 }
400
ResolveCompatibleVMethod(EtsCoroutine * coro,ark::Class * klass,Method * lookupTarget)401 ALWAYS_INLINE inline Method *ResolveCompatibleVMethod(EtsCoroutine *coro, ark::Class *klass, Method *lookupTarget)
402 {
403 for (Class *t = klass; t != nullptr; t = t->GetBase()) {
404 Method *resolved = ResolveCompatibleVMethodInClass(coro, t, lookupTarget);
405 if (resolved != nullptr) {
406 return resolved;
407 }
408 }
409 return nullptr;
410 }
411
GetMethodByName(EtsCoroutine * coro,ETSStubCacheInfo const & cache,Method * rawMethod,ark::Class * klass)412 ALWAYS_INLINE inline Method *GetMethodByName(EtsCoroutine *coro, ETSStubCacheInfo const &cache, Method *rawMethod,
413 ark::Class *klass)
414 {
415 Method *callee = GetMethodFromCache(cache, klass);
416 if (callee != nullptr) {
417 return callee;
418 }
419 callee = ResolveCompatibleVMethod(coro, klass, rawMethod);
420 if (callee != nullptr) {
421 cache.UpdateItem(callee);
422 return callee;
423 }
424 return callee;
425 }
426
427 } // namespace ark::ets
428
429 #endif // PANDA_PLUGINS_ETS_RUNTIME_STUBS_INL_H
430