RegExpObject.cpp (41662B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/RegExpObject.h" 8 9 #include "mozilla/MemoryReporting.h" 10 #include "mozilla/PodOperations.h" 11 12 #include <type_traits> 13 14 #include "builtin/RegExp.h" 15 #include "builtin/SelfHostingDefines.h" // REGEXP_*_FLAG 16 #include "frontend/FrontendContext.h" // AutoReportFrontendContext 17 #include "frontend/TokenStream.h" 18 #include "gc/HashUtil.h" 19 #include "irregexp/RegExpAPI.h" 20 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 21 #include "js/friend/StackLimits.h" // js::ReportOverRecursed 22 #include "js/Object.h" // JS::GetBuiltinClass 23 #include "js/Printer.h" // js::GenericPrinter 24 #include "js/RegExp.h" 25 #include "js/RegExpFlags.h" // JS::RegExpFlags 26 #include "util/StringBuilder.h" 27 #include "util/Unicode.h" 28 #include "vm/JSONPrinter.h" // js::JSONPrinter 29 #include "vm/MatchPairs.h" 30 #include "vm/PlainObject.h" 31 #include "vm/RegExpStatics.h" 32 #include "vm/StringType.h" 33 34 #include "vm/JSContext-inl.h" 35 #include "vm/JSObject-inl.h" 36 #include "vm/NativeObject-inl.h" 37 #include "vm/Shape-inl.h" 38 39 using namespace js; 40 41 using JS::CompileOptions; 42 using JS::RegExpFlag; 43 using JS::RegExpFlags; 44 using mozilla::DebugOnly; 45 using mozilla::PodCopy; 46 47 using JS::AutoCheckCannotGC; 48 49 static_assert(RegExpFlag::HasIndices == REGEXP_HASINDICES_FLAG, 50 "self-hosted JS and /d flag bits must agree"); 51 static_assert(RegExpFlag::Global == REGEXP_GLOBAL_FLAG, 52 "self-hosted JS and /g flag bits must agree"); 53 static_assert(RegExpFlag::IgnoreCase == REGEXP_IGNORECASE_FLAG, 54 "self-hosted JS and /i flag bits must agree"); 55 static_assert(RegExpFlag::Multiline == REGEXP_MULTILINE_FLAG, 56 "self-hosted JS and /m flag bits must agree"); 57 static_assert(RegExpFlag::DotAll == REGEXP_DOTALL_FLAG, 58 "self-hosted JS and /s flag bits must agree"); 59 static_assert(RegExpFlag::Unicode == REGEXP_UNICODE_FLAG, 60 "self-hosted JS and /u flag bits must agree"); 61 static_assert(RegExpFlag::UnicodeSets == REGEXP_UNICODESETS_FLAG, 62 "self-hosted JS and /v flag bits must agree"); 63 static_assert(RegExpFlag::Sticky == REGEXP_STICKY_FLAG, 64 "self-hosted JS and /y flag bits must agree"); 65 /* 66 * RegExpAlloc ( newTarget ) 67 * https://github.com/tc39/proposal-regexp-legacy-features?tab=readme-ov-file 68 */ 69 RegExpObject* js::RegExpAlloc(JSContext* cx, NewObjectKind newKind, 70 HandleObject proto, HandleObject newTarget) { 71 Rooted<RegExpObject*> regexp( 72 cx, NewObjectWithClassProtoAndKind<RegExpObject>(cx, proto, newKind)); 73 if (!regexp) { 74 return nullptr; 75 } 76 77 if (!SharedShape::ensureInitialCustomShape<RegExpObject>(cx, regexp)) { 78 return nullptr; 79 } 80 // Step 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, 81 // "%RegExpPrototype%", «[[RegExpMatcher]], [[OriginalSource]], 82 // [[OriginalFlags]], [[Realm]], [[LegacyFeaturesEnabled]]»). 83 // Set default newTarget if not provided 84 bool legacyFeaturesEnabled = false; 85 if (JS::Prefs::experimental_legacy_regexp()) { 86 // Step 2. Let thisRealm be the current Realm Record. 87 // Step 3. Set the value of obj’s [[Realm]] internal slot to thisRealm. 88 JS::Realm* thisRealm = cx->realm(); 89 90 JSObject* thisRealmRegExp = 91 &thisRealm->maybeGlobal()->getConstructor(JSProto_RegExp); 92 93 // Step 4. If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is 94 // true, Step 4.i then Set the value of obj’s [[LegacyFeaturesEnabled]] 95 // internal slot to true. Step 5. Else, Step 5.i. Set the value of obj’s 96 // [[LegacyFeaturesEnabled]] internal slot to false. 97 legacyFeaturesEnabled = (!newTarget || newTarget == thisRealmRegExp); 98 } 99 regexp->setLegacyFeaturesEnabled(legacyFeaturesEnabled); 100 101 // Step 6: Perform ! DefinePropertyOrThrow(obj, "lastIndex", 102 // PropertyDescriptor {[[Writable]]: true, [Enumerable]]: false, 103 // [[Configurable]]: false}). 104 MOZ_ASSERT(regexp->lookupPure(cx->names().lastIndex)->slot() == 105 RegExpObject::lastIndexSlot()); 106 107 // Step 7: Return obj. 108 return regexp; 109 } 110 111 /* MatchPairs */ 112 113 bool VectorMatchPairs::initArrayFrom(VectorMatchPairs& copyFrom) { 114 MOZ_ASSERT(copyFrom.pairCount() > 0); 115 116 if (!allocOrExpandArray(copyFrom.pairCount())) { 117 return false; 118 } 119 120 PodCopy(pairs_, copyFrom.pairs_, pairCount_); 121 122 return true; 123 } 124 125 bool VectorMatchPairs::allocOrExpandArray(size_t pairCount) { 126 if (!vec_.resizeUninitialized(pairCount)) { 127 return false; 128 } 129 130 pairs_ = &vec_[0]; 131 pairCount_ = pairCount; 132 return true; 133 } 134 135 /* RegExpObject */ 136 137 /* static */ 138 RegExpShared* RegExpObject::getShared(JSContext* cx, 139 Handle<RegExpObject*> regexp) { 140 if (regexp->hasShared()) { 141 return regexp->getShared(); 142 } 143 144 return createShared(cx, regexp); 145 } 146 147 static const ClassSpec RegExpObjectClassSpec = { 148 GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>, 149 GenericCreatePrototype<RegExpObject>, 150 js::regexp_static_methods, 151 js::regexp_static_props, 152 js::regexp_methods, 153 js::regexp_properties, 154 GenericFinishInit<WhichHasRealmFuseProperty::Proto>, 155 }; 156 157 const JSClass RegExpObject::class_ = { 158 "RegExp", 159 JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) | 160 JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), 161 JS_NULL_CLASS_OPS, 162 &RegExpObjectClassSpec, 163 }; 164 165 const JSClass RegExpObject::protoClass_ = { 166 "RegExp.prototype", 167 JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), 168 JS_NULL_CLASS_OPS, 169 &RegExpObjectClassSpec, 170 }; 171 172 template <typename CharT> 173 RegExpObject* RegExpObject::create(JSContext* cx, const CharT* chars, 174 size_t length, RegExpFlags flags, 175 NewObjectKind newKind, 176 HandleObject newTarget) { 177 static_assert(std::is_same_v<CharT, char16_t>, 178 "this code may need updating if/when CharT encodes UTF-8"); 179 180 Rooted<JSAtom*> source(cx, AtomizeChars(cx, chars, length)); 181 if (!source) { 182 return nullptr; 183 } 184 185 return create(cx, source, flags, newKind, newTarget); 186 } 187 188 template RegExpObject* RegExpObject::create(JSContext* cx, 189 const char16_t* chars, 190 size_t length, RegExpFlags flags, 191 NewObjectKind newKind, 192 HandleObject newTarget); 193 194 RegExpObject* RegExpObject::createSyntaxChecked(JSContext* cx, 195 Handle<JSAtom*> source, 196 RegExpFlags flags, 197 NewObjectKind newKind, 198 HandleObject newTarget) { 199 RegExpObject* regexp = RegExpAlloc(cx, newKind, nullptr, newTarget); 200 if (!regexp) { 201 return nullptr; 202 } 203 204 regexp->initAndZeroLastIndex(source, flags, cx); 205 206 return regexp; 207 } 208 209 RegExpObject* RegExpObject::create(JSContext* cx, Handle<JSAtom*> source, 210 RegExpFlags flags, NewObjectKind newKind, 211 HandleObject newTarget) { 212 Rooted<RegExpObject*> regexp(cx); 213 { 214 AutoReportFrontendContext fc(cx); 215 CompileOptions dummyOptions(cx); 216 frontend::DummyTokenStream dummyTokenStream(&fc, dummyOptions); 217 218 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 219 if (!irregexp::CheckPatternSyntax(cx, cx->stackLimitForCurrentPrincipal(), 220 dummyTokenStream, source, flags)) { 221 return nullptr; 222 } 223 224 regexp = RegExpAlloc(cx, newKind, nullptr, newTarget); 225 if (!regexp) { 226 return nullptr; 227 } 228 229 regexp->initAndZeroLastIndex(source, flags, cx); 230 231 MOZ_ASSERT(!regexp->hasShared()); 232 } 233 return regexp; 234 } 235 236 /* static */ 237 RegExpShared* RegExpObject::createShared(JSContext* cx, 238 Handle<RegExpObject*> regexp) { 239 MOZ_ASSERT(!regexp->hasShared()); 240 Rooted<JSAtom*> source(cx, regexp->getSource()); 241 RegExpShared* shared = 242 cx->zone()->regExps().get(cx, source, regexp->getFlags()); 243 if (!shared) { 244 return nullptr; 245 } 246 247 regexp->setShared(shared); 248 249 MOZ_ASSERT(regexp->hasShared()); 250 251 return shared; 252 } 253 254 SharedShape* RegExpObject::assignInitialShape(JSContext* cx, 255 Handle<RegExpObject*> self) { 256 MOZ_ASSERT(self->empty()); 257 258 static_assert(LAST_INDEX_SLOT == 0); 259 260 /* The lastIndex property alone is writable but non-configurable. */ 261 if (!NativeObject::addPropertyInReservedSlot(cx, self, cx->names().lastIndex, 262 LAST_INDEX_SLOT, 263 {PropertyFlag::Writable})) { 264 return nullptr; 265 } 266 267 // Cache the initial RegExpObject shape that has RegExp.prototype as proto in 268 // the global object. 269 SharedShape* shape = self->sharedShape(); 270 JSObject* proto = cx->global()->maybeGetPrototype(JSProto_RegExp); 271 if (proto && shape->proto() == TaggedProto(proto)) { 272 cx->global()->setRegExpShapeWithDefaultProto(shape); 273 } 274 return shape; 275 } 276 277 void RegExpObject::initIgnoringLastIndex(JSAtom* source, RegExpFlags flags) { 278 // If this is a re-initialization with an existing RegExpShared, 'flags' 279 // may not match getShared()->flags, so forget the RegExpShared. 280 clearShared(); 281 282 setSource(source); 283 setFlags(flags); 284 } 285 286 void RegExpObject::initAndZeroLastIndex(JSAtom* source, RegExpFlags flags, 287 JSContext* cx) { 288 initIgnoringLastIndex(source, flags); 289 zeroLastIndex(cx); 290 } 291 292 template <typename KnownF, typename UnknownF> 293 void ForEachRegExpFlag(JS::RegExpFlags flags, KnownF known, UnknownF unknown) { 294 uint8_t raw = flags.value(); 295 296 for (uint8_t i = 1; i; i = i << 1) { 297 if (!(raw & i)) { 298 continue; 299 } 300 switch (raw & i) { 301 case RegExpFlag::HasIndices: 302 known("HasIndices", "d"); 303 break; 304 case RegExpFlag::Global: 305 known("Global", "g"); 306 break; 307 case RegExpFlag::IgnoreCase: 308 known("IgnoreCase", "i"); 309 break; 310 case RegExpFlag::Multiline: 311 known("Multiline", "m"); 312 break; 313 case RegExpFlag::DotAll: 314 known("DotAll", "s"); 315 break; 316 case RegExpFlag::Unicode: 317 known("Unicode", "u"); 318 break; 319 case RegExpFlag::UnicodeSets: 320 known("UnicodeSets", "v"); 321 break; 322 case RegExpFlag::Sticky: 323 known("Sticky", "y"); 324 break; 325 default: 326 unknown(i); 327 break; 328 } 329 } 330 } 331 332 std::ostream& JS::operator<<(std::ostream& os, RegExpFlags flags) { 333 ForEachRegExpFlag( 334 flags, [&](const char* name, const char* c) { os << c; }, 335 [&](uint8_t value) { os << '?'; }); 336 return os; 337 } 338 339 #if defined(DEBUG) || defined(JS_JITSPEW) 340 void RegExpObject::dumpOwnFields(js::JSONPrinter& json) const { 341 { 342 js::GenericPrinter& out = json.beginStringProperty("source"); 343 getSource()->dumpPropertyName(out); 344 json.endStringProperty(); 345 } 346 347 json.beginInlineListProperty("flags"); 348 ForEachRegExpFlag( 349 getFlags(), 350 [&](const char* name, const char* c) { json.value("%s", name); }, 351 [&](uint8_t value) { json.value("Unknown(%02x)", value); }); 352 json.endInlineList(); 353 354 { 355 js::GenericPrinter& out = json.beginStringProperty("lastIndex"); 356 getLastIndex().dumpStringContent(out); 357 json.endStringProperty(); 358 } 359 } 360 361 void RegExpObject::dumpOwnStringContent(js::GenericPrinter& out) const { 362 out.put("/"); 363 364 getSource()->dumpCharsNoQuote(out); 365 366 out.put("/"); 367 368 ForEachRegExpFlag( 369 getFlags(), [&](const char* name, const char* c) { out.put(c); }, 370 [&](uint8_t value) {}); 371 } 372 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ 373 374 static MOZ_ALWAYS_INLINE bool IsRegExpLineTerminator(const JS::Latin1Char c) { 375 return c == '\n' || c == '\r'; 376 } 377 378 static MOZ_ALWAYS_INLINE bool IsRegExpLineTerminator(const char16_t c) { 379 return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029; 380 } 381 382 static MOZ_ALWAYS_INLINE bool AppendEscapedLineTerminator( 383 StringBuilder& sb, const JS::Latin1Char c) { 384 switch (c) { 385 case '\n': 386 if (!sb.append('n')) { 387 return false; 388 } 389 break; 390 case '\r': 391 if (!sb.append('r')) { 392 return false; 393 } 394 break; 395 default: 396 MOZ_CRASH("Bad LineTerminator"); 397 } 398 return true; 399 } 400 401 static MOZ_ALWAYS_INLINE bool AppendEscapedLineTerminator(StringBuilder& sb, 402 const char16_t c) { 403 switch (c) { 404 case '\n': 405 if (!sb.append('n')) { 406 return false; 407 } 408 break; 409 case '\r': 410 if (!sb.append('r')) { 411 return false; 412 } 413 break; 414 case 0x2028: 415 if (!sb.append("u2028")) { 416 return false; 417 } 418 break; 419 case 0x2029: 420 if (!sb.append("u2029")) { 421 return false; 422 } 423 break; 424 default: 425 MOZ_CRASH("Bad LineTerminator"); 426 } 427 return true; 428 } 429 430 template <typename CharT> 431 static MOZ_ALWAYS_INLINE bool SetupBuilder(StringBuilder& sb, 432 const CharT* oldChars, size_t oldLen, 433 const CharT* it) { 434 if constexpr (std::is_same_v<CharT, char16_t>) { 435 if (!sb.ensureTwoByteChars()) { 436 return false; 437 } 438 } 439 440 if (!sb.reserve(oldLen + 1)) { 441 return false; 442 } 443 444 sb.infallibleAppend(oldChars, size_t(it - oldChars)); 445 return true; 446 } 447 448 // Note: leaves the string builder empty if no escaping need be performed. 449 template <typename CharT> 450 static bool EscapeRegExpPattern(StringBuilder& sb, const CharT* oldChars, 451 size_t oldLen) { 452 bool inBrackets = false; 453 bool previousCharacterWasBackslash = false; 454 455 for (const CharT* it = oldChars; it < oldChars + oldLen; ++it) { 456 CharT ch = *it; 457 if (!previousCharacterWasBackslash) { 458 if (inBrackets) { 459 if (ch == ']') { 460 inBrackets = false; 461 } 462 } else if (ch == '/') { 463 // There's a forward slash that needs escaping. 464 if (sb.empty()) { 465 // This is the first char we've seen that needs escaping, 466 // copy everything up to this point. 467 if (!SetupBuilder(sb, oldChars, oldLen, it)) { 468 return false; 469 } 470 } 471 if (!sb.append('\\')) { 472 return false; 473 } 474 } else if (ch == '[') { 475 inBrackets = true; 476 } 477 } 478 479 if (IsRegExpLineTerminator(ch)) { 480 // There's LineTerminator that needs escaping. 481 if (sb.empty()) { 482 // This is the first char we've seen that needs escaping, 483 // copy everything up to this point. 484 if (!SetupBuilder(sb, oldChars, oldLen, it)) { 485 return false; 486 } 487 } 488 if (!previousCharacterWasBackslash) { 489 if (!sb.append('\\')) { 490 return false; 491 } 492 } 493 if (!AppendEscapedLineTerminator(sb, ch)) { 494 return false; 495 } 496 } else if (!sb.empty()) { 497 if (!sb.append(ch)) { 498 return false; 499 } 500 } 501 502 if (previousCharacterWasBackslash) { 503 previousCharacterWasBackslash = false; 504 } else if (ch == '\\') { 505 previousCharacterWasBackslash = true; 506 } 507 } 508 509 return true; 510 } 511 512 // ES6 draft rev32 21.2.3.2.4. 513 JSLinearString* js::EscapeRegExpPattern(JSContext* cx, Handle<JSAtom*> src) { 514 // Step 2. 515 if (src->length() == 0) { 516 return cx->names().emptyRegExp_; 517 } 518 519 // We may never need to use |sb|. Start using it lazily. 520 JSStringBuilder sb(cx); 521 bool escapeFailed = false; 522 if (src->hasLatin1Chars()) { 523 JS::AutoCheckCannotGC nogc; 524 escapeFailed = 525 !::EscapeRegExpPattern(sb, src->latin1Chars(nogc), src->length()); 526 } else { 527 JS::AutoCheckCannotGC nogc; 528 escapeFailed = 529 !::EscapeRegExpPattern(sb, src->twoByteChars(nogc), src->length()); 530 } 531 if (escapeFailed) { 532 return nullptr; 533 } 534 535 // Step 3. 536 if (sb.empty()) { 537 return src; 538 } 539 return sb.finishString(); 540 } 541 542 // ES6 draft rev32 21.2.5.14. Optimized for RegExpObject. 543 JSLinearString* RegExpObject::toString(JSContext* cx, 544 Handle<RegExpObject*> obj) { 545 // Steps 3-4. 546 Rooted<JSAtom*> src(cx, obj->getSource()); 547 if (!src) { 548 return nullptr; 549 } 550 Rooted<JSLinearString*> escapedSrc(cx, EscapeRegExpPattern(cx, src)); 551 552 // Step 7. 553 JSStringBuilder sb(cx); 554 size_t len = escapedSrc->length(); 555 if (!sb.reserve(len + 2)) { 556 return nullptr; 557 } 558 sb.infallibleAppend('/'); 559 if (!sb.append(escapedSrc)) { 560 return nullptr; 561 } 562 sb.infallibleAppend('/'); 563 564 // Steps 5-7. 565 if (obj->hasIndices() && !sb.append('d')) { 566 return nullptr; 567 } 568 if (obj->global() && !sb.append('g')) { 569 return nullptr; 570 } 571 if (obj->ignoreCase() && !sb.append('i')) { 572 return nullptr; 573 } 574 if (obj->multiline() && !sb.append('m')) { 575 return nullptr; 576 } 577 if (obj->dotAll() && !sb.append('s')) { 578 return nullptr; 579 } 580 if (obj->unicode() && !sb.append('u')) { 581 return nullptr; 582 } 583 if (obj->unicodeSets() && !sb.append('v')) { 584 return nullptr; 585 } 586 if (obj->sticky() && !sb.append('y')) { 587 return nullptr; 588 } 589 590 return sb.finishString(); 591 } 592 593 template <typename CharT> 594 static MOZ_ALWAYS_INLINE bool IsRegExpMetaChar(CharT ch) { 595 switch (ch) { 596 /* ES 2016 draft Mar 25, 2016 21.2.1 SyntaxCharacter. */ 597 case '^': 598 case '$': 599 case '\\': 600 case '.': 601 case '*': 602 case '+': 603 case '?': 604 case '(': 605 case ')': 606 case '[': 607 case ']': 608 case '{': 609 case '}': 610 case '|': 611 return true; 612 default: 613 return false; 614 } 615 } 616 617 template <typename CharT> 618 bool js::HasRegExpMetaChars(const CharT* chars, size_t length) { 619 for (size_t i = 0; i < length; ++i) { 620 if (IsRegExpMetaChar<CharT>(chars[i])) { 621 return true; 622 } 623 } 624 return false; 625 } 626 627 template bool js::HasRegExpMetaChars<Latin1Char>(const Latin1Char* chars, 628 size_t length); 629 630 template bool js::HasRegExpMetaChars<char16_t>(const char16_t* chars, 631 size_t length); 632 633 bool js::StringHasRegExpMetaChars(const JSLinearString* str) { 634 AutoCheckCannotGC nogc; 635 if (str->hasLatin1Chars()) { 636 return HasRegExpMetaChars(str->latin1Chars(nogc), str->length()); 637 } 638 639 return HasRegExpMetaChars(str->twoByteChars(nogc), str->length()); 640 } 641 642 /* RegExpShared */ 643 644 RegExpShared::RegExpShared(JSAtom* source, RegExpFlags flags) 645 : CellWithTenuredGCPointer(source), pairCount_(0), flags(flags) {} 646 647 void RegExpShared::traceChildren(JSTracer* trc) { 648 TraceNullableCellHeaderEdge(trc, this, "RegExpShared source"); 649 if (kind() == RegExpShared::Kind::Atom) { 650 TraceNullableEdge(trc, &patternAtom_, "RegExpShared pattern atom"); 651 } else { 652 for (auto& comp : compilationArray) { 653 TraceNullableEdge(trc, &comp.jitCode, "RegExpShared code"); 654 } 655 TraceNullableEdge(trc, &groupsTemplate_, "RegExpShared groups template"); 656 } 657 } 658 659 void RegExpShared::discardJitCode() { 660 for (auto& comp : compilationArray) { 661 comp.jitCode = nullptr; 662 } 663 664 // We can also purge the tables used by JIT code. 665 tables.clearAndFree(); 666 } 667 668 void RegExpShared::finalize(JS::GCContext* gcx) { 669 for (auto& comp : compilationArray) { 670 if (comp.byteCode) { 671 size_t length = comp.byteCodeLength(); 672 gcx->free_(this, comp.byteCode, length, MemoryUse::RegExpSharedBytecode); 673 } 674 } 675 if (namedCaptureIndices_) { 676 size_t length = numNamedCaptures() * sizeof(uint32_t); 677 gcx->free_(this, namedCaptureIndices_, length, 678 MemoryUse::RegExpSharedNamedCaptureData); 679 } 680 if (namedCaptureSliceIndices_) { 681 size_t length = numDistinctNamedCaptures() * sizeof(uint32_t); 682 gcx->free_(this, namedCaptureSliceIndices_, length, 683 MemoryUse::RegExpSharedNamedCaptureSliceData); 684 } 685 tables.~JitCodeTables(); 686 } 687 688 /* static */ 689 bool RegExpShared::compileIfNecessary(JSContext* cx, 690 MutableHandleRegExpShared re, 691 Handle<JSLinearString*> input, 692 RegExpShared::CodeKind codeKind) { 693 if (codeKind == RegExpShared::CodeKind::Any) { 694 // We start by interpreting regexps, then compile them once they are 695 // sufficiently hot. For very long input strings, we tier up eagerly. 696 codeKind = RegExpShared::CodeKind::Bytecode; 697 if (re->markedForTierUp() || input->length() > 1000) { 698 codeKind = RegExpShared::CodeKind::Jitcode; 699 } 700 } 701 702 // Fall back to bytecode if native codegen is not available. 703 if (!IsNativeRegExpEnabled() && codeKind == RegExpShared::CodeKind::Jitcode) { 704 codeKind = RegExpShared::CodeKind::Bytecode; 705 } 706 707 bool needsCompile = false; 708 if (re->kind() == RegExpShared::Kind::Unparsed) { 709 needsCompile = true; 710 } 711 if (re->kind() == RegExpShared::Kind::RegExp) { 712 if (!re->isCompiled(input->hasLatin1Chars(), codeKind)) { 713 needsCompile = true; 714 } 715 } 716 if (needsCompile) { 717 return irregexp::CompilePattern(cx, re, input, codeKind); 718 } 719 return true; 720 } 721 722 /* static */ 723 RegExpRunStatus RegExpShared::execute(JSContext* cx, 724 MutableHandleRegExpShared re, 725 Handle<JSLinearString*> input, 726 size_t start, VectorMatchPairs* matches) { 727 MOZ_ASSERT(matches); 728 729 // TODO: Add tracelogger support 730 731 /* Compile the code at point-of-use. */ 732 if (!compileIfNecessary(cx, re, input, RegExpShared::CodeKind::Any)) { 733 return RegExpRunStatus::Error; 734 } 735 736 /* 737 * Ensure sufficient memory for output vector. 738 * No need to initialize it. The RegExp engine fills them in on a match. 739 */ 740 if (!matches->allocOrExpandArray(re->pairCount())) { 741 ReportOutOfMemory(cx); 742 return RegExpRunStatus::Error; 743 } 744 745 if (re->kind() == RegExpShared::Kind::Atom) { 746 return RegExpShared::executeAtom(re, input, start, matches); 747 } 748 749 /* 750 * Ensure sufficient memory for output vector. 751 * No need to initialize it. The RegExp engine fills them in on a match. 752 */ 753 if (!matches->allocOrExpandArray(re->pairCount())) { 754 ReportOutOfMemory(cx); 755 return RegExpRunStatus::Error; 756 } 757 758 uint32_t interruptRetries = 0; 759 const uint32_t maxInterruptRetries = 4; 760 do { 761 DebugOnly<bool> alreadyThrowing = cx->isExceptionPending(); 762 RegExpRunStatus result = irregexp::Execute(cx, re, input, start, matches); 763 #ifdef DEBUG 764 // Check if we must simulate the interruption 765 if (js::irregexp::IsolateShouldSimulateInterrupt(cx->isolate)) { 766 js::irregexp::IsolateClearShouldSimulateInterrupt(cx->isolate); 767 cx->requestInterrupt(InterruptReason::CallbackUrgent); 768 } 769 #endif 770 if (result == RegExpRunStatus::Error) { 771 /* Execute can return RegExpRunStatus::Error: 772 * 773 * 1. If the native stack overflowed 774 * 2. If the backtrack stack overflowed 775 * 3. If an interrupt was requested during execution. 776 * 777 * In the first two cases, we want to throw an error. In the 778 * third case, we want to handle the interrupt and try again. 779 * We cap the number of times we will retry. 780 */ 781 if (cx->isExceptionPending()) { 782 // If this regexp is being executed by recovery instructions 783 // while bailing out to handle an exception, there may already 784 // be an exception pending. If so, just return that exception 785 // instead of reporting a new one. 786 MOZ_ASSERT(alreadyThrowing); 787 return RegExpRunStatus::Error; 788 } 789 if (cx->hasAnyPendingInterrupt()) { 790 if (!CheckForInterrupt(cx)) { 791 return RegExpRunStatus::Error; 792 } 793 if (interruptRetries++ < maxInterruptRetries) { 794 // The initial execution may have been interpreted, or the 795 // interrupt may have triggered a GC that discarded jitcode. 796 // To maximize the chance of succeeding before being 797 // interrupted again, we want to ensure we are compiled. 798 if (!compileIfNecessary(cx, re, input, 799 RegExpShared::CodeKind::Jitcode)) { 800 return RegExpRunStatus::Error; 801 } 802 continue; 803 } 804 } 805 // If we have run out of retries, this regexp takes too long to execute. 806 ReportOverRecursed(cx); 807 return RegExpRunStatus::Error; 808 } 809 810 MOZ_ASSERT(result == RegExpRunStatus::Success || 811 result == RegExpRunStatus::Success_NotFound); 812 813 return result; 814 } while (true); 815 816 MOZ_CRASH("Unreachable"); 817 } 818 819 void RegExpShared::useAtomMatch(Handle<JSAtom*> pattern) { 820 MOZ_ASSERT(kind() == RegExpShared::Kind::Unparsed); 821 kind_ = RegExpShared::Kind::Atom; 822 patternAtom_ = pattern; 823 pairCount_ = 1; 824 } 825 826 void RegExpShared::useRegExpMatch(size_t pairCount) { 827 MOZ_ASSERT(kind() == RegExpShared::Kind::Unparsed); 828 kind_ = RegExpShared::Kind::RegExp; 829 pairCount_ = pairCount; 830 ticks_ = jit::JitOptions.regexpWarmUpThreshold; 831 } 832 833 /* static */ 834 void RegExpShared::InitializeNamedCaptures(JSContext* cx, HandleRegExpShared re, 835 uint32_t numNamedCaptures, 836 uint32_t numDistinctNamedCaptures, 837 Handle<PlainObject*> templateObject, 838 uint32_t* captureIndices, 839 uint32_t* sliceIndices) { 840 MOZ_ASSERT(!re->groupsTemplate_); 841 MOZ_ASSERT(!re->namedCaptureIndices_); 842 MOZ_ASSERT(!re->namedCaptureSliceIndices_); 843 844 re->numNamedCaptures_ = numNamedCaptures; 845 re->numDistinctNamedCaptures_ = numDistinctNamedCaptures; 846 re->groupsTemplate_ = templateObject; 847 re->namedCaptureIndices_ = captureIndices; 848 re->namedCaptureSliceIndices_ = sliceIndices; 849 850 uint32_t arraySize = numNamedCaptures * sizeof(uint32_t); 851 js::AddCellMemory(re, arraySize, MemoryUse::RegExpSharedNamedCaptureData); 852 853 if (sliceIndices) { 854 arraySize = numDistinctNamedCaptures * sizeof(uint32_t); 855 js::AddCellMemory(re, arraySize, 856 MemoryUse::RegExpSharedNamedCaptureSliceData); 857 } 858 } 859 860 void RegExpShared::tierUpTick() { 861 MOZ_ASSERT(kind() == RegExpShared::Kind::RegExp); 862 if (ticks_ > 0) { 863 ticks_--; 864 } 865 } 866 867 bool RegExpShared::markedForTierUp() const { 868 if (!IsNativeRegExpEnabled()) { 869 return false; 870 } 871 if (kind() != RegExpShared::Kind::RegExp) { 872 return false; 873 } 874 return ticks_ == 0; 875 } 876 877 // When either unicode flag is set and if |index| points to a trail surrogate, 878 // step back to the corresponding lead surrogate. 879 static size_t StepBackToLeadSurrogate(const JSLinearString* input, 880 size_t index) { 881 // |index| must be a position within a two-byte string, otherwise it can't 882 // point to the trail surrogate of a surrogate pair. 883 if (index == 0 || index >= input->length() || input->hasLatin1Chars()) { 884 return index; 885 } 886 887 /* 888 * ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 889 * 21.2.2.2 step 2. 890 * Let listIndex be the index into Input of the character that was obtained 891 * from element index of str. 892 * 893 * In the spec, pattern match is performed with decoded Unicode code points, 894 * but our implementation performs it with UTF-16 encoded strings. In step 2, 895 * we should decrement lastIndex (index) if it points to a trail surrogate 896 * that has a corresponding lead surrogate. 897 * 898 * var r = /\uD83D\uDC38/ug; 899 * r.lastIndex = 1; 900 * var str = "\uD83D\uDC38"; 901 * var result = r.exec(str); // pattern match starts from index 0 902 * print(result.index); // prints 0 903 * 904 * Note: This doesn't match the current spec text and result in different 905 * values for `result.index` under certain conditions. However, the spec will 906 * change to match our implementation's behavior. 907 * See https://github.com/tc39/ecma262/issues/128. 908 */ 909 JS::AutoCheckCannotGC nogc; 910 const auto* chars = input->twoByteChars(nogc); 911 if (unicode::IsTrailSurrogate(chars[index]) && 912 unicode::IsLeadSurrogate(chars[index - 1])) { 913 index--; 914 } 915 return index; 916 } 917 918 static RegExpRunStatus ExecuteAtomImpl(RegExpShared* re, 919 const JSLinearString* input, 920 size_t start, MatchPairs* matches) { 921 MOZ_ASSERT(re->pairCount() == 1); 922 size_t length = input->length(); 923 size_t searchLength = re->patternAtom()->length(); 924 925 if (re->unicode() || re->unicodeSets()) { 926 start = StepBackToLeadSurrogate(input, start); 927 } 928 929 if (re->sticky()) { 930 // First part checks size_t overflow. 931 if (searchLength + start < searchLength || searchLength + start > length) { 932 return RegExpRunStatus::Success_NotFound; 933 } 934 if (!HasSubstringAt(input, re->patternAtom(), start)) { 935 return RegExpRunStatus::Success_NotFound; 936 } 937 938 (*matches)[0].start = start; 939 (*matches)[0].limit = start + searchLength; 940 matches->checkAgainst(input->length()); 941 return RegExpRunStatus::Success; 942 } 943 944 int res = StringFindPattern(input, re->patternAtom(), start); 945 if (res == -1) { 946 return RegExpRunStatus::Success_NotFound; 947 } 948 949 (*matches)[0].start = res; 950 (*matches)[0].limit = res + searchLength; 951 matches->checkAgainst(input->length()); 952 return RegExpRunStatus::Success; 953 } 954 955 RegExpRunStatus js::ExecuteRegExpAtomRaw(RegExpShared* re, 956 const JSLinearString* input, 957 size_t start, MatchPairs* matchPairs) { 958 AutoUnsafeCallWithABI unsafe; 959 return ExecuteAtomImpl(re, input, start, matchPairs); 960 } 961 962 /* static */ 963 RegExpRunStatus RegExpShared::executeAtom(MutableHandleRegExpShared re, 964 Handle<JSLinearString*> input, 965 size_t start, 966 VectorMatchPairs* matches) { 967 return ExecuteAtomImpl(re, input, start, matches); 968 } 969 970 size_t RegExpShared::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { 971 size_t n = 0; 972 973 for (const auto& compilation : compilationArray) { 974 if (compilation.byteCode) { 975 n += mallocSizeOf(compilation.byteCode); 976 } 977 } 978 979 n += tables.sizeOfExcludingThis(mallocSizeOf); 980 for (size_t i = 0; i < tables.length(); i++) { 981 n += mallocSizeOf(tables[i].get()); 982 } 983 984 return n; 985 } 986 987 /* RegExpRealm */ 988 989 RegExpRealm::RegExpRealm() { 990 for (auto& shape : matchResultShapes_) { 991 shape = nullptr; 992 } 993 } 994 995 SharedShape* RegExpRealm::createMatchResultShape(JSContext* cx, 996 ResultShapeKind kind) { 997 MOZ_ASSERT(!matchResultShapes_[kind]); 998 999 /* Create template array object */ 1000 Rooted<ArrayObject*> templateObject(cx, NewDenseEmptyArray(cx)); 1001 if (!templateObject) { 1002 return nullptr; 1003 } 1004 1005 if (kind == ResultShapeKind::Indices) { 1006 /* The |indices| array only has a |groups| property. */ 1007 if (!NativeDefineDataProperty(cx, templateObject, cx->names().groups, 1008 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1009 return nullptr; 1010 } 1011 MOZ_ASSERT(templateObject->getLastProperty().slot() == IndicesGroupsSlot); 1012 1013 matchResultShapes_[kind].set(templateObject->sharedShape()); 1014 return matchResultShapes_[kind]; 1015 } 1016 1017 /* Set dummy index property */ 1018 if (!NativeDefineDataProperty(cx, templateObject, cx->names().index, 1019 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1020 return nullptr; 1021 } 1022 MOZ_ASSERT(templateObject->getLastProperty().slot() == 1023 MatchResultObjectIndexSlot); 1024 1025 /* Set dummy input property */ 1026 if (!NativeDefineDataProperty(cx, templateObject, cx->names().input, 1027 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1028 return nullptr; 1029 } 1030 MOZ_ASSERT(templateObject->getLastProperty().slot() == 1031 MatchResultObjectInputSlot); 1032 1033 /* Set dummy groups property */ 1034 if (!NativeDefineDataProperty(cx, templateObject, cx->names().groups, 1035 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1036 return nullptr; 1037 } 1038 MOZ_ASSERT(templateObject->getLastProperty().slot() == 1039 MatchResultObjectGroupsSlot); 1040 1041 if (kind == ResultShapeKind::WithIndices) { 1042 /* Set dummy indices property */ 1043 if (!NativeDefineDataProperty(cx, templateObject, cx->names().indices, 1044 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1045 return nullptr; 1046 } 1047 MOZ_ASSERT(templateObject->getLastProperty().slot() == 1048 MatchResultObjectIndicesSlot); 1049 } 1050 1051 #ifdef DEBUG 1052 if (kind == ResultShapeKind::Normal) { 1053 MOZ_ASSERT(templateObject->numFixedSlots() == 0); 1054 MOZ_ASSERT(templateObject->numDynamicSlots() == 1055 MatchResultObjectNumDynamicSlots); 1056 MOZ_ASSERT(templateObject->slotSpan() == MatchResultObjectSlotSpan); 1057 } 1058 #endif 1059 1060 matchResultShapes_[kind].set(templateObject->sharedShape()); 1061 1062 return matchResultShapes_[kind]; 1063 } 1064 1065 void RegExpRealm::trace(JSTracer* trc) { 1066 if (regExpStatics) { 1067 regExpStatics->trace(trc); 1068 } 1069 1070 for (auto& shape : matchResultShapes_) { 1071 TraceNullableEdge(trc, &shape, "RegExpRealm::matchResultShapes_"); 1072 } 1073 } 1074 1075 RegExpShared* RegExpZone::get(JSContext* cx, Handle<JSAtom*> source, 1076 RegExpFlags flags) { 1077 DependentAddPtr<Set> p(cx, set_, Key(source, flags)); 1078 if (p) { 1079 return *p; 1080 } 1081 1082 auto* shared = cx->newCell<RegExpShared>(source, flags); 1083 if (!shared) { 1084 return nullptr; 1085 } 1086 1087 if (!p.add(cx, set_, Key(source, flags), shared)) { 1088 return nullptr; 1089 } 1090 1091 return shared; 1092 } 1093 1094 size_t RegExpZone::sizeOfIncludingThis( 1095 mozilla::MallocSizeOf mallocSizeOf) const { 1096 return mallocSizeOf(this) + set_.sizeOfExcludingThis(mallocSizeOf); 1097 } 1098 1099 RegExpZone::RegExpZone(Zone* zone) : set_(zone, zone) {} 1100 1101 /* Functions */ 1102 1103 JSObject* js::CloneRegExpObject(JSContext* cx, Handle<RegExpObject*> regex) { 1104 constexpr gc::AllocKind allocKind = RegExpObject::AllocKind; 1105 static_assert(gc::GetGCKindSlots(allocKind) == RegExpObject::RESERVED_SLOTS); 1106 MOZ_ASSERT(regex->asTenured().getAllocKind() == allocKind); 1107 1108 Rooted<SharedShape*> shape(cx, regex->sharedShape()); 1109 Rooted<RegExpObject*> clone(cx, NativeObject::create<RegExpObject>( 1110 cx, allocKind, gc::Heap::Default, shape)); 1111 if (!clone) { 1112 return nullptr; 1113 } 1114 1115 RegExpShared* shared = RegExpObject::getShared(cx, regex); 1116 if (!shared) { 1117 return nullptr; 1118 } 1119 1120 clone->initAndZeroLastIndex(shared->getSource(), shared->getFlags(), cx); 1121 clone->setShared(shared); 1122 if (JS::Prefs::experimental_legacy_regexp()) { 1123 clone->setLegacyFeaturesEnabled(regex->legacyFeaturesEnabled()); 1124 } 1125 return clone; 1126 } 1127 1128 template <typename CharT> 1129 static bool ParseRegExpFlags(const CharT* chars, size_t length, 1130 RegExpFlags* flagsOut, char16_t* invalidFlag) { 1131 *flagsOut = RegExpFlag::NoFlags; 1132 1133 for (size_t i = 0; i < length; i++) { 1134 uint8_t flag; 1135 if (!JS::MaybeParseRegExpFlag(chars[i], &flag) || *flagsOut & flag) { 1136 *invalidFlag = chars[i]; 1137 return false; 1138 } 1139 1140 // /u and /v flags are mutually exclusive. 1141 if (((*flagsOut & RegExpFlag::Unicode) && 1142 (flag & RegExpFlag::UnicodeSets)) || 1143 ((*flagsOut & RegExpFlag::UnicodeSets) && 1144 (flag & RegExpFlag::Unicode))) { 1145 *invalidFlag = chars[i]; 1146 return false; 1147 } 1148 1149 *flagsOut |= flag; 1150 } 1151 1152 return true; 1153 } 1154 1155 bool js::ParseRegExpFlags(JSContext* cx, JSString* flagStr, 1156 RegExpFlags* flagsOut) { 1157 JSLinearString* linear = flagStr->ensureLinear(cx); 1158 if (!linear) { 1159 return false; 1160 } 1161 1162 size_t len = linear->length(); 1163 1164 bool ok; 1165 char16_t invalidFlag; 1166 if (linear->hasLatin1Chars()) { 1167 AutoCheckCannotGC nogc; 1168 ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, 1169 &invalidFlag); 1170 } else { 1171 AutoCheckCannotGC nogc; 1172 ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, 1173 &invalidFlag); 1174 } 1175 1176 if (!ok) { 1177 JS::TwoByteChars range(&invalidFlag, 1); 1178 UniqueChars utf8(JS::CharsToNewUTF8CharsZ(cx, range).c_str()); 1179 if (!utf8) { 1180 return false; 1181 } 1182 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1183 JSMSG_BAD_REGEXP_FLAG, utf8.get()); 1184 return false; 1185 } 1186 1187 return true; 1188 } 1189 1190 JS::ubi::Node::Size JS::ubi::Concrete<RegExpShared>::size( 1191 mozilla::MallocSizeOf mallocSizeOf) const { 1192 return js::gc::Arena::thingSize(gc::AllocKind::REGEXP_SHARED) + 1193 get().sizeOfExcludingThis(mallocSizeOf); 1194 } 1195 1196 /* 1197 * Regular Expressions. 1198 */ 1199 JS_PUBLIC_API JSObject* JS::NewRegExpObject(JSContext* cx, const char* bytes, 1200 size_t length, RegExpFlags flags) { 1201 AssertHeapIsIdle(); 1202 CHECK_THREAD(cx); 1203 1204 UniqueTwoByteChars chars(InflateString(cx, bytes, length)); 1205 if (!chars) { 1206 return nullptr; 1207 } 1208 1209 return RegExpObject::create(cx, chars.get(), length, flags, GenericObject); 1210 } 1211 1212 JS_PUBLIC_API JSObject* JS::NewUCRegExpObject(JSContext* cx, 1213 const char16_t* chars, 1214 size_t length, 1215 RegExpFlags flags) { 1216 AssertHeapIsIdle(); 1217 CHECK_THREAD(cx); 1218 1219 return RegExpObject::create(cx, chars, length, flags, GenericObject); 1220 } 1221 1222 JS_PUBLIC_API bool JS::SetRegExpInput(JSContext* cx, HandleObject obj, 1223 HandleString input) { 1224 AssertHeapIsIdle(); 1225 CHECK_THREAD(cx); 1226 cx->check(input); 1227 1228 Handle<GlobalObject*> global = obj.as<GlobalObject>(); 1229 RegExpStatics* res = GlobalObject::getRegExpStatics(cx, global); 1230 if (!res) { 1231 return false; 1232 } 1233 1234 res->reset(input); 1235 return true; 1236 } 1237 1238 JS_PUBLIC_API bool JS::ClearRegExpStatics(JSContext* cx, HandleObject obj) { 1239 AssertHeapIsIdle(); 1240 CHECK_THREAD(cx); 1241 MOZ_ASSERT(obj); 1242 1243 Handle<GlobalObject*> global = obj.as<GlobalObject>(); 1244 RegExpStatics* res = GlobalObject::getRegExpStatics(cx, global); 1245 if (!res) { 1246 return false; 1247 } 1248 1249 res->clear(); 1250 return true; 1251 } 1252 1253 JS_PUBLIC_API bool JS::ExecuteRegExp(JSContext* cx, HandleObject obj, 1254 HandleObject reobj, const char16_t* chars, 1255 size_t length, size_t* indexp, bool test, 1256 MutableHandleValue rval) { 1257 AssertHeapIsIdle(); 1258 CHECK_THREAD(cx); 1259 1260 Handle<GlobalObject*> global = obj.as<GlobalObject>(); 1261 RegExpStatics* res = GlobalObject::getRegExpStatics(cx, global); 1262 if (!res) { 1263 return false; 1264 } 1265 1266 Rooted<JSLinearString*> input(cx, NewStringCopyN<CanGC>(cx, chars, length)); 1267 if (!input) { 1268 return false; 1269 } 1270 1271 return ExecuteRegExpLegacy(cx, res, reobj.as<RegExpObject>(), input, indexp, 1272 test, rval); 1273 } 1274 1275 JS_PUBLIC_API bool JS::ExecuteRegExpNoStatics(JSContext* cx, HandleObject obj, 1276 const char16_t* chars, 1277 size_t length, size_t* indexp, 1278 bool test, 1279 MutableHandleValue rval) { 1280 AssertHeapIsIdle(); 1281 CHECK_THREAD(cx); 1282 1283 Rooted<JSLinearString*> input(cx, NewStringCopyN<CanGC>(cx, chars, length)); 1284 if (!input) { 1285 return false; 1286 } 1287 1288 return ExecuteRegExpLegacy(cx, nullptr, obj.as<RegExpObject>(), input, indexp, 1289 test, rval); 1290 } 1291 1292 JS_PUBLIC_API bool JS::ObjectIsRegExp(JSContext* cx, HandleObject obj, 1293 bool* isRegExp) { 1294 cx->check(obj); 1295 1296 ESClass cls; 1297 if (!GetBuiltinClass(cx, obj, &cls)) { 1298 return false; 1299 } 1300 1301 *isRegExp = cls == ESClass::RegExp; 1302 return true; 1303 } 1304 1305 JS_PUBLIC_API RegExpFlags JS::GetRegExpFlags(JSContext* cx, HandleObject obj) { 1306 AssertHeapIsIdle(); 1307 CHECK_THREAD(cx); 1308 1309 RegExpShared* shared = RegExpToShared(cx, obj); 1310 if (!shared) { 1311 return RegExpFlag::NoFlags; 1312 } 1313 return shared->getFlags(); 1314 } 1315 1316 JS_PUBLIC_API JSString* JS::GetRegExpSource(JSContext* cx, HandleObject obj) { 1317 AssertHeapIsIdle(); 1318 CHECK_THREAD(cx); 1319 1320 RegExpShared* shared = RegExpToShared(cx, obj); 1321 if (!shared) { 1322 return nullptr; 1323 } 1324 return shared->getSource(); 1325 } 1326 1327 JS_PUBLIC_API bool JS::CheckRegExpSyntax(JSContext* cx, const char16_t* chars, 1328 size_t length, RegExpFlags flags, 1329 MutableHandleValue error) { 1330 AssertHeapIsIdle(); 1331 CHECK_THREAD(cx); 1332 1333 AutoReportFrontendContext fc(cx); 1334 CompileOptions dummyOptions(cx); 1335 frontend::DummyTokenStream dummyTokenStream(&fc, dummyOptions); 1336 1337 LifoAllocScope allocScope(&cx->tempLifoAlloc()); 1338 1339 mozilla::Range<const char16_t> source(chars, length); 1340 bool success = irregexp::CheckPatternSyntax( 1341 cx->tempLifoAlloc(), cx->stackLimitForCurrentPrincipal(), 1342 dummyTokenStream, source, flags); 1343 error.set(UndefinedValue()); 1344 if (!success) { 1345 if (!fc.convertToRuntimeErrorAndClear()) { 1346 return false; 1347 } 1348 // We can fail because of OOM or over-recursion even if the syntax is valid. 1349 if (cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()) { 1350 return false; 1351 } 1352 1353 if (!cx->getPendingException(error)) { 1354 return false; 1355 } 1356 cx->clearPendingException(); 1357 } 1358 return true; 1359 }