• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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