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