• 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_FFI_CLASSES_ETS_CLASS_H_
17 #define PANDA_PLUGINS_ETS_RUNTIME_FFI_CLASSES_ETS_CLASS_H_
18 
19 #include <cstdint>
20 #include "include/mem/panda_containers.h"
21 #include "include/object_header.h"
22 #include "include/runtime.h"
23 #include "libpandabase/mem/object_pointer.h"
24 #include "runtime/class_linker_context.h"
25 #include "runtime/include/class-inl.h"
26 #include "runtime/include/thread.h"
27 #include "runtime/include/class_linker.h"
28 #include "plugins/ets/runtime/types/ets_field.h"
29 #include "plugins/ets/runtime/types/ets_type.h"
30 #include "plugins/ets/runtime/ets_panda_file_items.h"
31 #include "plugins/ets/runtime/types/ets_method_signature.h"
32 #include "utils/utf.h"
33 
34 namespace ark::ets {
35 
36 enum class AccessLevel { PUBLIC, PROTECTED, DEFAULT, PRIVATE };
37 
38 class EtsMethod;
39 class EtsObject;
40 class EtsString;
41 class EtsArray;
42 class EtsPromise;
43 class EtsJob;
44 class EtsErrorOptions;
45 class EtsTypeAPIField;
46 class EtsTypeAPIMethod;
47 class EtsTypeAPIParameter;
48 
49 enum class EtsType;
50 
51 namespace test {
52 class EtsClassTest;
53 }  // namespace test
54 
55 class EtsClass {
56 public:
57     // We shouldn't init header_ here - because it has been memset(0) in object allocation,
58     // otherwise it may cause data race while visiting object's class concurrently in gc.
InitClass(const uint8_t * descriptor,uint32_t vtableSize,uint32_t imtSize,uint32_t klassSize)59     void InitClass(const uint8_t *descriptor, uint32_t vtableSize, uint32_t imtSize, uint32_t klassSize)
60     {
61         new (&klass_) ark::Class(descriptor, panda_file::SourceLang::ETS, vtableSize, imtSize, klassSize);
62     }
63 
GetDescriptor()64     const char *GetDescriptor() const
65     {
66         return utf::Mutf8AsCString(GetRuntimeClass()->GetDescriptor());
67     }
68 
69     PANDA_PUBLIC_API EtsClass *GetBase();
70 
71     uint32_t GetFieldsNumber();
72 
73     uint32_t GetOwnFieldsNumber();  // Without inherited fields
74 
GetStaticFieldsNumber()75     uint32_t GetStaticFieldsNumber()
76     {
77         return GetRuntimeClass()->GetStaticFields().size();
78     }
79 
GetInstanceFieldsNumber()80     uint32_t GetInstanceFieldsNumber()
81     {
82         return GetRuntimeClass()->GetInstanceFields().size();
83     }
84 
85     uint32_t GetFieldIndexByName(const char *name);
86 
87     PANDA_PUBLIC_API PandaVector<EtsField *> GetFields();
88 
89     EtsField *GetFieldByIndex(uint32_t i);
90 
91     EtsField *GetFieldIDByOffset(uint32_t fieldOffset);
92     PANDA_PUBLIC_API EtsField *GetFieldIDByName(const char *name, const char *sig = nullptr);
93 
94     EtsField *GetOwnFieldByIndex(uint32_t i);
95     EtsField *GetDeclaredFieldIDByName(const char *name);
96 
97     PANDA_PUBLIC_API EtsField *GetStaticFieldIDByName(const char *name, const char *sig = nullptr);
98     EtsField *GetStaticFieldIDByOffset(uint32_t fieldOffset);
99 
100     PANDA_PUBLIC_API EtsMethod *GetDirectMethod(const char *name);
101     PANDA_PUBLIC_API EtsMethod *GetDirectMethod(const uint8_t *name, const char *signature);
102     PANDA_PUBLIC_API EtsMethod *GetDirectMethod(const char *name, const char *signature);
103 
104     PANDA_PUBLIC_API EtsMethod *GetStaticMethod(const char *name, const char *signature, bool isANIFormat = false) const
105     {
106         if (signature == nullptr) {
107             return GetMethodInternal<FindFilter::STATIC>(name);
108         }
109         return GetMethodInternal<FindFilter::STATIC>(name, signature, isANIFormat);
110     }
111 
112     PANDA_PUBLIC_API EtsMethod *GetInstanceMethod(const char *name, const char *signature,
113                                                   bool isANIFormat = false) const
114     {
115         if (signature == nullptr) {
116             return GetMethodInternal<FindFilter::INSTANCE>(name);
117         }
118         return GetMethodInternal<FindFilter::INSTANCE>(name, signature, isANIFormat);
119     }
120 
GetDirectMethod(const PandaString & name,const PandaString & signature)121     PANDA_PUBLIC_API EtsMethod *GetDirectMethod(const PandaString &name, const PandaString &signature)
122     {
123         return GetDirectMethod(name.c_str(), signature.c_str());
124     }
125 
126     PANDA_PUBLIC_API EtsMethod *GetDirectMethod(const char *name, const Method::Proto &proto) const;
127 
128     EtsMethod *GetMethodByIndex(uint32_t i);
129 
130     uint32_t GetMethodsNum();
131 
132     PandaVector<EtsMethod *> GetMethods();
133 
134     PANDA_PUBLIC_API PandaVector<EtsMethod *> GetConstructors();
135 
136     template <class T>
GetStaticFieldPrimitive(EtsField * field)137     T GetStaticFieldPrimitive(EtsField *field)
138     {
139         return GetRuntimeClass()->GetFieldPrimitive<T>(*field->GetRuntimeField());
140     }
141 
142     template <class T>
GetStaticFieldPrimitive(int32_t fieldOffset,bool isVolatile)143     T GetStaticFieldPrimitive(int32_t fieldOffset, bool isVolatile)
144     {
145         if (isVolatile) {
146             return GetRuntimeClass()->GetFieldPrimitive<T, true>(fieldOffset);
147         }
148         return GetRuntimeClass()->GetFieldPrimitive<T, false>(fieldOffset);
149     }
150 
151     template <class T>
SetStaticFieldPrimitive(EtsField * field,T value)152     void SetStaticFieldPrimitive(EtsField *field, T value)
153     {
154         GetRuntimeClass()->SetFieldPrimitive<T>(*field->GetRuntimeField(), value);
155     }
156 
157     template <class T>
SetStaticFieldPrimitive(int32_t fieldOffset,bool isVolatile,T value)158     void SetStaticFieldPrimitive(int32_t fieldOffset, bool isVolatile, T value)
159     {
160         if (isVolatile) {
161             GetRuntimeClass()->SetFieldPrimitive<T, true>(fieldOffset, value);
162         }
163         GetRuntimeClass()->SetFieldPrimitive<T, false>(fieldOffset, value);
164     }
165 
166     PANDA_PUBLIC_API EtsObject *GetStaticFieldObject(EtsField *field);
167     EtsObject *GetStaticFieldObject(int32_t fieldOffset, bool isVolatile);
168 
169     void SetStaticFieldObject(EtsField *field, EtsObject *value);
170     void SetStaticFieldObject(int32_t fieldOffset, bool isVolatile, EtsObject *value);
171 
172     EtsObject *CreateInstance();
173 
IsEtsObject()174     bool IsEtsObject()
175     {
176         return GetRuntimeClass()->IsObjectClass();
177     }
178 
IsPrimitive()179     bool IsPrimitive() const
180     {
181         return GetRuntimeClass()->IsPrimitive();
182     }
183 
IsAbstract()184     bool IsAbstract()
185     {
186         return GetRuntimeClass()->IsAbstract();
187     }
188 
IsPublic()189     bool IsPublic() const
190     {
191         return GetRuntimeClass()->IsPublic();
192     }
193 
IsFinal()194     bool IsFinal() const
195     {
196         return GetRuntimeClass()->IsFinal();
197     }
198 
IsAnnotation()199     bool IsAnnotation() const
200     {
201         return GetRuntimeClass()->IsAnnotation();
202     }
203 
IsEnum()204     bool IsEnum() const
205     {
206         return GetRuntimeClass()->IsEnum();
207     }
208 
IsStringClass()209     bool IsStringClass() const
210     {
211         return GetRuntimeClass()->IsStringClass();
212     }
213 
IsClassClass()214     bool IsClassClass() const
215     {
216         return GetRuntimeClass()->IsClassClass();
217     }
218 
IsArrayClass()219     bool IsArrayClass() const
220     {
221         return GetRuntimeClass()->IsArrayClass();
222     }
223 
IsInterface()224     bool IsInterface() const
225     {
226         return GetRuntimeClass()->IsInterface();
227     }
228 
IsClass()229     bool IsClass() const
230     {
231         return GetRuntimeClass()->IsClass();
232     }
233 
234     static bool IsInSamePackage(std::string_view className1, std::string_view className2);
235 
236     bool IsInSamePackage(EtsClass *that);
237 
GetComponentSize()238     uint32_t GetComponentSize() const
239     {
240         return GetRuntimeClass()->GetComponentSize();
241     }
242 
GetType()243     panda_file::Type GetType() const
244     {
245         return GetRuntimeClass()->GetType();
246     }
247 
IsSubClass(EtsClass * cls)248     bool IsSubClass(EtsClass *cls)
249     {
250         return GetRuntimeClass()->IsSubClassOf(cls->GetRuntimeClass());
251     }
252 
IsAssignableFrom(EtsClass * clazz)253     bool IsAssignableFrom(EtsClass *clazz) const
254     {
255         return GetRuntimeClass()->IsAssignableFrom(clazz->GetRuntimeClass());
256     }
257 
IsGenerated()258     bool IsGenerated() const
259     {
260         return IsArrayClass() || IsPrimitive();
261     }
262 
CanAccess(EtsClass * that)263     bool CanAccess(EtsClass *that)
264     {
265         return that->IsPublic() || IsInSamePackage(that);
266     }
267 
268     EtsMethod *ResolveVirtualMethod(const EtsMethod *method) const;
269 
270     template <class Callback>
EnumerateMethods(const Callback & callback)271     void EnumerateMethods(const Callback &callback)
272     {
273         for (auto &method : GetRuntimeClass()->GetMethods()) {
274             bool finished = callback(reinterpret_cast<EtsMethod *>(&method));
275             if (finished) {
276                 break;
277             }
278         }
279     }
280 
281     template <class Callback>
EnumerateDirectInterfaces(const Callback & callback)282     void EnumerateDirectInterfaces(const Callback &callback)
283     {
284         for (Class *runtimeInterface : GetRuntimeClass()->GetInterfaces()) {
285             EtsClass *interface = EtsClass::FromRuntimeClass(runtimeInterface);
286             bool finished = callback(interface);
287             if (finished) {
288                 break;
289             }
290         }
291     }
292 
293     void GetInterfaces(PandaUnorderedSet<EtsClass *> &ifaces, EtsClass *iface);
294 
295     template <class Callback>
EnumerateInterfaces(const Callback & callback)296     void EnumerateInterfaces(const Callback &callback)
297     {
298         PandaUnorderedSet<EtsClass *> ifaces;
299         GetInterfaces(ifaces, this);
300         for (auto iface : ifaces) {
301             bool finished = callback(iface);
302             if (finished) {
303                 break;
304             }
305         }
306     }
307 
308     template <class Callback>
EnumerateBaseClasses(const Callback & callback)309     void EnumerateBaseClasses(const Callback &callback)
310     {
311         PandaVector<EtsClass *> inherChain;
312         auto curClass = this;
313         while (curClass != nullptr) {
314             inherChain.push_back(curClass);
315             curClass = curClass->GetBase();
316         }
317         for (auto i = inherChain.rbegin(); i != inherChain.rend(); i++) {
318             bool finished = callback(*i);
319             if (finished) {
320                 break;
321             }
322         }
323     }
324 
GetLoadContext()325     ClassLinkerContext *GetLoadContext() const
326     {
327         return GetRuntimeClass()->GetLoadContext();
328     }
329 
GetRuntimeClass()330     Class *GetRuntimeClass()
331     {
332         return &klass_;
333     }
334 
GetRuntimeClass()335     const Class *GetRuntimeClass() const
336     {
337         return &klass_;
338     }
339 
GetTypeMetaData()340     EtsLong GetTypeMetaData() const
341     {
342         return typeMetaData_;
343     }
344 
SetTypeMetaData(EtsLong metaData)345     void SetTypeMetaData(EtsLong metaData)
346     {
347         typeMetaData_ = metaData;
348     }
349 
AsObject()350     EtsObject *AsObject()
351     {
352         return reinterpret_cast<EtsObject *>(this);
353     }
354 
AsObject()355     const EtsObject *AsObject() const
356     {
357         return reinterpret_cast<const EtsObject *>(this);
358     }
359 
IsInitialized()360     bool IsInitialized() const
361     {
362         return GetRuntimeClass()->IsInitialized();
363     }
364 
365     EtsType GetEtsType();
366 
GetSize(uint32_t klassSize)367     static size_t GetSize(uint32_t klassSize)
368     {
369         return GetRuntimeClassOffset() + klassSize;
370     }
371 
GetRuntimeClassOffset()372     static constexpr size_t GetRuntimeClassOffset()
373     {
374         return MEMBER_OFFSET(EtsClass, klass_);
375     }
376 
GetTypeMetaDataOffset()377     static constexpr size_t GetTypeMetaDataOffset()
378     {
379         return MEMBER_OFFSET(EtsClass, typeMetaData_);
380     }
381 
GetHeaderOffset()382     static constexpr size_t GetHeaderOffset()
383     {
384         return MEMBER_OFFSET(EtsClass, header_);
385     }
386 
FromRuntimeClass(const Class * cls)387     static EtsClass *FromRuntimeClass(const Class *cls)
388     {
389         ASSERT(cls != nullptr);
390         return reinterpret_cast<EtsClass *>(reinterpret_cast<uintptr_t>(cls) - GetRuntimeClassOffset());
391     }
392 
FromClassObject(ObjectHeader * obj)393     static EtsClass *FromClassObject(ObjectHeader *obj)
394     {
395         ASSERT(obj->ClassAddr<Class>()->IsClassClass());
396         return reinterpret_cast<EtsClass *>(obj);
397     }
398 
FromEtsClassObject(EtsObject * obj)399     static EtsClass *FromEtsClassObject(EtsObject *obj)
400     {
401         return FromClassObject(reinterpret_cast<ObjectHeader *>(obj));
402     }
403 
404     static EtsClass *GetPrimitiveClass(EtsString *primitiveName);
405 
406     void Initialize(EtsClass *superClass, uint16_t accessFlags, bool isPrimitiveType,
407                     ClassLinkerErrorHandler *errorHandler);
408 
409     void SetComponentType(EtsClass *componentType);
410 
411     EtsClass *GetComponentType() const;
412 
413     void SetName(EtsString *name);
414 
415     bool CompareAndSetName(EtsString *oldName, EtsString *newName);
416 
417     EtsString *GetName();
418     static EtsString *CreateEtsClassName([[maybe_unused]] const char *descriptor);
419 
SetSuperClass(EtsClass * superClass)420     void SetSuperClass(EtsClass *superClass)
421     {
422         auto obj = reinterpret_cast<ObjectHeader *>(superClass);
423         GetObjectHeader()->SetFieldObject(GetSuperClassOffset(), obj);
424     }
425 
GetSuperClass()426     EtsClass *GetSuperClass() const
427     {
428         return reinterpret_cast<EtsClass *>(GetObjectHeader()->GetFieldObject(GetSuperClassOffset()));
429     }
430 
SetFlags(uint32_t flags)431     void SetFlags(uint32_t flags)
432     {
433         GetObjectHeader()->SetFieldPrimitive(GetFlagsOffset(), flags);
434     }
435 
GetFlags()436     uint32_t GetFlags() const
437     {
438         return GetObjectHeader()->GetFieldPrimitive<uint32_t>(GetFlagsOffset());
439     }
440 
441     void SetWeakReference();
442     void SetFinalizeReference();
443     void SetValueTyped();
444     void SetNullValue();
445     void SetBoxed();
446     void SetFunction();
447     void SetEtsEnum();
448     void SetBigInt();
449 
IsWeakReference()450     [[nodiscard]] bool IsWeakReference() const
451     {
452         return (flags_ & IS_WEAK_REFERENCE) != 0;
453     }
454 
IsFinalizerReference()455     [[nodiscard]] bool IsFinalizerReference() const
456     {
457         return (flags_ & IS_FINALIZE_REFERENCE) != 0;
458     }
459 
460     /// True if class inherited from Reference, false otherwise
IsReference()461     [[nodiscard]] bool IsReference() const
462     {
463         return (flags_ & IS_REFERENCE) != 0;
464     }
465 
IsValueTyped()466     [[nodiscard]] bool IsValueTyped() const
467     {
468         return (GetFlags() & IS_VALUE_TYPED) != 0;
469     }
470 
IsNullValue()471     [[nodiscard]] bool IsNullValue() const
472     {
473         return (GetFlags() & IS_NULLVALUE) != 0;
474     }
475 
IsBoxed()476     [[nodiscard]] bool IsBoxed() const
477     {
478         return (GetFlags() & IS_BOXED) != 0;
479     }
480 
IsFunction()481     [[nodiscard]] bool IsFunction() const
482     {
483         return (GetFlags() & IS_FUNCTION) != 0;
484     }
485 
IsFunctionReference()486     [[nodiscard]] bool IsFunctionReference() const
487     {
488         return (GetFlags() & IS_FUNCTION_REFERENCE) != 0;
489     }
490 
IsEtsEnum()491     [[nodiscard]] bool IsEtsEnum() const
492     {
493         return (GetFlags() & IS_ETS_ENUM) != 0;
494     }
495 
IsBigInt()496     [[nodiscard]] bool IsBigInt() const
497     {
498         return (GetFlags() & IS_BIGINT) != 0;
499     }
500 
IsModule()501     [[nodiscard]] bool IsModule() const
502     {
503         return (GetFlags() & IS_MODULE) != 0;
504     }
505 
506     EtsClass() = delete;
507     ~EtsClass() = delete;
508     NO_COPY_SEMANTIC(EtsClass);
509     NO_MOVE_SEMANTIC(EtsClass);
510 
GetNameOffset()511     static constexpr size_t GetNameOffset()
512     {
513         return MEMBER_OFFSET(EtsClass, name_);
514     }
515 
GetSuperClassOffset()516     static constexpr size_t GetSuperClassOffset()
517     {
518         return MEMBER_OFFSET(EtsClass, superClass_);
519     }
520 
GetFlagsOffset()521     static constexpr size_t GetFlagsOffset()
522     {
523         return MEMBER_OFFSET(EtsClass, flags_);
524     }
525 
GCRefFieldsOffset()526     static constexpr size_t GCRefFieldsOffset()
527     {
528         return GetHeaderOffset() + sizeof(header_);
529     }
530 
GCRefFieldsNum()531     static constexpr size_t GCRefFieldsNum()
532     {
533         return (GetFlagsOffset() - GCRefFieldsOffset()) / sizeof(ObjectPointerType);
534     }
535 
536 private:
537     enum class FindFilter { STATIC, INSTANCE };
538 
539     template <FindFilter FILTER>
GetMethodInternal(const char * name)540     EtsMethod *GetMethodInternal(const char *name) const
541     {
542         const auto *coreName = utf::CStringAsMutf8(name);
543 
544         Method *coreMethod = nullptr;
545         auto *runtimeClass = GetRuntimeClass();
546         if (IsInterface()) {
547             if constexpr (FILTER == FindFilter::STATIC) {
548                 coreMethod = runtimeClass->GetStaticInterfaceMethod(coreName);
549             } else {
550                 static_assert(FILTER == FindFilter::INSTANCE);
551                 coreMethod = runtimeClass->GetVirtualInterfaceMethod(coreName);
552             }
553         } else {
554             if constexpr (FILTER == FindFilter::STATIC) {
555                 coreMethod = runtimeClass->GetStaticClassMethod(coreName);
556             } else {
557                 static_assert(FILTER == FindFilter::INSTANCE);
558                 coreMethod = runtimeClass->GetVirtualClassMethod(coreName);
559             }
560         }
561         return reinterpret_cast<EtsMethod *>(coreMethod);
562     }
563 
564     template <FindFilter FILTER>
GetMethodInternal(const char * name,const char * signature,bool isANIFormat)565     EtsMethod *GetMethodInternal(const char *name, const char *signature, bool isANIFormat) const
566     {
567         EtsMethodSignature methodSignature(signature, isANIFormat);
568         if (!methodSignature.IsValid()) {
569             LOG(ERROR, ETS_NAPI) << "Wrong method signature:" << signature;
570             return nullptr;
571         }
572 
573         const auto *coreName = utf::CStringAsMutf8(name);
574 
575         Method *coreMethod = nullptr;
576         auto *runtimeClass = GetRuntimeClass();
577         if (IsInterface()) {
578             if constexpr (FILTER == FindFilter::STATIC) {
579                 coreMethod = runtimeClass->GetStaticInterfaceMethod(coreName, methodSignature.GetProto());
580             } else {
581                 static_assert(FILTER == FindFilter::INSTANCE);
582                 coreMethod = runtimeClass->GetVirtualInterfaceMethod(coreName, methodSignature.GetProto());
583             }
584         } else {
585             if constexpr (FILTER == FindFilter::STATIC) {
586                 coreMethod = runtimeClass->GetStaticClassMethod(coreName, methodSignature.GetProto());
587             } else {
588                 static_assert(FILTER == FindFilter::INSTANCE);
589                 coreMethod = runtimeClass->GetVirtualClassMethod(coreName, methodSignature.GetProto());
590             }
591         }
592         return reinterpret_cast<EtsMethod *>(coreMethod);
593     }
594 
GetObjectHeader()595     ObjectHeader *GetObjectHeader()
596     {
597         return &header_;
598     }
599 
GetObjectHeader()600     const ObjectHeader *GetObjectHeader() const
601     {
602         return &header_;
603     }
604 
605     constexpr static uint32_t ETS_ACC_PRIMITIVE = 1U << 16U;
606     /// Class is a WeakReference or successor of this class
607     constexpr static uint32_t IS_WEAK_REFERENCE = 1U << 17U;
608     /// Class is a FinalizerReference or successor of this class
609     constexpr static uint32_t IS_FINALIZE_REFERENCE = 1U << 18U;
610     constexpr static uint32_t IS_REFERENCE = IS_WEAK_REFERENCE | IS_FINALIZE_REFERENCE;
611 
612     // Class is a value-semantic type
613     constexpr static uint32_t IS_VALUE_TYPED = 1U << 19U;
614     // Class is an internal "nullvalue" class
615     constexpr static uint32_t IS_NULLVALUE = 1U << 20U;
616     // Class is a boxed type
617     constexpr static uint32_t IS_BOXED = 1U << 21U;
618     // Class is Function
619     constexpr static uint32_t IS_FUNCTION = 1U << 22U;
620     // Class is BigInt
621     constexpr static uint32_t IS_BIGINT = 1U << 23U;
622     // Class is Module
623     constexpr static uint32_t IS_MODULE = 1U << 24U;
624     // Class is enum
625     constexpr static uint32_t IS_ETS_ENUM = 1U << 25U;
626     // Class is Function Reference
627     constexpr static uint32_t IS_FUNCTION_REFERENCE = 1U << 26U;
628 
629     ark::ObjectHeader header_;  // EtsObject
630 
631     // ets.Class fields BEGIN
632     FIELD_UNUSED ObjectPointer<EtsString> name_;       // String
633     FIELD_UNUSED ObjectPointer<EtsClass> superClass_;  // Class<? super T>
634     FIELD_UNUSED uint32_t flags_;
635     FIELD_UNUSED EtsLong typeMetaData_;
636     // ets.Class fields END
637 
638     ark::Class klass_;
639 
640     friend class test::EtsClassTest;
641 };
642 
643 // Object header field must be first
644 static_assert(EtsClass::GetHeaderOffset() == 0);
645 
646 // Klass field has variable size so it must be last
647 static_assert(EtsClass::GetRuntimeClassOffset() + sizeof(ark::Class) == sizeof(EtsClass));
648 
649 }  // namespace ark::ets
650 
651 #endif  // PANDA_PLUGINS_ETS_RUNTIME_FFI_CLASSES_ETS_CLASS_H_
652