1 /**
2 * Copyright (c) 2021-2024 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 "include/language_context.h"
17 #include "include/mem/panda_containers.h"
18 #include "libpandabase/utils/utf.h"
19 #include "macros.h"
20 #include "napi/ets_napi.h"
21 #include "runtime/include/runtime.h"
22 #include "plugins/ets/runtime/types/ets_array.h"
23 #include "plugins/ets/runtime/types/ets_object.h"
24 #include "plugins/ets/runtime/types/ets_field.h"
25 #include "plugins/ets/runtime/types/ets_method.h"
26 #include "plugins/ets/runtime/types/ets_method_signature.h"
27 #include "plugins/ets/runtime/types/ets_string.h"
28 #include "plugins/ets/runtime/types/ets_value.h"
29 #include "plugins/ets/runtime/types/ets_class.h"
30
31 namespace ark::ets {
32
GetFieldsNumber()33 uint32_t EtsClass::GetFieldsNumber()
34 {
35 uint32_t fnumber = 0;
36 EnumerateBaseClasses([&](EtsClass *c) {
37 fnumber += c->GetRuntimeClass()->GetFields().Size();
38 return false;
39 });
40 return fnumber;
41 }
42
43 // Without inherited fields
GetOwnFieldsNumber()44 uint32_t EtsClass::GetOwnFieldsNumber()
45 {
46 return GetRuntimeClass()->GetFields().Size();
47 }
48
GetFields()49 PandaVector<EtsField *> EtsClass::GetFields()
50 {
51 auto etsFields = PandaVector<EtsField *>(Runtime::GetCurrent()->GetInternalAllocator()->Adapter());
52 EnumerateBaseClasses([&](EtsClass *c) {
53 auto fields = c->GetRuntimeClass()->GetFields();
54 auto fnum = fields.Size();
55 for (uint32_t i = 0; i < fnum; i++) {
56 etsFields.push_back(EtsField::FromRuntimeField(&fields[i]));
57 }
58 return false;
59 });
60 return etsFields;
61 }
62
GetFieldByIndex(uint32_t i)63 EtsField *EtsClass::GetFieldByIndex(uint32_t i)
64 {
65 EtsField *res = nullptr;
66 EnumerateBaseClasses([&](EtsClass *c) {
67 auto fields = c->GetRuntimeClass()->GetFields();
68 auto fnum = fields.Size();
69 if (i >= fnum) {
70 i -= fnum;
71 return false;
72 }
73 res = EtsField::FromRuntimeField(&fields[i]);
74 return true;
75 });
76 return res;
77 }
78
GetOwnFieldByIndex(uint32_t i)79 EtsField *EtsClass::GetOwnFieldByIndex(uint32_t i)
80 {
81 return EtsField::FromRuntimeField(&GetRuntimeClass()->GetFields()[i]);
82 }
83
GetDirectMethod(const char * name,const char * signature)84 EtsMethod *EtsClass::GetDirectMethod(const char *name, const char *signature)
85 {
86 auto coreName = reinterpret_cast<const uint8_t *>(name);
87 return GetDirectMethod(coreName, signature);
88 }
89
GetDirectMethod(const char * name)90 EtsMethod *EtsClass::GetDirectMethod(const char *name)
91 {
92 const uint8_t *mutf8Name = utf::CStringAsMutf8(name);
93 Method *rtMethod = GetRuntimeClass()->GetDirectMethod(mutf8Name);
94 return EtsMethod::FromRuntimeMethod(rtMethod);
95 }
96
GetDirectMethod(const uint8_t * name,const char * signature)97 EtsMethod *EtsClass::GetDirectMethod(const uint8_t *name, const char *signature)
98 {
99 EtsMethodSignature methodSignature(signature);
100 if (!methodSignature.IsValid()) {
101 LOG(ERROR, ETS_NAPI) << "Wrong method signature: " << signature;
102 return nullptr;
103 }
104
105 auto coreMethod = GetRuntimeClass()->GetDirectMethod(name, methodSignature.GetProto());
106 return reinterpret_cast<EtsMethod *>(coreMethod);
107 }
108
GetDirectMethod(const char * name,const Method::Proto & proto) const109 EtsMethod *EtsClass::GetDirectMethod(const char *name, const Method::Proto &proto) const
110 {
111 Method *method = klass_.GetDirectMethod(utf::CStringAsMutf8(name), proto);
112 return EtsMethod::FromRuntimeMethod(method);
113 }
114
GetMethodsNum()115 uint32_t EtsClass::GetMethodsNum()
116 {
117 return GetMethods().size();
118 }
119
GetMethodByIndex(uint32_t ind)120 EtsMethod *EtsClass::GetMethodByIndex(uint32_t ind)
121 {
122 EtsMethod *res = nullptr;
123 auto methods = GetMethods();
124 ASSERT(ind < methods.size());
125 res = methods[ind];
126 return res;
127 }
128
GetMethod(const char * name)129 EtsMethod *EtsClass::GetMethod(const char *name)
130 {
131 auto coreName = reinterpret_cast<const uint8_t *>(name);
132
133 Method *coreMethod = nullptr;
134 auto *runtimeClass = GetRuntimeClass();
135 if (IsInterface()) {
136 coreMethod = runtimeClass->GetInterfaceMethod(coreName);
137 } else {
138 coreMethod = runtimeClass->GetClassMethod(coreName);
139 }
140 return reinterpret_cast<EtsMethod *>(coreMethod);
141 }
142
GetMethod(const char * name,const char * signature)143 EtsMethod *EtsClass::GetMethod(const char *name, const char *signature)
144 {
145 EtsMethodSignature methodSignature(signature);
146 if (!methodSignature.IsValid()) {
147 LOG(ERROR, ETS_NAPI) << "Wrong method signature:" << signature;
148 return nullptr;
149 }
150
151 auto coreName = reinterpret_cast<const uint8_t *>(name);
152
153 Method *coreMethod = nullptr;
154 auto *runtimeClass = GetRuntimeClass();
155 if (IsInterface()) {
156 coreMethod = runtimeClass->GetInterfaceMethod(coreName, methodSignature.GetProto());
157 } else {
158 coreMethod = runtimeClass->GetClassMethod(coreName, methodSignature.GetProto());
159 }
160 return reinterpret_cast<EtsMethod *>(coreMethod);
161 }
162
163 // NOTE(kirill-mitkin): Cache in EtsClass field later
GetMethods()164 PandaVector<EtsMethod *> EtsClass::GetMethods()
165 {
166 PandaUnorderedMap<PandaString, EtsMethod *> uniqueMethods;
167
168 auto addDirectMethods = [&](const EtsClass *c) {
169 auto directMethods = c->GetRuntimeClass()->GetMethods();
170 for (auto &method : directMethods) {
171 PandaString methodFullName = utf::Mutf8AsCString(method.GetName().data);
172 methodFullName += method.GetProto().GetSignature();
173 if (uniqueMethods.find(methodFullName) == uniqueMethods.end()) {
174 uniqueMethods[methodFullName] = EtsMethod::FromRuntimeMethod(&method);
175 }
176 }
177 };
178
179 auto addDirectMethodsForBaseClass = [&uniqueMethods](EtsClass *c) {
180 auto directMethods = c->GetRuntimeClass()->GetMethods();
181 auto fnum = directMethods.Size();
182 for (uint32_t i = 0; i < fnum; i++) {
183 Method *method = &directMethods[i];
184 // Skip constructors
185 if (method->IsConstructor()) {
186 continue;
187 }
188
189 PandaString methodFullName = utf::Mutf8AsCString((method->GetName().data));
190 methodFullName += method->GetProto().GetSignature();
191
192 uniqueMethods[methodFullName] = EtsMethod::FromRuntimeMethod(method);
193 }
194 };
195
196 if (IsInterface()) {
197 addDirectMethods(this);
198 EnumerateInterfaces([&](const EtsClass *c) {
199 addDirectMethods(c);
200 return false;
201 });
202 } else {
203 EnumerateBaseClasses([&](EtsClass *c) {
204 addDirectMethodsForBaseClass(c);
205 return false;
206 });
207 }
208 auto etsMethods = PandaVector<EtsMethod *>();
209 for (auto &iter : uniqueMethods) {
210 etsMethods.push_back(iter.second);
211 }
212 return etsMethods;
213 }
214
GetConstructors()215 PandaVector<EtsMethod *> EtsClass::GetConstructors()
216 {
217 auto constructors = PandaVector<EtsMethod *>();
218 auto methods = GetRuntimeClass()->GetMethods();
219 // NOTE(kirill-mitkin): cache in ets_class field
220 for (auto &method : methods) {
221 // Skip constructors
222 if (method.IsInstanceConstructor()) {
223 constructors.emplace_back(EtsMethod::FromRuntimeMethod(&method));
224 }
225 }
226 return constructors;
227 }
228
ResolveVirtualMethod(const EtsMethod * method) const229 EtsMethod *EtsClass::ResolveVirtualMethod(const EtsMethod *method) const
230 {
231 return reinterpret_cast<EtsMethod *>(GetRuntimeClass()->ResolveVirtualMethod(method->GetPandaMethod()));
232 }
233
GetNameAndClassRoot(char hash)234 static std::pair<char const *, EtsClassRoot> GetNameAndClassRoot(char hash)
235 {
236 const char *primitiveName = nullptr;
237 EtsClassRoot classRoot;
238
239 switch (hash) {
240 case 'v':
241 primitiveName = "void";
242 classRoot = EtsClassRoot::VOID;
243 break;
244 case 'b':
245 primitiveName = "boolean";
246 classRoot = EtsClassRoot::BOOLEAN;
247 break;
248 case 'B':
249 primitiveName = "byte";
250 classRoot = EtsClassRoot::BYTE;
251 break;
252 case 'c':
253 primitiveName = "char";
254 classRoot = EtsClassRoot::CHAR;
255 break;
256 case 's':
257 primitiveName = "short";
258 classRoot = EtsClassRoot::SHORT;
259 break;
260 case 'i':
261 primitiveName = "int";
262 classRoot = EtsClassRoot::INT;
263 break;
264 case 'l':
265 primitiveName = "long";
266 classRoot = EtsClassRoot::LONG;
267 break;
268 case 'f':
269 primitiveName = "float";
270 classRoot = EtsClassRoot::FLOAT;
271 break;
272 case 'd':
273 primitiveName = "double";
274 classRoot = EtsClassRoot::DOUBLE;
275 break;
276 default:
277 break;
278 }
279
280 return {primitiveName, classRoot};
281 }
282
283 /* static */
GetPrimitiveClass(EtsString * name)284 EtsClass *EtsClass::GetPrimitiveClass(EtsString *name)
285 {
286 if (name == nullptr || name->GetMUtf8Length() < 2) { // MUtf8Length must be >= 2
287 return nullptr;
288 }
289 // StringIndexOutOfBoundsException is not thrown by At method, because index (0, 1) < length (>= 2)
290 // SUPPRESS_CSA_NEXTLINE(alpha.core.WasteObjHeader)
291 char hash = name->At(0) ^ ((name->At(1) & 0x10) << 1); // NOLINT(hicpp-signed-bitwise, readability-magic-numbers)
292 auto [primitiveName, classRoot] = GetNameAndClassRoot(hash);
293
294 if (primitiveName != nullptr && name->IsEqual(primitiveName)) { // SUPPRESS_CSA(alpha.core.WasteObjHeader)
295 return PandaEtsVM::GetCurrent()->GetClassLinker()->GetClassRoot(classRoot);
296 }
297
298 return nullptr;
299 }
300
CreateEtsClassName(const char * descriptor)301 EtsString *EtsClass::CreateEtsClassName([[maybe_unused]] const char *descriptor)
302 {
303 ASSERT_HAVE_ACCESS_TO_MANAGED_OBJECTS();
304
305 if (*descriptor == 'L') {
306 std::string_view tmpName(descriptor);
307 tmpName.remove_prefix(1);
308 tmpName.remove_suffix(1);
309 PandaString etsName(tmpName);
310 std::replace(etsName.begin(), etsName.end(), '/', '.');
311 return EtsString::CreateFromMUtf8(etsName.data(), etsName.length());
312 }
313 if (*descriptor == '[') {
314 PandaString etsName(descriptor);
315 std::replace(etsName.begin(), etsName.end(), '/', '.');
316 return EtsString::CreateFromMUtf8(etsName.data(), etsName.length());
317 }
318
319 switch (*descriptor) {
320 case 'Z':
321 return EtsString::CreateFromMUtf8("boolean");
322 case 'B':
323 return EtsString::CreateFromMUtf8("byte");
324 case 'C':
325 return EtsString::CreateFromMUtf8("char");
326 case 'S':
327 return EtsString::CreateFromMUtf8("short");
328 case 'I':
329 return EtsString::CreateFromMUtf8("int");
330 case 'J':
331 return EtsString::CreateFromMUtf8("long");
332 case 'F':
333 return EtsString::CreateFromMUtf8("float");
334 case 'D':
335 return EtsString::CreateFromMUtf8("double");
336 case 'V':
337 return EtsString::CreateFromMUtf8("void");
338 default:
339 LOG(FATAL, RUNTIME) << "Incorrect primitive name" << descriptor;
340 UNREACHABLE();
341 }
342 }
343
GetName()344 EtsString *EtsClass::GetName()
345 {
346 ASSERT_HAVE_ACCESS_TO_MANAGED_OBJECTS();
347
348 EtsString *name = nullptr;
349 bool success = false;
350
351 do {
352 name = reinterpret_cast<EtsString *>(GetObjectHeader()->GetFieldObject(GetNameOffset()));
353 if (name != nullptr) {
354 return name;
355 }
356
357 name = CreateEtsClassName(GetDescriptor());
358 if (name == nullptr) {
359 return nullptr;
360 }
361 success = CompareAndSetName(nullptr, name);
362 } while (!success);
363 return name;
364 }
365
IsInSamePackage(std::string_view className1,std::string_view className2)366 bool EtsClass::IsInSamePackage(std::string_view className1, std::string_view className2)
367 {
368 size_t i = 0;
369 size_t minLength = std::min(className1.size(), className2.size());
370 while (i < minLength && className1[i] == className2[i]) {
371 ++i;
372 }
373 return className1.find('/', i) == std::string::npos && className2.find('/', i) == std::string::npos;
374 }
375
IsInSamePackage(EtsClass * that)376 bool EtsClass::IsInSamePackage(EtsClass *that)
377 {
378 if (this == that) {
379 return true;
380 }
381
382 EtsClass *klass1 = this;
383 EtsClass *klass2 = that;
384 while (klass1->IsArrayClass()) {
385 klass1 = klass1->GetComponentType();
386 }
387 while (klass2->IsArrayClass()) {
388 klass2 = klass2->GetComponentType();
389 }
390 if (klass1 == klass2) {
391 return true;
392 }
393
394 // Compare the package part of the descriptor string.
395 return IsInSamePackage(klass1->GetDescriptor(), klass2->GetDescriptor());
396 }
397
SetWeakReference()398 void EtsClass::SetWeakReference()
399 {
400 flags_ = flags_ | IS_WEAK_REFERENCE;
401 ASSERT(IsWeakReference() && IsReference());
402 }
SetFinalizeReference()403 void EtsClass::SetFinalizeReference()
404 {
405 flags_ = flags_ | IS_FINALIZE_REFERENCE;
406 ASSERT(IsFinalizerReference() && IsReference());
407 }
408
SetValueTyped()409 void EtsClass::SetValueTyped()
410 {
411 flags_ = flags_ | IS_VALUE_TYPED;
412 ASSERT(IsValueTyped());
413 }
SetUndefined()414 void EtsClass::SetUndefined()
415 {
416 flags_ = flags_ | IS_UNDEFINED;
417 ASSERT(IsUndefined());
418 }
SetBoxed()419 void EtsClass::SetBoxed()
420 {
421 flags_ = flags_ | IS_BOXED;
422 ASSERT(IsBoxed());
423 }
SetFunction()424 void EtsClass::SetFunction()
425 {
426 flags_ = flags_ | IS_FUNCTION;
427 ASSERT(IsFunction());
428 }
SetBigInt()429 void EtsClass::SetBigInt()
430 {
431 flags_ = flags_ | IS_BIGINT;
432 ASSERT(IsBigInt());
433 }
434
HasFunctionTypeInSuperClasses(EtsClass * cls)435 static bool HasFunctionTypeInSuperClasses(EtsClass *cls)
436 {
437 if (EtsClass *base = cls->GetBase(); base != nullptr) {
438 if (UNLIKELY(base->IsFunction())) {
439 return true;
440 }
441 }
442 for (Class *iface : cls->GetRuntimeClass()->GetInterfaces()) {
443 if (UNLIKELY(EtsClass::FromRuntimeClass(iface)->IsFunction())) {
444 return true;
445 }
446 }
447 return false;
448 }
449
Initialize(EtsClass * superClass,uint16_t accessFlags,bool isPrimitiveType)450 void EtsClass::Initialize(EtsClass *superClass, uint16_t accessFlags, bool isPrimitiveType)
451 {
452 ASSERT_HAVE_ACCESS_TO_MANAGED_OBJECTS();
453
454 SetName(nullptr);
455 SetSuperClass(superClass);
456
457 uint32_t flags = accessFlags;
458 if (isPrimitiveType) {
459 flags |= ETS_ACC_PRIMITIVE;
460 }
461
462 if (superClass != nullptr) {
463 static constexpr uint32_t COPIED_MASK = IS_WEAK_REFERENCE | IS_FINALIZE_REFERENCE;
464 flags |= superClass->GetFlags() & COPIED_MASK;
465 ASSERT(!superClass->IsValueTyped());
466 }
467 if (UNLIKELY(HasFunctionTypeInSuperClasses(this))) {
468 flags |= IS_FUNCTION;
469 }
470 SetFlags(flags);
471 }
472
SetComponentType(EtsClass * componentType)473 void EtsClass::SetComponentType(EtsClass *componentType)
474 {
475 if (componentType == nullptr) {
476 GetRuntimeClass()->SetComponentType(nullptr);
477 return;
478 }
479 GetRuntimeClass()->SetComponentType(componentType->GetRuntimeClass());
480 }
481
GetComponentType() const482 EtsClass *EtsClass::GetComponentType() const
483 {
484 ark::Class *componentType = GetRuntimeClass()->GetComponentType();
485 if (componentType == nullptr) {
486 return nullptr;
487 }
488 return FromRuntimeClass(componentType);
489 }
490
SetName(EtsString * name)491 void EtsClass::SetName(EtsString *name)
492 {
493 GetObjectHeader()->SetFieldObject(GetNameOffset(), reinterpret_cast<ObjectHeader *>(name));
494 }
495
CompareAndSetName(EtsString * oldName,EtsString * newName)496 bool EtsClass::CompareAndSetName(EtsString *oldName, EtsString *newName)
497 {
498 return GetObjectHeader()->CompareAndSetFieldObject(GetNameOffset(), reinterpret_cast<ObjectHeader *>(oldName),
499 reinterpret_cast<ObjectHeader *>(newName),
500 std::memory_order::memory_order_seq_cst, true);
501 }
502
GetFieldIDByName(const char * name,const char * sig)503 EtsField *EtsClass::GetFieldIDByName(const char *name, const char *sig)
504 {
505 auto u8name = reinterpret_cast<const uint8_t *>(name);
506 auto *field = reinterpret_cast<EtsField *>(GetRuntimeClass()->GetInstanceFieldByName(u8name));
507
508 if (sig != nullptr && field != nullptr) {
509 if (strcmp(field->GetTypeDescriptor(), sig) != 0) {
510 return nullptr;
511 }
512 }
513
514 return field;
515 }
516
GetFieldIndexByName(const char * name)517 uint32_t EtsClass::GetFieldIndexByName(const char *name)
518 {
519 auto u8name = reinterpret_cast<const uint8_t *>(name);
520 auto fields = GetRuntimeClass()->GetFields();
521 panda_file::File::StringData sd = {static_cast<uint32_t>(ark::utf::MUtf8ToUtf16Size(u8name)), u8name};
522 for (uint32_t i = 0; i < GetFieldsNumber(); i++) {
523 if (fields[i].GetName() == sd) {
524 return i;
525 }
526 }
527 return -1;
528 }
529
GetStaticFieldIDByName(const char * name,const char * sig)530 EtsField *EtsClass::GetStaticFieldIDByName(const char *name, const char *sig)
531 {
532 auto u8name = reinterpret_cast<const uint8_t *>(name);
533 auto *field = reinterpret_cast<EtsField *>(GetRuntimeClass()->GetStaticFieldByName(u8name));
534
535 if (sig != nullptr && field != nullptr) {
536 if (strcmp(field->GetTypeDescriptor(), sig) != 0) {
537 return nullptr;
538 }
539 }
540
541 return field;
542 }
543
GetDeclaredFieldIDByName(const char * name)544 EtsField *EtsClass::GetDeclaredFieldIDByName(const char *name)
545 {
546 return reinterpret_cast<EtsField *>(GetRuntimeClass()->FindDeclaredField([name](const ark::Field &field) -> bool {
547 auto *efield = EtsField::FromRuntimeField(&field);
548 return ::strcmp(efield->GetName(), name) == 0;
549 }));
550 }
551
GetFieldIDByOffset(uint32_t fieldOffset)552 EtsField *EtsClass::GetFieldIDByOffset(uint32_t fieldOffset)
553 {
554 auto pred = [fieldOffset](const ark::Field &f) { return f.GetOffset() == fieldOffset; };
555 return reinterpret_cast<EtsField *>(GetRuntimeClass()->FindInstanceField(pred));
556 }
557
GetStaticFieldIDByOffset(uint32_t fieldOffset)558 EtsField *EtsClass::GetStaticFieldIDByOffset(uint32_t fieldOffset)
559 {
560 auto pred = [fieldOffset](const ark::Field &f) { return f.GetOffset() == fieldOffset; };
561 return reinterpret_cast<EtsField *>(GetRuntimeClass()->FindStaticField(pred));
562 }
563
GetBase()564 EtsClass *EtsClass::GetBase()
565 {
566 if (IsInterface()) {
567 return nullptr;
568 }
569 auto *base = GetRuntimeClass()->GetBase();
570 if (base == nullptr) {
571 return nullptr;
572 }
573 return FromRuntimeClass(base);
574 }
575
GetInterfaces(PandaUnorderedSet<EtsClass * > & ifaces,EtsClass * iface)576 void EtsClass::GetInterfaces(PandaUnorderedSet<EtsClass *> &ifaces, EtsClass *iface)
577 {
578 ifaces.insert(iface);
579 EnumerateDirectInterfaces([&](EtsClass *runtimeInterface) {
580 if (ifaces.find(runtimeInterface) == ifaces.end()) {
581 runtimeInterface->GetInterfaces(ifaces, runtimeInterface);
582 }
583 return false;
584 });
585 }
586
GetStaticFieldObject(EtsField * field)587 EtsObject *EtsClass::GetStaticFieldObject(EtsField *field)
588 {
589 return reinterpret_cast<EtsObject *>(GetRuntimeClass()->GetFieldObject(*field->GetRuntimeField()));
590 }
591
GetStaticFieldObject(int32_t fieldOffset,bool isVolatile)592 EtsObject *EtsClass::GetStaticFieldObject(int32_t fieldOffset, bool isVolatile)
593 {
594 if (isVolatile) {
595 return reinterpret_cast<EtsObject *>(GetRuntimeClass()->GetFieldObject<true>(fieldOffset));
596 }
597 return reinterpret_cast<EtsObject *>(GetRuntimeClass()->GetFieldObject<false>(fieldOffset));
598 }
599
SetStaticFieldObject(EtsField * field,EtsObject * value)600 void EtsClass::SetStaticFieldObject(EtsField *field, EtsObject *value)
601 {
602 GetRuntimeClass()->SetFieldObject(*field->GetRuntimeField(), reinterpret_cast<ObjectHeader *>(value));
603 }
604
SetStaticFieldObject(int32_t fieldOffset,bool isVolatile,EtsObject * value)605 void EtsClass::SetStaticFieldObject(int32_t fieldOffset, bool isVolatile, EtsObject *value)
606 {
607 if (isVolatile) {
608 GetRuntimeClass()->SetFieldObject<true>(fieldOffset, reinterpret_cast<ObjectHeader *>(value));
609 }
610 GetRuntimeClass()->SetFieldObject<false>(fieldOffset, reinterpret_cast<ObjectHeader *>(value));
611 }
612
613 } // namespace ark::ets
614