1// Copyright 2012 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/regexp/regexp-macro-assembler.h" 6 7#include "src/codegen/assembler.h" 8#include "src/codegen/label.h" 9#include "src/execution/isolate-inl.h" 10#include "src/execution/pointer-authentication.h" 11#include "src/execution/simulator.h" 12#include "src/regexp/regexp-stack.h" 13#include "src/regexp/special-case.h" 14#include "src/strings/unicode-inl.h" 15 16#ifdef V8_INTL_SUPPORT 17#include "unicode/uchar.h" 18#include "unicode/unistr.h" 19#endif // V8_INTL_SUPPORT 20 21namespace v8 { 22namespace internal { 23 24RegExpMacroAssembler::RegExpMacroAssembler(Isolate* isolate, Zone* zone) 25 : slow_safe_compiler_(false), 26 backtrack_limit_(JSRegExp::kNoBacktrackLimit), 27 global_mode_(NOT_GLOBAL), 28 isolate_(isolate), 29 zone_(zone) {} 30 31bool RegExpMacroAssembler::has_backtrack_limit() const { 32 return backtrack_limit_ != JSRegExp::kNoBacktrackLimit; 33} 34 35// static 36int RegExpMacroAssembler::CaseInsensitiveCompareNonUnicode(Address byte_offset1, 37 Address byte_offset2, 38 size_t byte_length, 39 Isolate* isolate) { 40#ifdef V8_INTL_SUPPORT 41 // This function is not allowed to cause a garbage collection. 42 // A GC might move the calling generated code and invalidate the 43 // return address on the stack. 44 DisallowGarbageCollection no_gc; 45 DCHECK_EQ(0, byte_length % 2); 46 size_t length = byte_length / 2; 47 base::uc16* substring1 = reinterpret_cast<base::uc16*>(byte_offset1); 48 base::uc16* substring2 = reinterpret_cast<base::uc16*>(byte_offset2); 49 50 for (size_t i = 0; i < length; i++) { 51 UChar32 c1 = RegExpCaseFolding::Canonicalize(substring1[i]); 52 UChar32 c2 = RegExpCaseFolding::Canonicalize(substring2[i]); 53 if (c1 != c2) { 54 return 0; 55 } 56 } 57 return 1; 58#else 59 return CaseInsensitiveCompareUnicode(byte_offset1, byte_offset2, byte_length, 60 isolate); 61#endif 62} 63 64// static 65int RegExpMacroAssembler::CaseInsensitiveCompareUnicode(Address byte_offset1, 66 Address byte_offset2, 67 size_t byte_length, 68 Isolate* isolate) { 69 // This function is not allowed to cause a garbage collection. 70 // A GC might move the calling generated code and invalidate the 71 // return address on the stack. 72 DisallowGarbageCollection no_gc; 73 DCHECK_EQ(0, byte_length % 2); 74 75#ifdef V8_INTL_SUPPORT 76 int32_t length = static_cast<int32_t>(byte_length >> 1); 77 icu::UnicodeString uni_str_1(reinterpret_cast<const char16_t*>(byte_offset1), 78 length); 79 return uni_str_1.caseCompare(reinterpret_cast<const char16_t*>(byte_offset2), 80 length, U_FOLD_CASE_DEFAULT) == 0; 81#else 82 base::uc16* substring1 = reinterpret_cast<base::uc16*>(byte_offset1); 83 base::uc16* substring2 = reinterpret_cast<base::uc16*>(byte_offset2); 84 size_t length = byte_length >> 1; 85 DCHECK_NOT_NULL(isolate); 86 unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize = 87 isolate->regexp_macro_assembler_canonicalize(); 88 for (size_t i = 0; i < length; i++) { 89 unibrow::uchar c1 = substring1[i]; 90 unibrow::uchar c2 = substring2[i]; 91 if (c1 != c2) { 92 unibrow::uchar s1[1] = {c1}; 93 canonicalize->get(c1, '\0', s1); 94 if (s1[0] != c2) { 95 unibrow::uchar s2[1] = {c2}; 96 canonicalize->get(c2, '\0', s2); 97 if (s1[0] != s2[0]) { 98 return 0; 99 } 100 } 101 } 102 } 103 return 1; 104#endif // V8_INTL_SUPPORT 105} 106 107namespace { 108 109uint32_t Hash(const ZoneList<CharacterRange>* ranges) { 110 size_t seed = 0; 111 for (int i = 0; i < ranges->length(); i++) { 112 const CharacterRange& r = ranges->at(i); 113 seed = base::hash_combine(seed, r.from(), r.to()); 114 } 115 return static_cast<uint32_t>(seed); 116} 117 118constexpr base::uc32 MaskEndOfRangeMarker(base::uc32 c) { 119 // CharacterRanges may use 0x10ffff as the end-of-range marker irrespective 120 // of whether the regexp IsUnicode or not; translate the marker value here. 121 DCHECK_IMPLIES(c > kMaxUInt16, c == String::kMaxCodePoint); 122 return c & 0xffff; 123} 124 125int RangeArrayLengthFor(const ZoneList<CharacterRange>* ranges) { 126 const int ranges_length = ranges->length(); 127 return MaskEndOfRangeMarker(ranges->at(ranges_length - 1).to()) == kMaxUInt16 128 ? ranges_length * 2 - 1 129 : ranges_length * 2; 130} 131 132bool Equals(const ZoneList<CharacterRange>* lhs, const Handle<ByteArray>& rhs) { 133 DCHECK_EQ(rhs->length() % kUInt16Size, 0); // uc16 elements. 134 const int rhs_length = rhs->length() / kUInt16Size; 135 if (rhs_length != RangeArrayLengthFor(lhs)) return false; 136 for (int i = 0; i < lhs->length(); i++) { 137 const CharacterRange& r = lhs->at(i); 138 if (rhs->get_uint16(i * 2 + 0) != r.from()) return false; 139 if (i * 2 + 1 == rhs_length) break; 140 if (rhs->get_uint16(i * 2 + 1) != r.to() + 1) return false; 141 } 142 return true; 143} 144 145Handle<ByteArray> MakeRangeArray(Isolate* isolate, 146 const ZoneList<CharacterRange>* ranges) { 147 const int ranges_length = ranges->length(); 148 const int byte_array_length = RangeArrayLengthFor(ranges); 149 const int size_in_bytes = byte_array_length * kUInt16Size; 150 Handle<ByteArray> range_array = 151 isolate->factory()->NewByteArray(size_in_bytes); 152 for (int i = 0; i < ranges_length; i++) { 153 const CharacterRange& r = ranges->at(i); 154 DCHECK_LE(r.from(), kMaxUInt16); 155 range_array->set_uint16(i * 2 + 0, r.from()); 156 const base::uc32 to = MaskEndOfRangeMarker(r.to()); 157 if (i == ranges_length - 1 && to == kMaxUInt16) { 158 DCHECK_EQ(byte_array_length, ranges_length * 2 - 1); 159 break; // Avoid overflow by leaving the last range open-ended. 160 } 161 DCHECK_LT(to, kMaxUInt16); 162 range_array->set_uint16(i * 2 + 1, to + 1); // Exclusive. 163 } 164 return range_array; 165} 166 167} // namespace 168 169Handle<ByteArray> NativeRegExpMacroAssembler::GetOrAddRangeArray( 170 const ZoneList<CharacterRange>* ranges) { 171 const uint32_t hash = Hash(ranges); 172 173 if (range_array_cache_.count(hash) != 0) { 174 Handle<ByteArray> range_array = range_array_cache_[hash]; 175 if (Equals(ranges, range_array)) return range_array; 176 } 177 178 Handle<ByteArray> range_array = MakeRangeArray(isolate(), ranges); 179 range_array_cache_[hash] = range_array; 180 return range_array; 181} 182 183// static 184uint32_t RegExpMacroAssembler::IsCharacterInRangeArray(uint32_t current_char, 185 Address raw_byte_array, 186 Isolate* isolate) { 187 // Use uint32_t to avoid complexity around bool return types (which may be 188 // optimized to use only the least significant byte). 189 static constexpr uint32_t kTrue = 1; 190 static constexpr uint32_t kFalse = 0; 191 192 ByteArray ranges = ByteArray::cast(Object(raw_byte_array)); 193 194 DCHECK_EQ(ranges.length() % kUInt16Size, 0); // uc16 elements. 195 const int length = ranges.length() / kUInt16Size; 196 DCHECK_GE(length, 1); 197 198 // Shortcut for fully out of range chars. 199 if (current_char < ranges.get_uint16(0)) return kFalse; 200 if (current_char >= ranges.get_uint16(length - 1)) { 201 // The last range may be open-ended. 202 return (length % 2) == 0 ? kFalse : kTrue; 203 } 204 205 // Binary search for the matching range. `ranges` is encoded as 206 // [from0, to0, from1, to1, ..., fromN, toN], or 207 // [from0, to0, from1, to1, ..., fromN] (open-ended last interval). 208 209 int mid, lower = 0, upper = length; 210 do { 211 mid = lower + (upper - lower) / 2; 212 const base::uc16 elem = ranges.get_uint16(mid); 213 if (current_char < elem) { 214 upper = mid; 215 } else if (current_char > elem) { 216 lower = mid + 1; 217 } else { 218 DCHECK_EQ(current_char, elem); 219 break; 220 } 221 } while (lower < upper); 222 223 const bool current_char_ge_last_elem = current_char >= ranges.get_uint16(mid); 224 const int current_range_start_index = 225 current_char_ge_last_elem ? mid : mid - 1; 226 227 // Ranges start at even indices and end at odd indices. 228 return (current_range_start_index % 2) == 0 ? kTrue : kFalse; 229} 230 231void RegExpMacroAssembler::CheckNotInSurrogatePair(int cp_offset, 232 Label* on_failure) { 233 Label ok; 234 // Check that current character is not a trail surrogate. 235 LoadCurrentCharacter(cp_offset, &ok); 236 CheckCharacterNotInRange(kTrailSurrogateStart, kTrailSurrogateEnd, &ok); 237 // Check that previous character is not a lead surrogate. 238 LoadCurrentCharacter(cp_offset - 1, &ok); 239 CheckCharacterInRange(kLeadSurrogateStart, kLeadSurrogateEnd, on_failure); 240 Bind(&ok); 241} 242 243void RegExpMacroAssembler::CheckPosition(int cp_offset, 244 Label* on_outside_input) { 245 LoadCurrentCharacter(cp_offset, on_outside_input, true); 246} 247 248void RegExpMacroAssembler::LoadCurrentCharacter(int cp_offset, 249 Label* on_end_of_input, 250 bool check_bounds, 251 int characters, 252 int eats_at_least) { 253 // By default, eats_at_least = characters. 254 if (eats_at_least == kUseCharactersValue) { 255 eats_at_least = characters; 256 } 257 258 LoadCurrentCharacterImpl(cp_offset, on_end_of_input, check_bounds, characters, 259 eats_at_least); 260} 261 262void NativeRegExpMacroAssembler::LoadCurrentCharacterImpl( 263 int cp_offset, Label* on_end_of_input, bool check_bounds, int characters, 264 int eats_at_least) { 265 // It's possible to preload a small number of characters when each success 266 // path requires a large number of characters, but not the reverse. 267 DCHECK_GE(eats_at_least, characters); 268 269 DCHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); 270 if (check_bounds) { 271 if (cp_offset >= 0) { 272 CheckPosition(cp_offset + eats_at_least - 1, on_end_of_input); 273 } else { 274 CheckPosition(cp_offset, on_end_of_input); 275 } 276 } 277 LoadCurrentCharacterUnchecked(cp_offset, characters); 278} 279 280bool NativeRegExpMacroAssembler::CanReadUnaligned() const { 281 return FLAG_enable_regexp_unaligned_accesses && !slow_safe(); 282} 283 284#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER 285 286// This method may only be called after an interrupt. 287// static 288int NativeRegExpMacroAssembler::CheckStackGuardState( 289 Isolate* isolate, int start_index, RegExp::CallOrigin call_origin, 290 Address* return_address, Code re_code, Address* subject, 291 const byte** input_start, const byte** input_end) { 292 DisallowGarbageCollection no_gc; 293 Address old_pc = PointerAuthentication::AuthenticatePC(return_address, 0); 294 DCHECK_LE(re_code.raw_instruction_start(), old_pc); 295 DCHECK_LE(old_pc, re_code.raw_instruction_end()); 296 297 StackLimitCheck check(isolate); 298 bool js_has_overflowed = check.JsHasOverflowed(); 299 300 if (call_origin == RegExp::CallOrigin::kFromJs) { 301 // Direct calls from JavaScript can be interrupted in two ways: 302 // 1. A real stack overflow, in which case we let the caller throw the 303 // exception. 304 // 2. The stack guard was used to interrupt execution for another purpose, 305 // forcing the call through the runtime system. 306 307 // Bug(v8:9540) Investigate why this method is called from JS although no 308 // stackoverflow or interrupt is pending on ARM64. We return 0 in this case 309 // to continue execution normally. 310 if (js_has_overflowed) { 311 return EXCEPTION; 312 } else if (check.InterruptRequested()) { 313 return RETRY; 314 } else { 315 return 0; 316 } 317 } 318 DCHECK(call_origin == RegExp::CallOrigin::kFromRuntime); 319 320 // Prepare for possible GC. 321 HandleScope handles(isolate); 322 Handle<Code> code_handle(re_code, isolate); 323 Handle<String> subject_handle(String::cast(Object(*subject)), isolate); 324 bool is_one_byte = String::IsOneByteRepresentationUnderneath(*subject_handle); 325 int return_value = 0; 326 327 { 328 DisableGCMole no_gc_mole; 329 if (js_has_overflowed) { 330 AllowGarbageCollection yes_gc; 331 isolate->StackOverflow(); 332 return_value = EXCEPTION; 333 } else if (check.InterruptRequested()) { 334 AllowGarbageCollection yes_gc; 335 Object result = isolate->stack_guard()->HandleInterrupts(); 336 if (result.IsException(isolate)) return_value = EXCEPTION; 337 } 338 339 if (*code_handle != re_code) { // Return address no longer valid 340 // Overwrite the return address on the stack. 341 intptr_t delta = code_handle->address() - re_code.address(); 342 Address new_pc = old_pc + delta; 343 // TODO(v8:10026): avoid replacing a signed pointer. 344 PointerAuthentication::ReplacePC(return_address, new_pc, 0); 345 } 346 } 347 348 // If we continue, we need to update the subject string addresses. 349 if (return_value == 0) { 350 // String encoding might have changed. 351 if (String::IsOneByteRepresentationUnderneath(*subject_handle) != 352 is_one_byte) { 353 // If we changed between an LATIN1 and an UC16 string, the specialized 354 // code cannot be used, and we need to restart regexp matching from 355 // scratch (including, potentially, compiling a new version of the code). 356 return_value = RETRY; 357 } else { 358 *subject = subject_handle->ptr(); 359 intptr_t byte_length = *input_end - *input_start; 360 *input_start = subject_handle->AddressOfCharacterAt(start_index, no_gc); 361 *input_end = *input_start + byte_length; 362 } 363 } 364 return return_value; 365} 366 367// Returns a {Result} sentinel, or the number of successful matches. 368int NativeRegExpMacroAssembler::Match(Handle<JSRegExp> regexp, 369 Handle<String> subject, 370 int* offsets_vector, 371 int offsets_vector_length, 372 int previous_index, Isolate* isolate) { 373 DCHECK(subject->IsFlat()); 374 DCHECK_LE(0, previous_index); 375 DCHECK_LE(previous_index, subject->length()); 376 377 // No allocations before calling the regexp, but we can't use 378 // DisallowGarbageCollection, since regexps might be preempted, and another 379 // thread might do allocation anyway. 380 381 String subject_ptr = *subject; 382 // Character offsets into string. 383 int start_offset = previous_index; 384 int char_length = subject_ptr.length() - start_offset; 385 int slice_offset = 0; 386 387 // The string has been flattened, so if it is a cons string it contains the 388 // full string in the first part. 389 if (StringShape(subject_ptr).IsCons()) { 390 DCHECK_EQ(0, ConsString::cast(subject_ptr).second().length()); 391 subject_ptr = ConsString::cast(subject_ptr).first(); 392 } else if (StringShape(subject_ptr).IsSliced()) { 393 SlicedString slice = SlicedString::cast(subject_ptr); 394 subject_ptr = slice.parent(); 395 slice_offset = slice.offset(); 396 } 397 if (StringShape(subject_ptr).IsThin()) { 398 subject_ptr = ThinString::cast(subject_ptr).actual(); 399 } 400 // Ensure that an underlying string has the same representation. 401 bool is_one_byte = subject_ptr.IsOneByteRepresentation(); 402 DCHECK(subject_ptr.IsExternalString() || subject_ptr.IsSeqString()); 403 // String is now either Sequential or External 404 int char_size_shift = is_one_byte ? 0 : 1; 405 406 DisallowGarbageCollection no_gc; 407 const byte* input_start = 408 subject_ptr.AddressOfCharacterAt(start_offset + slice_offset, no_gc); 409 int byte_length = char_length << char_size_shift; 410 const byte* input_end = input_start + byte_length; 411 return Execute(*subject, start_offset, input_start, input_end, offsets_vector, 412 offsets_vector_length, isolate, *regexp); 413} 414 415// static 416int NativeRegExpMacroAssembler::ExecuteForTesting( 417 String input, int start_offset, const byte* input_start, 418 const byte* input_end, int* output, int output_size, Isolate* isolate, 419 JSRegExp regexp) { 420 return Execute(input, start_offset, input_start, input_end, output, 421 output_size, isolate, regexp); 422} 423 424// Returns a {Result} sentinel, or the number of successful matches. 425// TODO(pthier): The JSRegExp object is passed to native irregexp code to match 426// the signature of the interpreter. We should get rid of JS objects passed to 427// internal methods. 428int NativeRegExpMacroAssembler::Execute( 429 String input, // This needs to be the unpacked (sliced, cons) string. 430 int start_offset, const byte* input_start, const byte* input_end, 431 int* output, int output_size, Isolate* isolate, JSRegExp regexp) { 432 RegExpStackScope stack_scope(isolate); 433 434 bool is_one_byte = String::IsOneByteRepresentationUnderneath(input); 435 Code code = FromCodeT(CodeT::cast(regexp.code(is_one_byte))); 436 RegExp::CallOrigin call_origin = RegExp::CallOrigin::kFromRuntime; 437 438 using RegexpMatcherSig = 439 // NOLINTNEXTLINE(readability/casting) 440 int(Address input_string, int start_offset, const byte* input_start, 441 const byte* input_end, int* output, int output_size, int call_origin, 442 Isolate* isolate, Address regexp); 443 444 auto fn = GeneratedCode<RegexpMatcherSig>::FromCode(code); 445 int result = fn.Call(input.ptr(), start_offset, input_start, input_end, 446 output, output_size, call_origin, isolate, regexp.ptr()); 447 DCHECK_GE(result, SMALLEST_REGEXP_RESULT); 448 449 if (result == EXCEPTION && !isolate->has_pending_exception()) { 450 // We detected a stack overflow (on the backtrack stack) in RegExp code, 451 // but haven't created the exception yet. Additionally, we allow heap 452 // allocation because even though it invalidates {input_start} and 453 // {input_end}, we are about to return anyway. 454 AllowGarbageCollection allow_allocation; 455 isolate->StackOverflow(); 456 } 457 return result; 458} 459 460#endif // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER 461 462// clang-format off 463const byte NativeRegExpMacroAssembler::word_character_map[] = { 464 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 465 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 466 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 467 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 468 469 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 470 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 471 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // '0' - '7' 472 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // '8' - '9' 473 474 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'A' - 'G' 475 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'H' - 'O' 476 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'P' - 'W' 477 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, // 'X' - 'Z', '_' 478 479 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'a' - 'g' 480 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'h' - 'o' 481 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'p' - 'w' 482 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // 'x' - 'z' 483 // Latin-1 range 484 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 485 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 486 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 487 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 488 489 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 490 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 491 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 492 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 493 494 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 495 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 496 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 497 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 498 499 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 500 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 501 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 502 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 503}; 504// clang-format on 505 506// static 507Address NativeRegExpMacroAssembler::GrowStack(Isolate* isolate) { 508 DisallowGarbageCollection no_gc; 509 510 RegExpStack* regexp_stack = isolate->regexp_stack(); 511 const size_t old_size = regexp_stack->memory_size(); 512 513#ifdef DEBUG 514 const Address old_stack_top = regexp_stack->memory_top(); 515 const Address old_stack_pointer = regexp_stack->stack_pointer(); 516 CHECK_LE(old_stack_pointer, old_stack_top); 517 CHECK_LE(static_cast<size_t>(old_stack_top - old_stack_pointer), old_size); 518#endif // DEBUG 519 520 Address new_stack_base = regexp_stack->EnsureCapacity(old_size * 2); 521 if (new_stack_base == kNullAddress) return kNullAddress; 522 523 return regexp_stack->stack_pointer(); 524} 525 526} // namespace internal 527} // namespace v8 528