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 "runtime/regexp/ecmascript/regexp_executor.h"
17 #include "runtime/regexp/ecmascript/regexp_opcode.h"
18 #include "runtime/regexp/ecmascript/mem/dyn_chunk.h"
19 #include "utils/logger.h"
20 #include "securec.h"
21
22 namespace ark {
23 using RegExpState = RegExpExecutor::RegExpState;
Execute(const uint8_t * input,uint32_t lastIndex,uint32_t length,uint8_t * buf,bool isWideChar)24 bool RegExpExecutor::Execute(const uint8_t *input, uint32_t lastIndex, uint32_t length, uint8_t *buf, bool isWideChar)
25 {
26 DynChunk buffer(buf);
27 input_ = const_cast<uint8_t *>(input);
28 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
29 inputEnd_ = const_cast<uint8_t *>(input + length * (isWideChar ? WIDE_CHAR_SIZE : CHAR_SIZE));
30 uint32_t size = buffer.GetU32(0);
31 nCapture_ = buffer.GetU32(RegExpParser::NUM_CAPTURE__OFFSET);
32 nStack_ = buffer.GetU32(RegExpParser::NUM_STACK_OFFSET);
33 flags_ = buffer.GetU32(RegExpParser::FLAGS_OFFSET);
34 isWideChar_ = isWideChar;
35
36 uint32_t captureResultSize = sizeof(CaptureState) * nCapture_;
37 uint32_t stackSize = sizeof(uintptr_t) * nStack_;
38 stateSize_ = sizeof(RegExpState) + captureResultSize + stackSize;
39 stateStackLen_ = 0;
40
41 auto allocator = Runtime::GetCurrent()->GetInternalAllocator();
42
43 if (captureResultSize != 0) {
44 allocator->DeleteArray(captureResultList_);
45 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
46 captureResultList_ = allocator->New<CaptureState[]>(nCapture_);
47 if (memset_s(captureResultList_, captureResultSize, 0, captureResultSize) != EOK) {
48 LOG(FATAL, COMMON) << "memset_s failed";
49 UNREACHABLE();
50 }
51 }
52 if (stackSize != 0) {
53 allocator->DeleteArray(stack_);
54 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
55 stack_ = allocator->New<uintptr_t[]>(nStack_);
56 if (memset_s(stack_, stackSize, 0, stackSize) != EOK) {
57 LOG(FATAL, COMMON) << "memset_s failed";
58 UNREACHABLE();
59 }
60 }
61 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
62 SetCurrentPtr(input + lastIndex * (isWideChar ? WIDE_CHAR_SIZE : CHAR_SIZE));
63 SetCurrentPC(RegExpParser::OP_START_OFFSET);
64
65 // first split
66 if ((flags_ & RegExpParser::FLAG_STICKY) == 0) {
67 PushRegExpState(STATE_SPLIT, RegExpParser::OP_START_OFFSET);
68 }
69 return ExecuteInternal(buffer, size);
70 }
71
MatchFailed(bool isMatched)72 bool RegExpExecutor::MatchFailed(bool isMatched)
73 {
74 while (true) {
75 if (stateStackLen_ == 0) {
76 return true;
77 }
78 RegExpState *state = PeekRegExpState();
79 if (state->type == StateType::STATE_SPLIT) {
80 if (!isMatched) {
81 PopRegExpState();
82 return false;
83 }
84 } else {
85 isMatched = (state->type == StateType::STATE_MATCH_AHEAD && isMatched) ||
86 (state->type == StateType::STATE_NEGATIVE_MATCH_AHEAD && !isMatched);
87 if (!isMatched) {
88 DropRegExpState();
89 continue;
90 }
91 if (state->type == StateType::STATE_MATCH_AHEAD) {
92 PopRegExpState(false);
93 return false;
94 }
95 if (state->type == StateType::STATE_NEGATIVE_MATCH_AHEAD) {
96 PopRegExpState();
97 return false;
98 }
99 }
100 DropRegExpState();
101 }
102
103 return true;
104 }
105
106 // NOLINTNEXTLINE(readability-function-size)
ExecuteInternal(const DynChunk & byteCode,uint32_t pcEnd)107 bool RegExpExecutor::ExecuteInternal(const DynChunk &byteCode, uint32_t pcEnd)
108 {
109 while (GetCurrentPC() < pcEnd) {
110 // first split
111 if (!HandleFirstSplit()) {
112 return false;
113 }
114 uint8_t opCode = byteCode.GetU8(GetCurrentPC());
115 switch (opCode) {
116 case RegExpOpCode::OP_DOTS:
117 case RegExpOpCode::OP_ALL: {
118 if (!HandleOpAll(opCode)) {
119 return false;
120 }
121 break;
122 }
123 case RegExpOpCode::OP_CHAR32:
124 case RegExpOpCode::OP_CHAR: {
125 if (!HandleOpChar(byteCode, opCode)) {
126 return false;
127 }
128 break;
129 }
130 case RegExpOpCode::OP_NOT_WORD_BOUNDARY:
131 case RegExpOpCode::OP_WORD_BOUNDARY: {
132 if (!HandleOpWordBoundary(opCode)) {
133 return false;
134 }
135 break;
136 }
137 case RegExpOpCode::OP_LINE_START: {
138 if (!HandleOpLineStart(opCode)) {
139 return false;
140 }
141 break;
142 }
143 case RegExpOpCode::OP_LINE_END: {
144 if (!HandleOpLineEnd(opCode)) {
145 return false;
146 }
147 break;
148 }
149 case RegExpOpCode::OP_SAVE_START:
150 HandleOpSaveStart(byteCode, opCode);
151 break;
152 case RegExpOpCode::OP_SAVE_END:
153 HandleOpSaveEnd(byteCode, opCode);
154 break;
155 case RegExpOpCode::OP_GOTO: {
156 uint32_t offset = byteCode.GetU32(GetCurrentPC() + 1);
157 Advance(opCode, offset);
158 break;
159 }
160 case RegExpOpCode::OP_MATCH: {
161 // jump to match ahead
162 if (MatchFailed(true)) {
163 return false;
164 }
165 break;
166 }
167 case RegExpOpCode::OP_MATCH_END:
168 return true;
169 case RegExpOpCode::OP_SAVE_RESET:
170 HandleOpSaveReset(byteCode, opCode);
171 break;
172 case RegExpOpCode::OP_SPLIT_NEXT:
173 case RegExpOpCode::OP_MATCH_AHEAD:
174 case RegExpOpCode::OP_NEGATIVE_MATCH_AHEAD:
175 HandleOpMatch(byteCode, opCode);
176 break;
177 case RegExpOpCode::OP_SPLIT_FIRST:
178 HandleOpSplitFirst(byteCode, opCode);
179 break;
180 case RegExpOpCode::OP_PREV: {
181 if (!HandleOpPrev(opCode)) {
182 return false;
183 }
184 break;
185 }
186 case RegExpOpCode::OP_LOOP_GREEDY:
187 case RegExpOpCode::OP_LOOP:
188 HandleOpLoop(byteCode, opCode);
189 break;
190 case RegExpOpCode::OP_PUSH_CHAR: {
191 PushStack(reinterpret_cast<uintptr_t>(GetCurrentPtr()));
192 Advance(opCode);
193 break;
194 }
195 case RegExpOpCode::OP_CHECK_CHAR: {
196 if (PopStack() != reinterpret_cast<uintptr_t>(GetCurrentPtr())) {
197 Advance(opCode);
198 } else {
199 uint32_t offset = byteCode.GetU32(GetCurrentPC() + 1);
200 Advance(opCode, offset);
201 }
202 break;
203 }
204 case RegExpOpCode::OP_PUSH: {
205 PushStack(0);
206 Advance(opCode);
207 break;
208 }
209 case RegExpOpCode::OP_POP: {
210 PopStack();
211 Advance(opCode);
212 break;
213 }
214 case RegExpOpCode::OP_RANGE32: {
215 if (!HandleOpRange32(byteCode)) {
216 return false;
217 }
218 break;
219 }
220 case RegExpOpCode::OP_RANGE: {
221 if (!HandleOpRange(byteCode)) {
222 return false;
223 }
224 break;
225 }
226 case RegExpOpCode::OP_BACKREFERENCE:
227 case RegExpOpCode::OP_BACKWARD_BACKREFERENCE: {
228 if (!HandleOpBackReference(byteCode, opCode)) {
229 return false;
230 }
231 break;
232 }
233 default:
234 UNREACHABLE();
235 }
236 }
237 // for loop match
238 return true;
239 }
240
DumpResult(std::ostream & out) const241 void RegExpExecutor::DumpResult(std::ostream &out) const
242 {
243 out << "captures:" << std::endl;
244 for (uint32_t i = 0; i < nCapture_; i++) {
245 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
246 CaptureState *captureState = &captureResultList_[i];
247 int32_t len = captureState->captureEnd - captureState->captureStart;
248 if ((captureState->captureStart != nullptr && captureState->captureEnd != nullptr) && (len >= 0)) {
249 out << i << ":\t" << PandaString(reinterpret_cast<const char *>(captureState->captureStart), len)
250 << std::endl;
251 } else {
252 out << i << ":\t"
253 << "undefined" << std::endl;
254 }
255 }
256 }
257
PushRegExpState(StateType type,uint32_t pc)258 void RegExpExecutor::PushRegExpState(StateType type, uint32_t pc)
259 {
260 ReAllocStack(stateStackLen_ + 1);
261 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
262 auto state = reinterpret_cast<RegExpState *>(stateStack_ + stateStackLen_ * stateSize_);
263 state->type = type;
264 state->currentPc = pc;
265 state->currentStack = currentStack_;
266 state->currentPtr = GetCurrentPtr();
267 size_t listSize = sizeof(CaptureState) * nCapture_;
268 if (memcpy_s(state->captureResultList, listSize, GetCaptureResultList(), listSize) != EOK) {
269 LOG(FATAL, COMMON) << "memcpy_s failed";
270 UNREACHABLE();
271 }
272 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
273 uint8_t *stackStart = reinterpret_cast<uint8_t *>(state->captureResultList) + sizeof(CaptureState) * nCapture_;
274 if (stack_ != nullptr) {
275 size_t stackSize = sizeof(uintptr_t) * nStack_;
276 if (memcpy_s(stackStart, stackSize, stack_, stackSize) != EOK) {
277 LOG(FATAL, COMMON) << "memcpy_s failed";
278 UNREACHABLE();
279 }
280 }
281 stateStackLen_++;
282 }
283
PopRegExpState(bool copyCaptrue)284 RegExpState *RegExpExecutor::PopRegExpState(bool copyCaptrue)
285 {
286 if (stateStackLen_ != 0) {
287 auto state = PeekRegExpState();
288 size_t listSize = sizeof(CaptureState) * nCapture_;
289 if (copyCaptrue) {
290 if (memcpy_s(GetCaptureResultList(), listSize, state->captureResultList, listSize) != EOK) {
291 LOG(FATAL, COMMON) << "memcpy_s failed";
292 UNREACHABLE();
293 }
294 }
295 SetCurrentPtr(state->currentPtr);
296 SetCurrentPC(state->currentPc);
297 currentStack_ = state->currentStack;
298 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
299 uint8_t *stackStart = reinterpret_cast<uint8_t *>(state->captureResultList) + listSize;
300 if (stack_ != nullptr) {
301 size_t stackSize = sizeof(uintptr_t) * nStack_;
302 if (memcpy_s(stack_, stackSize, stackStart, stackSize) != EOK) {
303 LOG(FATAL, COMMON) << "memcpy_s failed";
304 UNREACHABLE();
305 }
306 }
307 stateStackLen_--;
308 return state;
309 }
310 return nullptr;
311 }
312
ReAllocStack(uint32_t stackLen)313 void RegExpExecutor::ReAllocStack(uint32_t stackLen)
314 {
315 auto allocator = Runtime::GetCurrent()->GetInternalAllocator();
316 if (stackLen > stateStackSize_) {
317 uint32_t newStackSize = std::max(stateStackSize_ * 2, MIN_STACK_SIZE); // 2: double the size
318 uint32_t stackByteSize = newStackSize * stateSize_;
319 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
320 auto newStack = allocator->New<uint8_t[]>(stackByteSize);
321 if (memset_s(newStack, stackByteSize, 0, stackByteSize) != EOK) {
322 LOG(FATAL, COMMON) << "memset_s failed";
323 UNREACHABLE();
324 }
325 if (stateStack_ != nullptr) {
326 size_t stackSize = stateStackSize_ * stateSize_;
327 if (memcpy_s(newStack, stackSize, stateStack_, stackSize) != EOK) {
328 return;
329 }
330 }
331 allocator->DeleteArray(stateStack_);
332 stateStack_ = newStack;
333 stateStackSize_ = newStackSize;
334 }
335 }
336
GetChar(const uint8_t ** pp,const uint8_t * end) const337 uint32_t RegExpExecutor::GetChar(const uint8_t **pp, const uint8_t *end) const
338 {
339 uint32_t c;
340 const uint8_t *cptr = *pp;
341 if (!isWideChar_) {
342 c = *cptr;
343 *pp += 1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
344 } else {
345 uint16_t c1 = *(reinterpret_cast<const uint16_t *>(cptr));
346 c = c1;
347 cptr += WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
348 if (U16_IS_LEAD(c) && IsUtf16() && cptr < end) {
349 c1 = *(reinterpret_cast<const uint16_t *>(cptr));
350 if (U16_IS_TRAIL(c1)) {
351 c = static_cast<uint32_t>(U16_GET_SUPPLEMENTARY(c, c1)); // NOLINT(hicpp-signed-bitwise)
352 cptr += WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
353 }
354 }
355 *pp = cptr;
356 }
357 return c;
358 }
359
PeekChar(const uint8_t * p,const uint8_t * end) const360 uint32_t RegExpExecutor::PeekChar(const uint8_t *p, const uint8_t *end) const
361 {
362 uint32_t c;
363 const uint8_t *cptr = p;
364 if (!isWideChar_) {
365 c = *cptr;
366 } else {
367 uint16_t c1 = *reinterpret_cast<const uint16_t *>(cptr);
368 c = c1;
369 cptr += WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
370 if (U16_IS_LEAD(c) && IsUtf16() && cptr < end) {
371 c1 = *reinterpret_cast<const uint16_t *>(cptr);
372 if (U16_IS_TRAIL(c1)) {
373 c = static_cast<uint32_t>(U16_GET_SUPPLEMENTARY(c, c1)); // NOLINT(hicpp-signed-bitwise)
374 }
375 }
376 }
377 return c;
378 }
379
AdvancePtr(const uint8_t ** pp,const uint8_t * end) const380 void RegExpExecutor::AdvancePtr(const uint8_t **pp, const uint8_t *end) const
381 {
382 const uint8_t *cptr = *pp;
383 if (!isWideChar_) {
384 *pp += 1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
385 } else {
386 uint16_t c1 = *reinterpret_cast<const uint16_t *>(cptr);
387 cptr += WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
388 if (U16_IS_LEAD(c1) && IsUtf16() && cptr < end) {
389 c1 = *reinterpret_cast<const uint16_t *>(cptr);
390 if (U16_IS_TRAIL(c1)) {
391 cptr += WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
392 }
393 }
394 *pp = cptr;
395 }
396 }
397
PeekPrevChar(const uint8_t * p,const uint8_t * start) const398 uint32_t RegExpExecutor::PeekPrevChar(const uint8_t *p, const uint8_t *start) const
399 {
400 uint32_t c;
401 const uint8_t *cptr = p;
402 if (!isWideChar_) {
403 c = cptr[-1]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
404 } else {
405 cptr -= WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
406 uint16_t c1 = *reinterpret_cast<const uint16_t *>(cptr);
407 c = c1;
408 if (U16_IS_TRAIL(c) && IsUtf16() && cptr > start) {
409 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
410 c1 = reinterpret_cast<const uint16_t *>(cptr)[-1];
411 if (U16_IS_LEAD(c1)) {
412 c = static_cast<uint32_t>(U16_GET_SUPPLEMENTARY(c1, c)); // NOLINT(hicpp-signed-bitwise)
413 }
414 }
415 }
416 return c;
417 }
418
GetPrevChar(const uint8_t ** pp,const uint8_t * start) const419 uint32_t RegExpExecutor::GetPrevChar(const uint8_t **pp, const uint8_t *start) const
420 {
421 uint32_t c;
422 const uint8_t *cptr = *pp;
423 if (!isWideChar_) {
424 c = cptr[-1]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
425 cptr -= 1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
426 *pp = cptr;
427 } else {
428 cptr -= WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
429 uint16_t c1 = *reinterpret_cast<const uint16_t *>(cptr);
430 c = c1;
431 if (U16_IS_TRAIL(c) && IsUtf16() && cptr > start) {
432 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
433 c1 = reinterpret_cast<const uint16_t *>(cptr)[-1];
434 if (U16_IS_LEAD(c1)) {
435 c = static_cast<uint32_t>(U16_GET_SUPPLEMENTARY(c1, c)); // NOLINT(hicpp-signed-bitwise)
436 cptr -= WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
437 }
438 }
439 *pp = cptr;
440 }
441 return c;
442 }
443
PrevPtr(const uint8_t ** pp,const uint8_t * start) const444 void RegExpExecutor::PrevPtr(const uint8_t **pp, const uint8_t *start) const
445 {
446 const uint8_t *cptr = *pp;
447 if (!isWideChar_) {
448 cptr -= 1; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
449 *pp = cptr;
450 } else {
451 cptr -= WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
452 uint16_t c1 = *reinterpret_cast<const uint16_t *>(cptr);
453 if (U16_IS_TRAIL(c1) && IsUtf16() && cptr > start) {
454 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
455 c1 = reinterpret_cast<const uint16_t *>(cptr)[-1];
456 if (U16_IS_LEAD(c1)) {
457 cptr -= WIDE_CHAR_SIZE; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
458 }
459 }
460 *pp = cptr;
461 }
462 }
463
HandleOpBackReferenceMatch(const uint8_t * captureStart,const uint8_t * captureEnd,uint8_t opCode)464 bool RegExpExecutor::HandleOpBackReferenceMatch(const uint8_t *captureStart, const uint8_t *captureEnd, uint8_t opCode)
465 {
466 const uint8_t *refCptr = captureStart;
467 bool isMatched = true;
468 while (refCptr < captureEnd) {
469 if (IsEOF()) {
470 isMatched = false;
471 break;
472 }
473 // NOLINTNEXTLINE(readability-identifier-naming)
474 uint32_t c1 = GetChar(&refCptr, captureEnd);
475 // NOLINTNEXTLINE(readability-identifier-naming)
476 uint32_t c2 = GetChar(¤tPtr_, inputEnd_);
477 if (IsIgnoreCase()) {
478 c1 = static_cast<uint32_t>(RegExpParser::Canonicalize(c1, IsUtf16()));
479 c2 = static_cast<uint32_t>(RegExpParser::Canonicalize(c2, IsUtf16()));
480 }
481 if (c1 != c2) {
482 isMatched = false;
483 break;
484 }
485 }
486 if (!isMatched) {
487 if (MatchFailed()) {
488 return false;
489 }
490 } else {
491 Advance(opCode);
492 }
493 return true;
494 }
495
HandleOpBackwardBackReferenceMatch(const uint8_t * captureStart,const uint8_t * captureEnd,uint8_t opCode)496 bool RegExpExecutor::HandleOpBackwardBackReferenceMatch(const uint8_t *captureStart, const uint8_t *captureEnd,
497 uint8_t opCode)
498 {
499 const uint8_t *refCptr = captureEnd;
500 bool isMatched = true;
501 while (refCptr > captureStart) {
502 if (GetCurrentPtr() == input_) {
503 isMatched = false;
504 break;
505 }
506 // NOLINTNEXTLINE(readability-identifier-naming)
507 uint32_t c1 = GetPrevChar(&refCptr, captureStart);
508 // NOLINTNEXTLINE(readability-identifier-naming)
509 uint32_t c2 = GetPrevChar(¤tPtr_, input_);
510 if (IsIgnoreCase()) {
511 c1 = static_cast<uint32_t>(RegExpParser::Canonicalize(c1, IsUtf16()));
512 c2 = static_cast<uint32_t>(RegExpParser::Canonicalize(c2, IsUtf16()));
513 }
514 if (c1 != c2) {
515 isMatched = false;
516 break;
517 }
518 }
519 if (!isMatched) {
520 if (MatchFailed()) {
521 return false;
522 }
523 } else {
524 Advance(opCode);
525 }
526 return true;
527 }
528
HandleOpBackReference(const DynChunk & byteCode,uint8_t opCode)529 bool RegExpExecutor::HandleOpBackReference(const DynChunk &byteCode, uint8_t opCode)
530 {
531 uint32_t captureIndex = byteCode.GetU8(GetCurrentPC() + 1);
532 if (captureIndex >= nCapture_) {
533 return !MatchFailed();
534 }
535 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
536 const uint8_t *captureStart = captureResultList_[captureIndex].captureStart;
537 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
538 const uint8_t *captureEnd = captureResultList_[captureIndex].captureEnd;
539 if (captureStart == nullptr || captureEnd == nullptr) {
540 Advance(opCode);
541 return true;
542 }
543
544 if (opCode == RegExpOpCode::OP_BACKREFERENCE) {
545 return HandleOpBackReferenceMatch(captureStart, captureEnd, opCode);
546 }
547 return HandleOpBackwardBackReferenceMatch(captureStart, captureEnd, opCode);
548 }
549
550 } // namespace ark
551