// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/regexp_parser.h" #include "unicode/uchar.h" #include "unicode/uniset.h" #include "platform/unicode.h" #include "vm/longjump.h" #include "vm/object_store.h" #include "vm/symbols.h" namespace dart { #define Z zone() // Enables possessive quantifier syntax for testing. static constexpr bool FLAG_regexp_possessive_quantifier = false; RegExpBuilder::RegExpBuilder(RegExpFlags flags) : zone_(Thread::Current()->zone()), pending_empty_(false), flags_(flags), characters_(nullptr), pending_surrogate_(kNoPendingSurrogate), terms_(), text_(), alternatives_() #ifdef DEBUG , last_added_(ADD_NONE) #endif { } void RegExpBuilder::AddLeadSurrogate(uint16_t lead_surrogate) { ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); FlushPendingSurrogate(); // Hold onto the lead surrogate, waiting for a trail surrogate to follow. pending_surrogate_ = lead_surrogate; } void RegExpBuilder::AddTrailSurrogate(uint16_t trail_surrogate) { ASSERT(Utf16::IsTrailSurrogate(trail_surrogate)); if (pending_surrogate_ != kNoPendingSurrogate) { uint16_t lead_surrogate = pending_surrogate_; pending_surrogate_ = kNoPendingSurrogate; ASSERT(Utf16::IsLeadSurrogate(lead_surrogate)); uint32_t combined = Utf16::Decode(lead_surrogate, trail_surrogate); if (NeedsDesugaringForIgnoreCase(combined)) { AddCharacterClassForDesugaring(combined); } else { auto surrogate_pair = new (Z) ZoneGrowableArray(2); surrogate_pair->Add(lead_surrogate); surrogate_pair->Add(trail_surrogate); RegExpAtom* atom = new (Z) RegExpAtom(surrogate_pair, flags_); AddAtom(atom); } } else { pending_surrogate_ = trail_surrogate; FlushPendingSurrogate(); } } void RegExpBuilder::FlushPendingSurrogate() { if (pending_surrogate_ != kNoPendingSurrogate) { ASSERT(is_unicode()); uint32_t c = pending_surrogate_; pending_surrogate_ = kNoPendingSurrogate; AddCharacterClassForDesugaring(c); } } void RegExpBuilder::FlushCharacters() { FlushPendingSurrogate(); pending_empty_ = false; if (characters_ != nullptr) { RegExpTree* atom = new (Z) RegExpAtom(characters_, flags_); characters_ = nullptr; text_.Add(atom); LAST(ADD_ATOM); } } void RegExpBuilder::FlushText() { FlushCharacters(); intptr_t num_text = text_.length(); if (num_text == 0) { return; } else if (num_text == 1) { terms_.Add(text_.Last()); } else { RegExpText* text = new (Z) RegExpText(); for (intptr_t i = 0; i < num_text; i++) text_[i]->AppendToText(text); terms_.Add(text); } text_.Clear(); } void RegExpBuilder::AddCharacter(uint16_t c) { FlushPendingSurrogate(); pending_empty_ = false; if (NeedsDesugaringForIgnoreCase(c)) { AddCharacterClassForDesugaring(c); } else { if (characters_ == nullptr) { characters_ = new (Z) ZoneGrowableArray(4); } characters_->Add(c); LAST(ADD_CHAR); } } void RegExpBuilder::AddUnicodeCharacter(uint32_t c) { if (c > static_cast(Utf16::kMaxCodeUnit)) { ASSERT(is_unicode()); uint16_t surrogates[2]; Utf16::Encode(c, surrogates); AddLeadSurrogate(surrogates[0]); AddTrailSurrogate(surrogates[1]); } else if (is_unicode() && Utf16::IsLeadSurrogate(c)) { AddLeadSurrogate(c); } else if (is_unicode() && Utf16::IsTrailSurrogate(c)) { AddTrailSurrogate(c); } else { AddCharacter(static_cast(c)); } } void RegExpBuilder::AddEscapedUnicodeCharacter(uint32_t character) { // A lead or trail surrogate parsed via escape sequence will not // pair up with any preceding lead or following trail surrogate. FlushPendingSurrogate(); AddUnicodeCharacter(character); FlushPendingSurrogate(); } void RegExpBuilder::AddEmpty() { pending_empty_ = true; } void RegExpBuilder::AddCharacterClass(RegExpCharacterClass* cc) { if (NeedsDesugaringForUnicode(cc)) { // With /u, character class needs to be desugared, so it // must be a standalone term instead of being part of a RegExpText. AddTerm(cc); } else { AddAtom(cc); } } void RegExpBuilder::AddCharacterClassForDesugaring(uint32_t c) { auto ranges = CharacterRange::List(Z, CharacterRange::Singleton(c)); AddTerm(new (Z) RegExpCharacterClass(ranges, flags_)); } void RegExpBuilder::AddAtom(RegExpTree* term) { if (term->IsEmpty()) { AddEmpty(); return; } if (term->IsTextElement()) { FlushCharacters(); text_.Add(term); } else { FlushText(); terms_.Add(term); } LAST(ADD_ATOM); } void RegExpBuilder::AddTerm(RegExpTree* term) { FlushText(); terms_.Add(term); LAST(ADD_ATOM); } void RegExpBuilder::AddAssertion(RegExpTree* assert) { FlushText(); terms_.Add(assert); LAST(ADD_ASSERT); } void RegExpBuilder::NewAlternative() { FlushTerms(); } void RegExpBuilder::FlushTerms() { FlushText(); intptr_t num_terms = terms_.length(); RegExpTree* alternative; if (num_terms == 0) { alternative = RegExpEmpty::GetInstance(); } else if (num_terms == 1) { alternative = terms_.Last(); } else { ZoneGrowableArray* terms = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < terms_.length(); i++) { terms->Add(terms_[i]); } alternative = new (Z) RegExpAlternative(terms); } alternatives_.Add(alternative); terms_.Clear(); LAST(ADD_NONE); } bool RegExpBuilder::NeedsDesugaringForUnicode(RegExpCharacterClass* cc) { if (!is_unicode()) return false; // TODO(yangguo): we could be smarter than this. Case-insensitivity does not // necessarily mean that we need to desugar. It's probably nicer to have a // separate pass to figure out unicode desugarings. if (ignore_case()) return true; ZoneGrowableArray* ranges = cc->ranges(); CharacterRange::Canonicalize(ranges); if (cc->is_negated()) { auto negated_ranges = new (Z) ZoneGrowableArray(ranges->length()); CharacterRange::Negate(ranges, negated_ranges); ranges = negated_ranges; } for (int i = ranges->length() - 1; i >= 0; i--) { uint32_t from = ranges->At(i).from(); uint32_t to = ranges->At(i).to(); // Check for non-BMP characters. if (to >= Utf16::kMaxCodeUnit) return true; // Check for lone surrogates. if (from <= Utf16::kTrailSurrogateEnd && to >= Utf16::kLeadSurrogateStart) { return true; } } return false; } bool RegExpBuilder::NeedsDesugaringForIgnoreCase(uint32_t c) { if (is_unicode() && ignore_case()) { icu::UnicodeSet set(c, c); set.closeOver(USET_CASE_INSENSITIVE); set.removeAllStrings(); return set.size() > 1; } return false; } RegExpTree* RegExpBuilder::ToRegExp() { FlushTerms(); intptr_t num_alternatives = alternatives_.length(); if (num_alternatives == 0) { return RegExpEmpty::GetInstance(); } if (num_alternatives == 1) { return alternatives_.Last(); } ZoneGrowableArray* alternatives = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < alternatives_.length(); i++) { alternatives->Add(alternatives_[i]); } return new (Z) RegExpDisjunction(alternatives); } bool RegExpBuilder::AddQuantifierToAtom( intptr_t min, intptr_t max, RegExpQuantifier::QuantifierType quantifier_type) { if (pending_empty_) { pending_empty_ = false; return true; } RegExpTree* atom; if (characters_ != nullptr) { DEBUG_ASSERT(last_added_ == ADD_CHAR); // Last atom was character. ZoneGrowableArray* char_vector = new (Z) ZoneGrowableArray(); char_vector->AddArray(*characters_); intptr_t num_chars = char_vector->length(); if (num_chars > 1) { ZoneGrowableArray* prefix = new (Z) ZoneGrowableArray(); for (intptr_t i = 0; i < num_chars - 1; i++) { prefix->Add(char_vector->At(i)); } text_.Add(new (Z) RegExpAtom(prefix, flags_)); ZoneGrowableArray* tail = new (Z) ZoneGrowableArray(); tail->Add(char_vector->At(num_chars - 1)); char_vector = tail; } characters_ = nullptr; atom = new (Z) RegExpAtom(char_vector, flags_); FlushText(); } else if (text_.length() > 0) { DEBUG_ASSERT(last_added_ == ADD_ATOM); atom = text_.RemoveLast(); FlushText(); } else if (terms_.length() > 0) { DEBUG_ASSERT(last_added_ == ADD_ATOM); atom = terms_.RemoveLast(); if (auto lookaround = atom->AsLookaround()) { // With /u, lookarounds are not quantifiable. if (is_unicode()) return false; // Lookbehinds are not quantifiable. if (lookaround->type() == RegExpLookaround::LOOKBEHIND) { return false; } } if (atom->max_match() == 0) { // Guaranteed to only match an empty string. LAST(ADD_TERM); if (min == 0) { return true; } terms_.Add(atom); return true; } } else { // Only call immediately after adding an atom or character! UNREACHABLE(); } terms_.Add(new (Z) RegExpQuantifier(min, max, quantifier_type, atom)); LAST(ADD_TERM); return true; } // ---------------------------------------------------------------------------- // Implementation of Parser RegExpParser::RegExpParser(const String& in, String* error, RegExpFlags flags) : zone_(Thread::Current()->zone()), captures_(nullptr), named_captures_(nullptr), named_back_references_(nullptr), in_(in), current_(kEndMarker), next_pos_(0), captures_started_(0), capture_count_(0), has_more_(true), top_level_flags_(flags), simple_(false), contains_anchor_(false), is_scanned_for_captures_(false), has_named_captures_(false) { Advance(); } inline uint32_t RegExpParser::ReadNext(bool update_position) { intptr_t position = next_pos_; const uint16_t c0 = in().CharAt(position); uint32_t c = c0; position++; if (is_unicode() && position < in().Length() && Utf16::IsLeadSurrogate(c0)) { const uint16_t c1 = in().CharAt(position); if (Utf16::IsTrailSurrogate(c1)) { c = Utf16::Decode(c0, c1); position++; } } if (update_position) next_pos_ = position; return c; } uint32_t RegExpParser::Next() { if (has_next()) { return ReadNext(false); } else { return kEndMarker; } } void RegExpParser::Advance() { if (has_next()) { current_ = ReadNext(true); } else { current_ = kEndMarker; // Advance so that position() points to 1 after the last character. This is // important so that Reset() to this position works correctly. next_pos_ = in().Length() + 1; has_more_ = false; } } void RegExpParser::Reset(intptr_t pos) { next_pos_ = pos; has_more_ = (pos < in().Length()); Advance(); } void RegExpParser::Advance(intptr_t dist) { next_pos_ += dist - 1; Advance(); } bool RegExpParser::simple() { return simple_; } bool RegExpParser::IsSyntaxCharacterOrSlash(uint32_t c) { switch (c) { case '^': case '$': case '\\': case '.': case '*': case '+': case '?': case '(': case ')': case '[': case ']': case '{': case '}': case '|': case '/': return true; default: break; } return false; } void RegExpParser::ReportError(const char* message) { // Zip to the end to make sure the no more input is read. current_ = kEndMarker; next_pos_ = in().Length(); // Throw a FormatException on parsing failures. Array& args = Array::Handle(); String& str = String::Handle(); args ^= Array::New(3); str ^= String::New(message); args.SetAt(0, str); args.SetAt(1, Symbols::Blank()); args.SetAt(2, in()); str ^= String::ConcatAll(args); args ^= Array::New(1); args.SetAt(0, str); Exceptions::ThrowByType(Exceptions::kFormat, args); UNREACHABLE(); } // Pattern :: // Disjunction RegExpTree* RegExpParser::ParsePattern() { RegExpTree* result = ParseDisjunction(); PatchNamedBackReferences(); ASSERT(!has_more()); // If the result of parsing is a literal string atom, and it has the // same length as the input, then the atom is identical to the input. if (result->IsAtom() && result->AsAtom()->length() == in().Length()) { simple_ = true; } return result; } // Used for error messages where we would have fallen back on treating an // escape as the identity escape, but we are in Unicode mode. static const char* kUnicodeIdentity = "Invalid identity escape in Unicode pattern"; // Disjunction :: // Alternative // Alternative | Disjunction // Alternative :: // [empty] // Term Alternative // Term :: // Assertion // Atom // Atom Quantifier RegExpTree* RegExpParser::ParseDisjunction() { // Used to store current state while parsing subexpressions. RegExpParserState initial_state(nullptr, INITIAL, RegExpLookaround::LOOKAHEAD, 0, nullptr, top_level_flags_, Z); RegExpParserState* stored_state = &initial_state; // Cache the builder in a local variable for quick access. RegExpBuilder* builder = initial_state.builder(); while (true) { switch (current()) { case kEndMarker: if (stored_state->IsSubexpression()) { // Inside a parenthesized group when hitting end of input. ReportError("Unterminated group"); UNREACHABLE(); } ASSERT(INITIAL == stored_state->group_type()); // Parsing completed successfully. return builder->ToRegExp(); case ')': { if (!stored_state->IsSubexpression()) { ReportError("Unmatched ')'"); UNREACHABLE(); } ASSERT(INITIAL != stored_state->group_type()); Advance(); // End disjunction parsing and convert builder content to new single // regexp atom. RegExpTree* body = builder->ToRegExp(); intptr_t end_capture_index = captures_started(); intptr_t capture_index = stored_state->capture_index(); SubexpressionType group_type = stored_state->group_type(); // Build result of subexpression. if (group_type == CAPTURE) { if (stored_state->IsNamedCapture()) { CreateNamedCaptureAtIndex(stored_state->capture_name(), capture_index); } RegExpCapture* capture = GetCapture(capture_index); capture->set_body(body); body = capture; } else if (group_type != GROUPING) { ASSERT(group_type == POSITIVE_LOOKAROUND || group_type == NEGATIVE_LOOKAROUND); bool is_positive = (group_type == POSITIVE_LOOKAROUND); body = new (Z) RegExpLookaround( body, is_positive, end_capture_index - capture_index, capture_index, stored_state->lookaround_type()); } // Restore previous state. stored_state = stored_state->previous_state(); builder = stored_state->builder(); builder->AddAtom(body); // For compatibility with JSC and ES3, we allow quantifiers after // lookaheads, and break in all cases. break; } case '|': { Advance(); builder->NewAlternative(); continue; } case '*': case '+': case '?': ReportError("Nothing to repeat"); UNREACHABLE(); case '^': { Advance(); if (builder->is_multi_line()) { builder->AddAssertion(new (Z) RegExpAssertion( RegExpAssertion::START_OF_LINE, builder->flags())); } else { builder->AddAssertion(new (Z) RegExpAssertion( RegExpAssertion::START_OF_INPUT, builder->flags())); set_contains_anchor(); } continue; } case '$': { Advance(); RegExpAssertion::AssertionType assertion_type = builder->is_multi_line() ? RegExpAssertion::END_OF_LINE : RegExpAssertion::END_OF_INPUT; builder->AddAssertion( new (Z) RegExpAssertion(assertion_type, builder->flags())); continue; } case '.': { Advance(); auto ranges = new (Z) ZoneGrowableArray(2); if (builder->is_dot_all()) { // Everything. CharacterRange::AddClassEscape( '*', ranges, /*add_unicode_case_equivalents=*/false); } else { // everything except \x0a, \x0d, \u2028 and \u2029 CharacterRange::AddClassEscape( '.', ranges, /*add_unicode_case_equivalents=*/false); } RegExpCharacterClass* cc = new (Z) RegExpCharacterClass(ranges, builder->flags()); builder->AddCharacterClass(cc); break; } case '(': { stored_state = ParseOpenParenthesis(stored_state); builder = stored_state->builder(); continue; } case '[': { RegExpTree* atom = ParseCharacterClass(builder); builder->AddCharacterClass(atom->AsCharacterClass()); break; } // Atom :: // \ AtomEscape case '\\': switch (Next()) { case kEndMarker: ReportError("\\ at end of pattern"); UNREACHABLE(); case 'b': Advance(2); builder->AddAssertion(new (Z) RegExpAssertion( RegExpAssertion::BOUNDARY, builder->flags())); continue; case 'B': Advance(2); builder->AddAssertion(new (Z) RegExpAssertion( RegExpAssertion::NON_BOUNDARY, builder->flags())); continue; // AtomEscape :: // CharacterClassEscape // // CharacterClassEscape :: one of // d D s S w W case 'd': case 'D': case 's': case 'S': case 'w': case 'W': { uint32_t c = Next(); Advance(2); auto ranges = new (Z) ZoneGrowableArray(2); CharacterRange::AddClassEscape( c, ranges, is_unicode() && builder->ignore_case()); RegExpCharacterClass* cc = new (Z) RegExpCharacterClass(ranges, builder->flags()); builder->AddCharacterClass(cc); break; } case 'p': case 'P': { uint32_t p = Next(); Advance(2); if (is_unicode()) { auto name_1 = new (Z) ZoneGrowableArray(); auto name_2 = new (Z) ZoneGrowableArray(); auto ranges = new (Z) ZoneGrowableArray(2); if (ParsePropertyClassName(name_1, name_2)) { if (AddPropertyClassRange(ranges, p == 'P', name_1, name_2)) { RegExpCharacterClass* cc = new (Z) RegExpCharacterClass(ranges, builder->flags()); builder->AddCharacterClass(cc); break; } } ReportError("Invalid property name"); UNREACHABLE(); } else { builder->AddCharacter(p); } break; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { intptr_t index = 0; if (ParseBackReferenceIndex(&index)) { if (stored_state->IsInsideCaptureGroup(index)) { // The back reference is inside the capture group it refers to. // Nothing can possibly have been captured yet, so we use empty // instead. This ensures that, when checking a back reference, // the capture registers of the referenced capture are either // both set or both cleared. builder->AddEmpty(); } else { RegExpCapture* capture = GetCapture(index); RegExpTree* atom = new (Z) RegExpBackReference(capture, builder->flags()); builder->AddAtom(atom); } break; } // With /u, no identity escapes except for syntax characters are // allowed. Otherwise, all identity escapes are allowed. if (is_unicode()) { ReportError(kUnicodeIdentity); UNREACHABLE(); } uint32_t first_digit = Next(); if (first_digit == '8' || first_digit == '9') { builder->AddCharacter(first_digit); Advance(2); break; } } FALL_THROUGH; case '0': { Advance(); if (is_unicode() && Next() >= '0' && Next() <= '9') { // With /u, decimal escape with leading 0 are not parsed as octal. ReportError("Invalid decimal escape"); UNREACHABLE(); } uint32_t octal = ParseOctalLiteral(); builder->AddCharacter(octal); break; } // ControlEscape :: one of // f n r t v case 'f': Advance(2); builder->AddCharacter('\f'); break; case 'n': Advance(2); builder->AddCharacter('\n'); break; case 'r': Advance(2); builder->AddCharacter('\r'); break; case 't': Advance(2); builder->AddCharacter('\t'); break; case 'v': Advance(2); builder->AddCharacter('\v'); break; case 'c': { Advance(); uint32_t controlLetter = Next(); // Special case if it is an ASCII letter. // Convert lower case letters to uppercase. uint32_t letter = controlLetter & ~('a' ^ 'A'); if (letter < 'A' || 'Z' < letter) { // controlLetter is not in range 'A'-'Z' or 'a'-'z'. // This is outside the specification. We match JSC in // reading the backslash as a literal character instead // of as starting an escape. if (is_unicode()) { // With /u, invalid escapes are not treated as identity escapes. ReportError(kUnicodeIdentity); UNREACHABLE(); } builder->AddCharacter('\\'); } else { Advance(2); builder->AddCharacter(controlLetter & 0x1f); } break; } case 'x': { Advance(2); uint32_t value; if (ParseHexEscape(2, &value)) { builder->AddCharacter(value); } else if (!is_unicode()) { builder->AddCharacter('x'); } else { // With /u, invalid escapes are not treated as identity escapes. ReportError(kUnicodeIdentity); UNREACHABLE(); } break; } case 'u': { Advance(2); uint32_t value; if (ParseUnicodeEscape(&value)) { builder->AddEscapedUnicodeCharacter(value); } else if (!is_unicode()) { builder->AddCharacter('u'); } else { // With /u, invalid escapes are not treated as identity escapes. ReportError(kUnicodeIdentity); UNREACHABLE(); } break; } case 'k': // Either an identity escape or a named back-reference. The two // interpretations are mutually exclusive: '\k' is interpreted as // an identity escape for non-Unicode patterns without named // capture groups, and as the beginning of a named back-reference // in all other cases. if (is_unicode() || HasNamedCaptures()) { Advance(2); ParseNamedBackReference(builder, stored_state); break; } FALL_THROUGH; default: Advance(); // With the unicode flag, no identity escapes except for syntax // characters are allowed. Otherwise, all identity escapes are // allowed. if (!is_unicode() || IsSyntaxCharacterOrSlash(current())) { builder->AddCharacter(current()); Advance(); } else { ReportError(kUnicodeIdentity); UNREACHABLE(); } break; } break; case '{': { intptr_t dummy; if (ParseIntervalQuantifier(&dummy, &dummy)) { ReportError("Nothing to repeat"); UNREACHABLE(); } } FALL_THROUGH; case '}': case ']': if (is_unicode()) { ReportError("Lone quantifier brackets"); UNREACHABLE(); } FALL_THROUGH; default: builder->AddUnicodeCharacter(current()); Advance(); break; } // end switch(current()) intptr_t min; intptr_t max; switch (current()) { // QuantifierPrefix :: // * // + // ? // { case '*': min = 0; max = RegExpTree::kInfinity; Advance(); break; case '+': min = 1; max = RegExpTree::kInfinity; Advance(); break; case '?': min = 0; max = 1; Advance(); break; case '{': if (ParseIntervalQuantifier(&min, &max)) { if (max < min) { ReportError("numbers out of order in {} quantifier."); UNREACHABLE(); } break; } else { continue; } default: continue; } RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; if (current() == '?') { quantifier_type = RegExpQuantifier::NON_GREEDY; Advance(); } else if (FLAG_regexp_possessive_quantifier && current() == '+') { // FLAG_regexp_possessive_quantifier is a debug-only flag. quantifier_type = RegExpQuantifier::POSSESSIVE; Advance(); } if (!builder->AddQuantifierToAtom(min, max, quantifier_type)) { ReportError("invalid quantifier."); UNREACHABLE(); } } } #ifdef DEBUG // Currently only used in an ASSERT. static bool IsSpecialClassEscape(uint32_t c) { switch (c) { case 'd': case 'D': case 's': case 'S': case 'w': case 'W': return true; default: return false; } } #endif RegExpParser::RegExpParserState* RegExpParser::ParseOpenParenthesis( RegExpParserState* state) { RegExpLookaround::Type lookaround_type = state->lookaround_type(); bool is_named_capture = false; const RegExpCaptureName* capture_name = nullptr; SubexpressionType subexpr_type = CAPTURE; Advance(); if (current() == '?') { switch (Next()) { case ':': Advance(2); subexpr_type = GROUPING; break; case '=': Advance(2); lookaround_type = RegExpLookaround::LOOKAHEAD; subexpr_type = POSITIVE_LOOKAROUND; break; case '!': Advance(2); lookaround_type = RegExpLookaround::LOOKAHEAD; subexpr_type = NEGATIVE_LOOKAROUND; break; case '<': Advance(); if (Next() == '=') { Advance(2); lookaround_type = RegExpLookaround::LOOKBEHIND; subexpr_type = POSITIVE_LOOKAROUND; break; } else if (Next() == '!') { Advance(2); lookaround_type = RegExpLookaround::LOOKBEHIND; subexpr_type = NEGATIVE_LOOKAROUND; break; } is_named_capture = true; has_named_captures_ = true; Advance(); break; default: ReportError("Invalid group"); UNREACHABLE(); } } if (subexpr_type == CAPTURE) { if (captures_started_ >= kMaxCaptures) { ReportError("Too many captures"); UNREACHABLE(); } captures_started_++; if (is_named_capture) { capture_name = ParseCaptureGroupName(); } } // Store current state and begin new disjunction parsing. return new (Z) RegExpParserState(state, subexpr_type, lookaround_type, captures_started_, capture_name, state->builder()->flags(), Z); } // In order to know whether an escape is a backreference or not we have to scan // the entire regexp and find the number of capturing parentheses. However we // don't want to scan the regexp twice unless it is necessary. This mini-parser // is called when needed. It can see the difference between capturing and // noncapturing parentheses and can skip character classes and backslash-escaped // characters. void RegExpParser::ScanForCaptures() { ASSERT(!is_scanned_for_captures_); const intptr_t saved_position = position(); // Start with captures started previous to current position intptr_t capture_count = captures_started(); // Add count of captures after this position. uintptr_t n; while ((n = current()) != kEndMarker) { Advance(); switch (n) { case '\\': Advance(); break; case '[': { uintptr_t c; while ((c = current()) != kEndMarker) { Advance(); if (c == '\\') { Advance(); } else { if (c == ']') break; } } break; } case '(': // At this point we could be in // * a non-capturing group '(:', // * a lookbehind assertion '(?<=' '(? kMaxCaptures) { Reset(start); return false; } Advance(); } else { break; } } if (value > captures_started()) { if (!is_scanned_for_captures_) ScanForCaptures(); if (value > capture_count_) { Reset(start); return false; } } *index_out = value; return true; } namespace { static inline constexpr bool IsAsciiIdentifierPart(uint32_t ch) { return Utils::IsAlphaNumeric(ch) || ch == '_' || ch == '$'; } // ES#sec-names-and-keywords Names and Keywords // UnicodeIDStart, '$', '_' and '\' static bool IsIdentifierStartSlow(uint32_t c) { // cannot use u_isIDStart because it does not work for // Other_ID_Start characters. return u_hasBinaryProperty(c, UCHAR_ID_START) || (c < 0x60 && (c == '$' || c == '\\' || c == '_')); } // ES#sec-names-and-keywords Names and Keywords // UnicodeIDContinue, '$', '_', '\', ZWJ, and ZWNJ static bool IsIdentifierPartSlow(uint32_t c) { const uint32_t kZeroWidthNonJoiner = 0x200C; const uint32_t kZeroWidthJoiner = 0x200D; // Can't use u_isIDPart because it does not work for // Other_ID_Continue characters. return u_hasBinaryProperty(c, UCHAR_ID_CONTINUE) || (c < 0x60 && (c == '$' || c == '\\' || c == '_')) || c == kZeroWidthNonJoiner || c == kZeroWidthJoiner; } static inline bool IsIdentifierStart(uint32_t c) { if (c > 127) return IsIdentifierStartSlow(c); return IsAsciiIdentifierPart(c) && !Utils::IsDecimalDigit(c); } static inline bool IsIdentifierPart(uint32_t c) { if (c > 127) return IsIdentifierPartSlow(c); return IsAsciiIdentifierPart(c); } static bool IsSameName(const RegExpCaptureName* name1, const RegExpCaptureName* name2) { if (name1->length() != name2->length()) return false; for (intptr_t i = 0; i < name1->length(); i++) { if (name1->At(i) != name2->At(i)) return false; } return true; } } // end namespace static void PushCodeUnit(RegExpCaptureName* v, uint32_t code_unit) { if (code_unit <= Utf16::kMaxCodeUnit) { v->Add(code_unit); } else { uint16_t units[2]; Utf16::Encode(code_unit, units); v->Add(units[0]); v->Add(units[1]); } } const RegExpCaptureName* RegExpParser::ParseCaptureGroupName() { auto name = new (Z) RegExpCaptureName(); bool at_start = true; while (true) { uint32_t c = current(); Advance(); // Convert unicode escapes. if (c == '\\' && current() == 'u') { Advance(); if (!ParseUnicodeEscape(&c)) { ReportError("Invalid Unicode escape sequence"); UNREACHABLE(); } } // The backslash char is misclassified as both ID_Start and ID_Continue. if (c == '\\') { ReportError("Invalid capture group name"); UNREACHABLE(); } if (at_start) { if (!IsIdentifierStart(c)) { ReportError("Invalid capture group name"); UNREACHABLE(); } PushCodeUnit(name, c); at_start = false; } else { if (c == '>') { break; } else if (IsIdentifierPart(c)) { PushCodeUnit(name, c); } else { ReportError("Invalid capture group name"); UNREACHABLE(); } } } return name; } intptr_t RegExpParser::GetNamedCaptureIndex(const RegExpCaptureName* name) { for (const auto& capture : *named_captures_) { if (IsSameName(name, capture->name())) return capture->index(); } return -1; } void RegExpParser::CreateNamedCaptureAtIndex(const RegExpCaptureName* name, intptr_t index) { ASSERT(0 < index && index <= captures_started_); ASSERT(name != nullptr); if (named_captures_ == nullptr) { named_captures_ = new (Z) ZoneGrowableArray(1); } else { // Check for duplicates and bail if we find any. Currently O(n^2). if (GetNamedCaptureIndex(name) >= 0) { ReportError("Duplicate capture group name"); UNREACHABLE(); } } RegExpCapture* capture = GetCapture(index); ASSERT(capture->name() == nullptr); capture->set_name(name); named_captures_->Add(capture); } bool RegExpParser::ParseNamedBackReference(RegExpBuilder* builder, RegExpParserState* state) { // The parser is assumed to be on the '<' in \k. if (current() != '<') { ReportError("Invalid named reference"); UNREACHABLE(); } Advance(); const RegExpCaptureName* name = ParseCaptureGroupName(); if (name == nullptr) { return false; } if (state->IsInsideCaptureGroup(name)) { builder->AddEmpty(); } else { RegExpBackReference* atom = new (Z) RegExpBackReference(builder->flags()); atom->set_name(name); builder->AddAtom(atom); if (named_back_references_ == nullptr) { named_back_references_ = new (Z) ZoneGrowableArray(1); } named_back_references_->Add(atom); } return true; } void RegExpParser::PatchNamedBackReferences() { if (named_back_references_ == nullptr) return; if (named_captures_ == nullptr) { ReportError("Invalid named capture referenced"); return; } // Look up and patch the actual capture for each named back reference. // Currently O(n^2), optimize if necessary. for (intptr_t i = 0; i < named_back_references_->length(); i++) { RegExpBackReference* ref = named_back_references_->At(i); intptr_t index = GetNamedCaptureIndex(ref->name()); if (index < 0) { ReportError("Invalid named capture referenced"); UNREACHABLE(); } ref->set_capture(GetCapture(index)); } } RegExpCapture* RegExpParser::GetCapture(intptr_t index) { // The index for the capture groups are one-based. Its index in the list is // zero-based. const intptr_t know_captures = is_scanned_for_captures_ ? capture_count_ : captures_started_; ASSERT(index <= know_captures); if (captures_ == nullptr) { captures_ = new (Z) ZoneGrowableArray(know_captures); } while (captures_->length() < know_captures) { captures_->Add(new (Z) RegExpCapture(captures_->length() + 1)); } return captures_->At(index - 1); } ArrayPtr RegExpParser::CreateCaptureNameMap() { if (named_captures_ == nullptr || named_captures_->is_empty()) { return Array::null(); } const intptr_t len = named_captures_->length() * 2; const Array& array = Array::Handle(Array::New(len)); auto& name = String::Handle(); auto& smi = Smi::Handle(); for (intptr_t i = 0; i < named_captures_->length(); i++) { RegExpCapture* capture = named_captures_->At(i); name = String::FromUTF16(capture->name()->data(), capture->name()->length()); smi = Smi::New(capture->index()); array.SetAt(i * 2, name); array.SetAt(i * 2 + 1, smi); } return array.ptr(); } bool RegExpParser::HasNamedCaptures() { if (has_named_captures_ || is_scanned_for_captures_) { return has_named_captures_; } ScanForCaptures(); ASSERT(is_scanned_for_captures_); return has_named_captures_; } bool RegExpParser::RegExpParserState::IsInsideCaptureGroup(intptr_t index) { for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { if (s->group_type() != CAPTURE) continue; // Return true if we found the matching capture index. if (index == s->capture_index()) return true; // Abort if index is larger than what has been parsed up till this state. if (index > s->capture_index()) return false; } return false; } bool RegExpParser::RegExpParserState::IsInsideCaptureGroup( const RegExpCaptureName* name) { ASSERT(name != nullptr); for (RegExpParserState* s = this; s != nullptr; s = s->previous_state()) { if (s->capture_name() == nullptr) continue; if (IsSameName(s->capture_name(), name)) return true; } return false; } // QuantifierPrefix :: // { DecimalDigits } // { DecimalDigits , } // { DecimalDigits , DecimalDigits } // // Returns true if parsing succeeds, and set the min_out and max_out // values. Values are truncated to RegExpTree::kInfinity if they overflow. bool RegExpParser::ParseIntervalQuantifier(intptr_t* min_out, intptr_t* max_out) { ASSERT(current() == '{'); intptr_t start = position(); Advance(); intptr_t min = 0; if (!Utils::IsDecimalDigit(current())) { Reset(start); return false; } while (Utils::IsDecimalDigit(current())) { intptr_t next = current() - '0'; if (min > (RegExpTree::kInfinity - next) / 10) { // Overflow. Skip past remaining decimal digits and return -1. do { Advance(); } while (Utils::IsDecimalDigit(current())); min = RegExpTree::kInfinity; break; } min = 10 * min + next; Advance(); } intptr_t max = 0; if (current() == '}') { max = min; Advance(); } else if (current() == ',') { Advance(); if (current() == '}') { max = RegExpTree::kInfinity; Advance(); } else { while (Utils::IsDecimalDigit(current())) { intptr_t next = current() - '0'; if (max > (RegExpTree::kInfinity - next) / 10) { do { Advance(); } while (Utils::IsDecimalDigit(current())); max = RegExpTree::kInfinity; break; } max = 10 * max + next; Advance(); } if (current() != '}') { Reset(start); return false; } Advance(); } } else { Reset(start); return false; } *min_out = min; *max_out = max; return true; } uint32_t RegExpParser::ParseOctalLiteral() { ASSERT(('0' <= current() && current() <= '7') || current() == kEndMarker); // For compatibility with some other browsers (not all), we parse // up to three octal digits with a value below 256. uint32_t value = current() - '0'; Advance(); if ('0' <= current() && current() <= '7') { value = value * 8 + current() - '0'; Advance(); if (value < 32 && '0' <= current() && current() <= '7') { value = value * 8 + current() - '0'; Advance(); } } return value; } // Returns the value (0 .. 15) of a hexadecimal character c. // If c is not a legal hexadecimal character, returns a value < 0. static inline intptr_t HexValue(uint32_t c) { c -= '0'; if (static_cast(c) <= 9) return c; c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. if (static_cast(c) <= 5) return c + 10; return -1; } bool RegExpParser::ParseHexEscape(intptr_t length, uint32_t* value) { intptr_t start = position(); uint32_t val = 0; bool done = false; for (intptr_t i = 0; !done; i++) { uint32_t c = current(); intptr_t d = HexValue(c); if (d < 0) { Reset(start); return false; } val = val * 16 + d; Advance(); if (i == length - 1) { done = true; } } *value = val; return true; } // This parses RegExpUnicodeEscapeSequence as described in ECMA262. bool RegExpParser::ParseUnicodeEscape(uint32_t* value) { // Accept both \uxxxx and \u{xxxxxx} (if harmony unicode escapes are // allowed). In the latter case, the number of hex digits between { } is // arbitrary. \ and u have already been read. if (current() == '{' && is_unicode()) { int start = position(); Advance(); if (ParseUnlimitedLengthHexNumber(Utf::kMaxCodePoint, value)) { if (current() == '}') { Advance(); return true; } } Reset(start); return false; } // \u but no {, or \u{...} escapes not allowed. bool result = ParseHexEscape(4, value); if (result && is_unicode() && Utf16::IsLeadSurrogate(*value) && current() == '\\') { // Attempt to read trail surrogate. int start = position(); if (Next() == 'u') { Advance(2); uint32_t trail; if (ParseHexEscape(4, &trail) && Utf16::IsTrailSurrogate(trail)) { *value = Utf16::Decode(static_cast(*value), static_cast(trail)); return true; } } Reset(start); } return result; } namespace { bool IsExactPropertyAlias(const char* property_name, UProperty property) { const char* short_name = u_getPropertyName(property, U_SHORT_PROPERTY_NAME); if (short_name != nullptr && strcmp(property_name, short_name) == 0) { return true; } for (int i = 0;; i++) { const char* long_name = u_getPropertyName( property, static_cast(U_LONG_PROPERTY_NAME + i)); if (long_name == nullptr) break; if (strcmp(property_name, long_name) == 0) return true; } return false; } bool IsExactPropertyValueAlias(const char* property_value_name, UProperty property, int32_t property_value) { const char* short_name = u_getPropertyValueName(property, property_value, U_SHORT_PROPERTY_NAME); if (short_name != nullptr && strcmp(property_value_name, short_name) == 0) { return true; } for (int i = 0;; i++) { const char* long_name = u_getPropertyValueName( property, property_value, static_cast(U_LONG_PROPERTY_NAME + i)); if (long_name == nullptr) break; if (strcmp(property_value_name, long_name) == 0) return true; } return false; } bool LookupPropertyValueName(UProperty property, const char* property_value_name, bool negate, ZoneGrowableArray* result) { UProperty property_for_lookup = property; if (property_for_lookup == UCHAR_SCRIPT_EXTENSIONS) { // For the property Script_Extensions, we have to do the property value // name lookup as if the property is Script. property_for_lookup = UCHAR_SCRIPT; } int32_t property_value = u_getPropertyValueEnum(property_for_lookup, property_value_name); if (property_value == UCHAR_INVALID_CODE) return false; // We require the property name to match exactly to one of the property value // aliases. However, u_getPropertyValueEnum uses loose matching. if (!IsExactPropertyValueAlias(property_value_name, property_for_lookup, property_value)) { return false; } UErrorCode ec = U_ZERO_ERROR; icu::UnicodeSet set; set.applyIntPropertyValue(property, property_value, ec); bool success = ec == U_ZERO_ERROR && (set.isEmpty() == 0); if (success) { set.removeAllStrings(); if (negate) set.complement(); for (int i = 0; i < set.getRangeCount(); i++) { result->Add( CharacterRange::Range(set.getRangeStart(i), set.getRangeEnd(i))); } } return success; } template inline bool NameEquals(const char* name, const char (&literal)[N]) { return strncmp(name, literal, N + 1) == 0; } bool LookupSpecialPropertyValueName(const char* name, ZoneGrowableArray* result, bool negate) { if (NameEquals(name, "Any")) { if (negate) { // Leave the list of character ranges empty, since the negation of 'Any' // is the empty set. } else { result->Add(CharacterRange::Everything()); } } else if (NameEquals(name, "ASCII")) { result->Add(negate ? CharacterRange::Range(0x80, Utf::kMaxCodePoint) : CharacterRange::Range(0x0, 0x7F)); } else if (NameEquals(name, "Assigned")) { return LookupPropertyValueName(UCHAR_GENERAL_CATEGORY, "Unassigned", !negate, result); } else { return false; } return true; } // Explicitly list supported binary properties. The spec forbids supporting // properties outside of this set to ensure interoperability. bool IsSupportedBinaryProperty(UProperty property) { switch (property) { case UCHAR_ALPHABETIC: // 'Any' is not supported by ICU. See LookupSpecialPropertyValueName. // 'ASCII' is not supported by ICU. See LookupSpecialPropertyValueName. case UCHAR_ASCII_HEX_DIGIT: // 'Assigned' is not supported by ICU. See LookupSpecialPropertyValueName. case UCHAR_BIDI_CONTROL: case UCHAR_BIDI_MIRRORED: case UCHAR_CASE_IGNORABLE: case UCHAR_CASED: case UCHAR_CHANGES_WHEN_CASEFOLDED: case UCHAR_CHANGES_WHEN_CASEMAPPED: case UCHAR_CHANGES_WHEN_LOWERCASED: case UCHAR_CHANGES_WHEN_NFKC_CASEFOLDED: case UCHAR_CHANGES_WHEN_TITLECASED: case UCHAR_CHANGES_WHEN_UPPERCASED: case UCHAR_DASH: case UCHAR_DEFAULT_IGNORABLE_CODE_POINT: case UCHAR_DEPRECATED: case UCHAR_DIACRITIC: case UCHAR_EMOJI: case UCHAR_EMOJI_COMPONENT: case UCHAR_EMOJI_MODIFIER_BASE: case UCHAR_EMOJI_MODIFIER: case UCHAR_EMOJI_PRESENTATION: case UCHAR_EXTENDED_PICTOGRAPHIC: case UCHAR_EXTENDER: case UCHAR_GRAPHEME_BASE: case UCHAR_GRAPHEME_EXTEND: case UCHAR_HEX_DIGIT: case UCHAR_ID_CONTINUE: case UCHAR_ID_START: case UCHAR_IDEOGRAPHIC: case UCHAR_IDS_BINARY_OPERATOR: case UCHAR_IDS_TRINARY_OPERATOR: case UCHAR_JOIN_CONTROL: case UCHAR_LOGICAL_ORDER_EXCEPTION: case UCHAR_LOWERCASE: case UCHAR_MATH: case UCHAR_NONCHARACTER_CODE_POINT: case UCHAR_PATTERN_SYNTAX: case UCHAR_PATTERN_WHITE_SPACE: case UCHAR_QUOTATION_MARK: case UCHAR_RADICAL: case UCHAR_REGIONAL_INDICATOR: case UCHAR_S_TERM: case UCHAR_SOFT_DOTTED: case UCHAR_TERMINAL_PUNCTUATION: case UCHAR_UNIFIED_IDEOGRAPH: case UCHAR_UPPERCASE: case UCHAR_VARIATION_SELECTOR: case UCHAR_WHITE_SPACE: case UCHAR_XID_CONTINUE: case UCHAR_XID_START: return true; default: break; } return false; } bool IsUnicodePropertyValueCharacter(char c) { // https://tc39.github.io/proposal-regexp-unicode-property-escapes/ // // Note that using this to validate each parsed char is quite conservative. // A possible alternative solution would be to only ensure the parsed // property name/value candidate string does not contain '\0' characters and // let ICU lookups trigger the final failure. if (Utils::IsAlphaNumeric(c)) return true; return (c == '_'); } } // anonymous namespace bool RegExpParser::ParsePropertyClassName(ZoneGrowableArray* name_1, ZoneGrowableArray* name_2) { ASSERT(name_1->is_empty()); ASSERT(name_2->is_empty()); // Parse the property class as follows: // - In \p{name}, 'name' is interpreted // - either as a general category property value name. // - or as a binary property name. // - In \p{name=value}, 'name' is interpreted as an enumerated property name, // and 'value' is interpreted as one of the available property value names. // - Aliases in PropertyAlias.txt and PropertyValueAlias.txt can be used. // - Loose matching is not applied. if (current() == '{') { // Parse \p{[PropertyName=]PropertyNameValue} for (Advance(); current() != '}' && current() != '='; Advance()) { if (!IsUnicodePropertyValueCharacter(current())) return false; if (!has_next()) return false; name_1->Add(static_cast(current())); } if (current() == '=') { for (Advance(); current() != '}'; Advance()) { if (!IsUnicodePropertyValueCharacter(current())) return false; if (!has_next()) return false; name_2->Add(static_cast(current())); } name_2->Add(0); // null-terminate string. } } else { return false; } Advance(); name_1->Add(0); // null-terminate string. ASSERT(static_cast(name_1->length() - 1) == strlen(name_1->data())); ASSERT(name_2->is_empty() || static_cast(name_2->length() - 1) == strlen(name_2->data())); return true; } bool RegExpParser::AddPropertyClassRange( ZoneGrowableArray* add_to, bool negate, ZoneGrowableArray* name_1, ZoneGrowableArray* name_2) { ASSERT(name_1->At(name_1->length() - 1) == '\0'); ASSERT(name_2->is_empty() || name_2->At(name_2->length() - 1) == '\0'); if (name_2->is_empty()) { // First attempt to interpret as general category property value name. const char* name = name_1->data(); if (LookupPropertyValueName(UCHAR_GENERAL_CATEGORY_MASK, name, negate, add_to)) { return true; } // Interpret "Any", "ASCII", and "Assigned". if (LookupSpecialPropertyValueName(name, add_to, negate)) { return true; } // Then attempt to interpret as binary property name with value name 'Y'. UProperty property = u_getPropertyEnum(name); if (!IsSupportedBinaryProperty(property)) return false; if (!IsExactPropertyAlias(name, property)) return false; return LookupPropertyValueName(property, negate ? "N" : "Y", false, add_to); } else { // Both property name and value name are specified. Attempt to interpret // the property name as enumerated property. const char* property_name = name_1->data(); const char* value_name = name_2->data(); UProperty property = u_getPropertyEnum(property_name); if (!IsExactPropertyAlias(property_name, property)) return false; if (property == UCHAR_GENERAL_CATEGORY) { // We want to allow aggregate value names such as "Letter". property = UCHAR_GENERAL_CATEGORY_MASK; } else if (property != UCHAR_SCRIPT && property != UCHAR_SCRIPT_EXTENSIONS) { return false; } return LookupPropertyValueName(property, value_name, negate, add_to); } } bool RegExpParser::ParseUnlimitedLengthHexNumber(uint32_t max_value, uint32_t* value) { uint32_t x = 0; int d = HexValue(current()); if (d < 0) { return false; } while (d >= 0) { x = x * 16 + d; if (x > max_value) { return false; } Advance(); d = HexValue(current()); } *value = x; return true; } uint32_t RegExpParser::ParseClassCharacterEscape() { ASSERT(current() == '\\'); DEBUG_ASSERT(has_next() && !IsSpecialClassEscape(Next())); Advance(); switch (current()) { case 'b': Advance(); return '\b'; // ControlEscape :: one of // f n r t v case 'f': Advance(); return '\f'; case 'n': Advance(); return '\n'; case 'r': Advance(); return '\r'; case 't': Advance(); return '\t'; case 'v': Advance(); return '\v'; case 'c': { uint32_t controlLetter = Next(); uint32_t letter = controlLetter & ~('A' ^ 'a'); // For compatibility with JSC, inside a character class // we also accept digits and underscore as control characters. if (letter >= 'A' && letter <= 'Z') { Advance(2); // Control letters mapped to ASCII control characters in the range // 0x00-0x1f. return controlLetter & 0x1f; } if (is_unicode()) { // With /u, \c# or \c_ are invalid. ReportError("Invalid class escape"); UNREACHABLE(); } if (Utils::IsDecimalDigit(controlLetter) || controlLetter == '_') { Advance(2); return controlLetter & 0x1f; } // We match JSC in reading the backslash as a literal // character instead of as starting an escape. return '\\'; } case '0': // With /u, \0 is interpreted as NUL if not followed by another digit. if (is_unicode() && !(Next() >= '0' && Next() <= '9')) { Advance(); return 0; } FALL_THROUGH; case '1': case '2': case '3': case '4': case '5': case '6': case '7': // For compatibility, we interpret a decimal escape that isn't // a back reference (and therefore either \0 or not valid according // to the specification) as a 1..3 digit octal character code. if (is_unicode()) { // With \u, decimal escape is not interpreted as octal character code. ReportError("Invalid class escape"); UNREACHABLE(); } return ParseOctalLiteral(); case 'x': { Advance(); uint32_t value; if (ParseHexEscape(2, &value)) { return value; } if (is_unicode()) { // With \u, invalid escapes are not treated as identity escapes. ReportError("Invalid escape"); UNREACHABLE(); } // If \x is not followed by a two-digit hexadecimal, treat it // as an identity escape. return 'x'; } case 'u': { Advance(); uint32_t value; if (ParseUnicodeEscape(&value)) { return value; } if (is_unicode()) { // With \u, invalid escapes are not treated as identity escapes. ReportError(kUnicodeIdentity); UNREACHABLE(); } // If \u is not followed by a four-digit hexadecimal, treat it // as an identity escape. return 'u'; } default: { // Extended identity escape. We accept any character that hasn't // been matched by a more specific case, not just the subset required // by the ECMAScript specification. uint32_t result = current(); if (!is_unicode() || IsSyntaxCharacterOrSlash(result) || result == '-') { Advance(); return result; } ReportError(kUnicodeIdentity); UNREACHABLE(); } } return 0; } bool RegExpParser::ParseClassEscape(ZoneGrowableArray* ranges, bool add_unicode_case_equivalents, uint32_t* char_out) { uint32_t first = current(); if (first == '\\') { switch (Next()) { case 'w': case 'W': case 'd': case 'D': case 's': case 'S': { CharacterRange::AddClassEscape(static_cast(Next()), ranges, add_unicode_case_equivalents); Advance(2); return true; } case 'p': case 'P': { if (!is_unicode()) break; bool negate = Next() == 'P'; Advance(2); auto name_1 = new (Z) ZoneGrowableArray(); auto name_2 = new (Z) ZoneGrowableArray(); if (!ParsePropertyClassName(name_1, name_2) || !AddPropertyClassRange(ranges, negate, name_1, name_2)) { ReportError("Invalid property name in character class"); UNREACHABLE(); } return true; } case kEndMarker: ReportError("\\ at end of pattern"); UNREACHABLE(); default: break; } *char_out = ParseClassCharacterEscape(); return false; } Advance(); *char_out = first; return false; } RegExpTree* RegExpParser::ParseCharacterClass(const RegExpBuilder* builder) { static const char* kUnterminated = "Unterminated character class"; static const char* kRangeInvalid = "Invalid character class"; static const char* kRangeOutOfOrder = "Range out of order in character class"; ASSERT(current() == '['); Advance(); bool is_negated = false; if (current() == '^') { is_negated = true; Advance(); } ZoneGrowableArray* ranges = new (Z) ZoneGrowableArray(2); bool add_unicode_case_equivalents = is_unicode() && builder->ignore_case(); while (has_more() && current() != ']') { uint32_t char_1 = 0; bool is_class_1 = ParseClassEscape(ranges, add_unicode_case_equivalents, &char_1); if (current() == '-') { Advance(); if (current() == kEndMarker) { // If we reach the end we break out of the loop and let the // following code report an error. break; } else if (current() == ']') { if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); ranges->Add(CharacterRange::Singleton('-')); break; } uint32_t char_2 = 0; bool is_class_2 = ParseClassEscape(ranges, add_unicode_case_equivalents, &char_2); if (is_class_1 || is_class_2) { // Either end is an escaped character class. Treat the '-' verbatim. if (is_unicode()) { // ES2015 21.2.2.15.1 step 1. ReportError(kRangeInvalid); UNREACHABLE(); } if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); ranges->Add(CharacterRange::Singleton('-')); if (!is_class_2) ranges->Add(CharacterRange::Singleton(char_2)); continue; } if (char_1 > char_2) { ReportError(kRangeOutOfOrder); UNREACHABLE(); } ranges->Add(CharacterRange::Range(char_1, char_2)); } else { if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1)); } } if (!has_more()) { ReportError(kUnterminated); UNREACHABLE(); } Advance(); RegExpCharacterClass::CharacterClassFlags character_class_flags = RegExpCharacterClass::DefaultFlags(); if (is_negated) character_class_flags |= RegExpCharacterClass::NEGATED; return new (Z) RegExpCharacterClass(ranges, builder->flags(), character_class_flags); } // ---------------------------------------------------------------------------- // The Parser interface. void RegExpParser::ParseRegExp(const String& input, RegExpFlags flags, RegExpCompileData* result) { ASSERT(result != nullptr); RegExpParser parser(input, &result->error, flags); // Throws an exception if 'input' is not valid. RegExpTree* tree = parser.ParsePattern(); ASSERT(tree != nullptr); ASSERT(result->error.IsNull()); result->tree = tree; intptr_t capture_count = parser.captures_started(); result->simple = tree->IsAtom() && parser.simple() && capture_count == 0; result->contains_anchor = parser.contains_anchor(); result->capture_name_map = parser.CreateCaptureNameMap(); result->capture_count = capture_count; } } // namespace dart