diff --git a/js/src/irregexp/imported/gen-regexp-special-case.cc b/js/src/irregexp/imported/gen-regexp-special-case.cc --- a/js/src/irregexp/imported/gen-regexp-special-case.cc +++ b/js/src/irregexp/imported/gen-regexp-special-case.cc @@ -6,11 +6,10 @@ #include #include #include #include "irregexp/imported/special-case.h" -#include "unicode/usetiter.h" namespace v8 { namespace internal { static const base::uc32 kSurrogateStart = 0xd800; @@ -124,56 +123,10 @@ PrintSet(out, "IgnoreSet", ignore); PrintSet(out, "SpecialAddSet", special_add); } -void PrintUnicodeSpecial(std::ofstream& out) { - icu::UnicodeSet non_simple_folding; - icu::UnicodeSet current; - UErrorCode status = U_ZERO_ERROR; - // Look at all characters except white spaces. - icu::UnicodeSet interestingCP(u"[^[:White_Space:]]", status); - CHECK_EQ(status, U_ZERO_ERROR); - icu::UnicodeSetIterator iter(interestingCP); - while (iter.next()) { - UChar32 c = iter.getCodepoint(); - current.set(c, c); - current.closeOver(USET_CASE_INSENSITIVE).removeAllStrings(); - CHECK(!current.isBogus()); - // Remove characters from the closeover that have a simple case folding. - icu::UnicodeSet toRemove; - icu::UnicodeSetIterator closeOverIter(current); - while (closeOverIter.next()) { - UChar32 closeOverChar = closeOverIter.getCodepoint(); - UChar32 closeOverSCF = u_foldCase(closeOverChar, U_FOLD_CASE_DEFAULT); - if (closeOverChar != closeOverSCF) { - toRemove.add(closeOverChar); - } - } - CHECK(!toRemove.isBogus()); - current.removeAll(toRemove); - - // The current character and its simple case folding are also always OK. - UChar32 scf = u_foldCase(c, U_FOLD_CASE_DEFAULT); - current.remove(c); - current.remove(scf); - - // If there are any characters remaining, they were added due to full case - // foldings and shouldn't match the current charcter according to the spec. - if (!current.isEmpty()) { - // Ensure that the character doesn't have a simple case folding. - // Otherwise the current approach of simply removing the character from - // the set before calling closeOver won't work. - CHECK_EQ(c, scf); - non_simple_folding.add(c); - } - } - CHECK(!non_simple_folding.isBogus()); - - PrintSet(out, "UnicodeNonSimpleCloseOverSet", non_simple_folding); -} - void WriteHeader(const char* header_filename) { std::ofstream out(header_filename); out << std::hex << std::setfill('0') << std::setw(4); out << "// Copyright 2020 the V8 project authors. All rights reserved.\n" << "// Use of this source code is governed by a BSD-style license that\n" @@ -190,11 +143,10 @@ << "#include \"unicode/uniset.h\"\n" << "namespace v8 {\n" << "namespace internal {\n\n"; PrintSpecial(out); - PrintUnicodeSpecial(out); out << "\n" << "} // namespace internal\n" << "} // namespace v8\n" << "#endif // V8_INTL_SUPPORT\n"; diff --git a/js/src/irregexp/imported/regexp-ast.h b/js/src/irregexp/imported/regexp-ast.h --- a/js/src/irregexp/imported/regexp-ast.h +++ b/js/src/irregexp/imported/regexp-ast.h @@ -128,16 +128,10 @@ // for /i, as the semantics for non-unicode mode are slightly different. // See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch Note 4. static void AddUnicodeCaseEquivalents(ZoneList* ranges, Zone* zone); -#ifdef V8_INTL_SUPPORT - // Creates the closeOver of the given UnicodeSet, removing all - // characters/strings that can't be derived via simple case folding. - static void UnicodeSimpleCloseOver(icu::UnicodeSet& set); -#endif // V8_INTL_SUPPORT - bool Contains(base::uc32 i) const { return from_ <= i && i <= to_; } base::uc32 from() const { return from_; } base::uc32 to() const { return to_; } bool IsEverything(base::uc32 max) const { return from_ == 0 && to_ >= max; } bool IsSingleton() const { return from_ == to_; } @@ -309,13 +303,16 @@ public: // NEGATED: The character class is negated and should match everything but // the specified ranges. // CONTAINS_SPLIT_SURROGATE: The character class contains part of a split // surrogate and should not be unicode-desugared (crbug.com/641091). + // IS_CASE_FOLDED: If case folding is required (/i), it was already + // performed on individual ranges and should not be applied again. enum Flag { NEGATED = 1 << 0, CONTAINS_SPLIT_SURROGATE = 1 << 1, + IS_CASE_FOLDED = 1 << 2, }; using ClassRangesFlags = base::Flags; RegExpClassRanges(Zone* zone, ZoneList* ranges, ClassRangesFlags class_ranges_flags = ClassRangesFlags()) @@ -354,10 +351,13 @@ bool is_negated() const { return (class_ranges_flags_ & NEGATED) != 0; } bool contains_split_surrogate() const { return (class_ranges_flags_ & CONTAINS_SPLIT_SURROGATE) != 0; } + bool is_case_folded() const { + return (class_ranges_flags_ & IS_CASE_FOLDED) != 0; + } private: CharacterSet set_; ClassRangesFlags class_ranges_flags_; }; diff --git a/js/src/irregexp/imported/regexp-ast.cc b/js/src/irregexp/imported/regexp-ast.cc --- a/js/src/irregexp/imported/regexp-ast.cc +++ b/js/src/irregexp/imported/regexp-ast.cc @@ -404,14 +404,21 @@ : operation_(op), is_negated_(is_negated), may_contain_strings_(may_contain_strings), operands_(operands) { DCHECK_NOT_NULL(operands); - DCHECK_IMPLIES(is_negated_, !may_contain_strings_); - max_match_ = 0; - for (auto op : *operands) { - max_match_ = std::max(max_match_, op->max_match()); + if (is_negated) { + DCHECK(!may_contain_strings_); + // We don't know anything about max matches for negated classes. + // As there are no strings involved, assume that we can match a unicode + // character (2 code points). + max_match_ = 2; + } else { + max_match_ = 0; + for (auto op : *operands) { + max_match_ = std::max(max_match_, op->max_match()); + } } } // static RegExpClassSetExpression* RegExpClassSetExpression::Empty(Zone* zone, diff --git a/js/src/irregexp/imported/regexp-bytecode-generator.cc b/js/src/irregexp/imported/regexp-bytecode-generator.cc --- a/js/src/irregexp/imported/regexp-bytecode-generator.cc +++ b/js/src/irregexp/imported/regexp-bytecode-generator.cc @@ -381,11 +381,11 @@ if (v8_flags.regexp_peephole_optimization) { array = RegExpBytecodePeepholeOptimization::OptimizeBytecode( isolate_, zone(), source, buffer_.data(), length(), jump_edges_); } else { array = isolate_->factory()->NewByteArray(length()); - Copy(array->GetDataStartAddress()); + Copy(array->begin()); } return array; } diff --git a/js/src/irregexp/imported/regexp-bytecode-peephole.cc b/js/src/irregexp/imported/regexp-bytecode-peephole.cc --- a/js/src/irregexp/imported/regexp-bytecode-peephole.cc +++ b/js/src/irregexp/imported/regexp-bytecode-peephole.cc @@ -1010,17 +1010,17 @@ const uint8_t* bytecode, int length, const ZoneUnorderedMap& jump_edges) { RegExpBytecodePeephole peephole(zone, length, jump_edges); bool did_optimize = peephole.OptimizeBytecode(bytecode, length); Handle array = isolate->factory()->NewByteArray(peephole.Length()); - peephole.CopyOptimizedBytecode(array->GetDataStartAddress()); + peephole.CopyOptimizedBytecode(array->begin()); if (did_optimize && v8_flags.trace_regexp_peephole_optimization) { PrintF("Original Bytecode:\n"); RegExpBytecodeDisassemble(bytecode, length, source->ToCString().get()); PrintF("Optimized Bytecode:\n"); - RegExpBytecodeDisassemble(array->GetDataStartAddress(), peephole.Length(), + RegExpBytecodeDisassemble(array->begin(), peephole.Length(), source->ToCString().get()); } return array; } diff --git a/js/src/irregexp/imported/regexp-compiler-tonode.cc b/js/src/irregexp/imported/regexp-compiler-tonode.cc --- a/js/src/irregexp/imported/regexp-compiler-tonode.cc +++ b/js/src/irregexp/imported/regexp-compiler-tonode.cc @@ -1,11 +1,10 @@ // Copyright 2019 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "irregexp/imported/regexp-compiler.h" - #include "irregexp/imported/regexp.h" #ifdef V8_INTL_SUPPORT #include "irregexp/imported/special-case.h" #include "unicode/locid.h" @@ -416,31 +415,10 @@ return TextNode::CreateForCharacterRanges(zone, range, false, on_success); } } // namespace -#ifdef V8_INTL_SUPPORT -// static -void CharacterRange::UnicodeSimpleCloseOver(icu::UnicodeSet& set) { - // Remove characters for which closeOver() adds full-case-folding equivalents - // because we should work only with simple case folding mappings. - icu::UnicodeSet non_simple = icu::UnicodeSet(set); - non_simple.retainAll(RegExpCaseFolding::UnicodeNonSimpleCloseOverSet()); - set.removeAll(non_simple); - - set.closeOver(USET_CASE_INSENSITIVE); - // Full case folding maps single characters to multiple characters. - // Those are represented as strings in the set. Remove them so that - // we end up with only simple and common case mappings. - set.removeAllStrings(); - - // Add characters that have non-simple case foldings again (they match - // themselves). - set.addAll(non_simple); -} -#endif // V8_INTL_SUPPORT - // static void CharacterRange::AddUnicodeCaseEquivalents(ZoneList* ranges, Zone* zone) { #ifdef V8_INTL_SUPPORT DCHECK(IsCanonical(ranges)); @@ -458,12 +436,11 @@ for (int i = 0; i < ranges->length(); i++) { set.add(ranges->at(i).from(), ranges->at(i).to()); } // Clear the ranges list without freeing the backing store. ranges->Rewind(0); - - UnicodeSimpleCloseOver(set); + set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); for (int i = 0; i < set.getRangeCount(); i++) { ranges->Add(Range(set.getRangeStart(i), set.getRangeEnd(i)), zone); } // No errors and everything we collected have been ranges. Canonicalize(ranges); @@ -474,23 +451,24 @@ RegExpNode* on_success) { set_.Canonicalize(); Zone* const zone = compiler->zone(); ZoneList* ranges = this->ranges(zone); - if (NeedsUnicodeCaseEquivalents(compiler->flags())) { + const bool needs_case_folding = + NeedsUnicodeCaseEquivalents(compiler->flags()) && !is_case_folded(); + if (needs_case_folding) { CharacterRange::AddUnicodeCaseEquivalents(ranges, zone); } if (!IsEitherUnicode(compiler->flags()) || compiler->one_byte() || contains_split_surrogate()) { return zone->New(this, compiler->read_backward(), on_success); } if (is_negated()) { // With /v, character classes are never negated. - // TODO(v8:11935): Change permalink once proposal is in stage 4. - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#sec-compileatom + // https://tc39.es/ecma262/#sec-compileatom // Atom :: CharacterClass // 4. Assert: cc.[[Invert]] is false. // Instead the complement is created when evaluating the class set. // The only exception is the "nothing range" (negated everything), which is // internally created for an empty set. @@ -559,11 +537,16 @@ alternatives->Add(string.second, zone); } } } if (!ranges()->is_empty()) { - alternatives->Add(zone->template New(zone, ranges()), + // In unicode sets mode case folding has to be done at precise locations + // (e.g. before building complements). + // It is therefore the parsers responsibility to case fold (sub-) ranges + // before creating ClassSetOperands. + alternatives->Add(zone->template New( + zone, ranges(), RegExpClassRanges::IS_CASE_FOLDED), zone); } if (empty_string != nullptr) { alternatives->Add(empty_string, zone); } diff --git a/js/src/irregexp/imported/regexp-compiler.cc b/js/src/irregexp/imported/regexp-compiler.cc --- a/js/src/irregexp/imported/regexp-compiler.cc +++ b/js/src/irregexp/imported/regexp-compiler.cc @@ -2865,11 +2865,11 @@ int BoyerMooreLookahead::GetSkipTable(int min_lookahead, int max_lookahead, Handle boolean_skip_table) { const int kSkipArrayEntry = 0; const int kDontSkipArrayEntry = 1; - std::memset(boolean_skip_table->GetDataStartAddress(), kSkipArrayEntry, + std::memset(boolean_skip_table->begin(), kSkipArrayEntry, boolean_skip_table->length()); for (int i = max_lookahead; i >= min_lookahead; i--) { BoyerMoorePositionInfo::Bitset bitset = bitmaps_->at(i)->raw_bitset(); diff --git a/js/src/irregexp/imported/regexp-interpreter.h b/js/src/irregexp/imported/regexp-interpreter.h --- a/js/src/irregexp/imported/regexp-interpreter.h +++ b/js/src/irregexp/imported/regexp-interpreter.h @@ -47,21 +47,22 @@ int* output_registers, int32_t output_register_count, RegExp::CallOrigin call_origin, Isolate* isolate, Address regexp); - static Result MatchInternal(Isolate* isolate, ByteArray code_array, - String subject_string, int* output_registers, - int output_register_count, + static Result MatchInternal(Isolate* isolate, Tagged code_array, + Tagged subject_string, + int* output_registers, int output_register_count, int total_register_count, int start_position, RegExp::CallOrigin call_origin, uint32_t backtrack_limit); private: - static Result Match(Isolate* isolate, JSRegExp regexp, String subject_string, - int* output_registers, int output_register_count, - int start_position, RegExp::CallOrigin call_origin); + static Result Match(Isolate* isolate, Tagged regexp, + Tagged subject_string, int* output_registers, + int output_register_count, int start_position, + RegExp::CallOrigin call_origin); }; } // namespace internal } // namespace v8 diff --git a/js/src/irregexp/imported/regexp-interpreter.cc b/js/src/irregexp/imported/regexp-interpreter.cc --- a/js/src/irregexp/imported/regexp-interpreter.cc +++ b/js/src/irregexp/imported/regexp-interpreter.cc @@ -86,12 +86,11 @@ int32_t Load32Aligned(const uint8_t* pc) { DCHECK_EQ(0, reinterpret_cast(pc) & 3); return *reinterpret_cast(pc); } -// TODO(jgruber): Rename to Load16AlignedUnsigned. -uint32_t Load16Aligned(const uint8_t* pc) { +uint32_t Load16AlignedUnsigned(const uint8_t* pc) { DCHECK_EQ(0, reinterpret_cast(pc) & 1); return *reinterpret_cast(pc); } int32_t Load16AlignedSigned(const uint8_t* pc) { @@ -219,21 +218,21 @@ } template void UpdateCodeAndSubjectReferences( Isolate* isolate, Handle code_array, - Handle subject_string, ByteArray* code_array_out, + Handle subject_string, Tagged* code_array_out, const uint8_t** code_base_out, const uint8_t** pc_out, - String* subject_string_out, + Tagged* subject_string_out, base::Vector* subject_string_vector_out) { DisallowGarbageCollection no_gc; - if (*code_base_out != code_array->GetDataStartAddress()) { + if (*code_base_out != code_array->begin()) { *code_array_out = *code_array; const intptr_t pc_offset = *pc_out - *code_base_out; DCHECK_GT(pc_offset, 0); - *code_base_out = code_array->GetDataStartAddress(); + *code_base_out = code_array->begin(); *pc_out = *code_base_out + pc_offset; } DCHECK(subject_string->IsFlat()); *subject_string_out = *subject_string; @@ -242,12 +241,13 @@ // Runs all pending interrupts and updates unhandlified object references if // necessary. template IrregexpInterpreter::Result HandleInterrupts( - Isolate* isolate, RegExp::CallOrigin call_origin, ByteArray* code_array_out, - String* subject_string_out, const uint8_t** code_base_out, + Isolate* isolate, RegExp::CallOrigin call_origin, + Tagged* code_array_out, Tagged* subject_string_out, + const uint8_t** code_base_out, base::Vector* subject_string_vector_out, const uint8_t** pc_out) { DisallowGarbageCollection no_gc; StackLimitCheck check(isolate); @@ -274,16 +274,16 @@ if (js_has_overflowed) { return ThrowStackOverflow(isolate, call_origin); } else if (check.InterruptRequested()) { const bool was_one_byte = String::IsOneByteRepresentationUnderneath(*subject_string_out); - Object result; + Tagged result; { AllowGarbageCollection yes_gc; result = isolate->stack_guard()->HandleInterrupts(); } - if (result.IsException(isolate)) { + if (IsException(result, isolate)) { return IrregexpInterpreter::EXCEPTION; } // If we changed between a LATIN1 and a UC16 string, we need to // restart regexp matching with the appropriate template instantiation of @@ -373,14 +373,14 @@ #define BYTECODE(name) BC_LABEL(name) #endif // DEBUG template IrregexpInterpreter::Result RawMatch( - Isolate* isolate, ByteArray code_array, String subject_string, - base::Vector subject, int* output_registers, - int output_register_count, int total_register_count, int current, - uint32_t current_char, RegExp::CallOrigin call_origin, + Isolate* isolate, Tagged code_array, + Tagged subject_string, base::Vector subject, + int* output_registers, int output_register_count, int total_register_count, + int current, uint32_t current_char, RegExp::CallOrigin call_origin, const uint32_t backtrack_limit) { DisallowGarbageCollection no_gc; #if V8_USE_COMPUTED_GOTO @@ -428,11 +428,11 @@ #undef DECLARE_DISPATCH_TABLE_ENTRY #undef BYTECODE_FILLER_ITERATOR #endif // V8_USE_COMPUTED_GOTO - const uint8_t* pc = code_array.GetDataStartAddress(); + const uint8_t* pc = code_array->begin(); const uint8_t* code_base = pc; InterpreterRegisters registers(total_register_count, output_registers, output_register_count); BacktrackStack backtrack_stack; @@ -700,32 +700,32 @@ } DISPATCH(); } BYTECODE(MINUS_AND_CHECK_NOT_CHAR) { uint32_t c = LoadPacked24Unsigned(insn); - uint32_t minus = Load16Aligned(pc + 4); - uint32_t mask = Load16Aligned(pc + 6); + uint32_t minus = Load16AlignedUnsigned(pc + 4); + uint32_t mask = Load16AlignedUnsigned(pc + 6); if (c != ((current_char - minus) & mask)) { SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); } else { ADVANCE(MINUS_AND_CHECK_NOT_CHAR); } DISPATCH(); } BYTECODE(CHECK_CHAR_IN_RANGE) { - uint32_t from = Load16Aligned(pc + 4); - uint32_t to = Load16Aligned(pc + 6); + uint32_t from = Load16AlignedUnsigned(pc + 4); + uint32_t to = Load16AlignedUnsigned(pc + 6); if (from <= current_char && current_char <= to) { SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); } else { ADVANCE(CHECK_CHAR_IN_RANGE); } DISPATCH(); } BYTECODE(CHECK_CHAR_NOT_IN_RANGE) { - uint32_t from = Load16Aligned(pc + 4); - uint32_t to = Load16Aligned(pc + 6); + uint32_t from = Load16AlignedUnsigned(pc + 4); + uint32_t to = Load16AlignedUnsigned(pc + 6); if (from > current_char || current_char > to) { SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); } else { ADVANCE(CHECK_CHAR_NOT_IN_RANGE); } @@ -912,11 +912,11 @@ DISPATCH(); } BYTECODE(SKIP_UNTIL_CHAR) { int32_t load_offset = LoadPacked24Signed(insn); int32_t advance = Load16AlignedSigned(pc + 4); - uint32_t c = Load16Aligned(pc + 6); + uint32_t c = Load16AlignedUnsigned(pc + 6); while (IndexIsInBounds(current + load_offset, subject.length())) { current_char = subject[current + load_offset]; if (c == current_char) { SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); DISPATCH(); @@ -927,11 +927,11 @@ DISPATCH(); } BYTECODE(SKIP_UNTIL_CHAR_AND) { int32_t load_offset = LoadPacked24Signed(insn); int32_t advance = Load16AlignedSigned(pc + 4); - uint16_t c = Load16Aligned(pc + 6); + uint16_t c = Load16AlignedUnsigned(pc + 6); uint32_t mask = Load32Aligned(pc + 8); int32_t maximum_offset = Load32Aligned(pc + 12); while (static_cast(current + maximum_offset) <= static_cast(subject.length())) { current_char = subject[current + load_offset]; @@ -945,11 +945,11 @@ DISPATCH(); } BYTECODE(SKIP_UNTIL_CHAR_POS_CHECKED) { int32_t load_offset = LoadPacked24Signed(insn); int32_t advance = Load16AlignedSigned(pc + 4); - uint16_t c = Load16Aligned(pc + 6); + uint16_t c = Load16AlignedUnsigned(pc + 6); int32_t maximum_offset = Load32Aligned(pc + 8); while (static_cast(current + maximum_offset) <= static_cast(subject.length())) { current_char = subject[current + load_offset]; if (c == current_char) { @@ -977,11 +977,11 @@ DISPATCH(); } BYTECODE(SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE) { int32_t load_offset = LoadPacked24Signed(insn); int32_t advance = Load16AlignedSigned(pc + 4); - uint16_t limit = Load16Aligned(pc + 6); + uint16_t limit = Load16AlignedUnsigned(pc + 6); const uint8_t* table = pc + 8; while (IndexIsInBounds(current + load_offset, subject.length())) { current_char = subject[current + load_offset]; if (current_char > limit) { SET_PC_FROM_OFFSET(Load32Aligned(pc + 24)); @@ -997,12 +997,12 @@ DISPATCH(); } BYTECODE(SKIP_UNTIL_CHAR_OR_CHAR) { int32_t load_offset = LoadPacked24Signed(insn); int32_t advance = Load32Aligned(pc + 4); - uint16_t c = Load16Aligned(pc + 8); - uint16_t c2 = Load16Aligned(pc + 10); + uint16_t c = Load16AlignedUnsigned(pc + 8); + uint16_t c2 = Load16AlignedUnsigned(pc + 10); while (IndexIsInBounds(current + load_offset, subject.length())) { current_char = subject[current + load_offset]; // The two if-statements below are split up intentionally, as combining // them seems to result in register allocation behaving quite // differently and slowing down the resulting code. @@ -1045,44 +1045,44 @@ } // namespace // static IrregexpInterpreter::Result IrregexpInterpreter::Match( - Isolate* isolate, JSRegExp regexp, String subject_string, + Isolate* isolate, Tagged regexp, Tagged subject_string, int* output_registers, int output_register_count, int start_position, RegExp::CallOrigin call_origin) { - if (v8_flags.regexp_tier_up) regexp.TierUpTick(); + if (v8_flags.regexp_tier_up) regexp->TierUpTick(); bool is_one_byte = String::IsOneByteRepresentationUnderneath(subject_string); - ByteArray code_array = ByteArray::cast(regexp.bytecode(is_one_byte)); - int total_register_count = regexp.max_register_count(); + Tagged code_array = ByteArray::cast(regexp->bytecode(is_one_byte)); + int total_register_count = regexp->max_register_count(); return MatchInternal(isolate, code_array, subject_string, output_registers, output_register_count, total_register_count, - start_position, call_origin, regexp.backtrack_limit()); + start_position, call_origin, regexp->backtrack_limit()); } IrregexpInterpreter::Result IrregexpInterpreter::MatchInternal( - Isolate* isolate, ByteArray code_array, String subject_string, - int* output_registers, int output_register_count, int total_register_count, - int start_position, RegExp::CallOrigin call_origin, - uint32_t backtrack_limit) { - DCHECK(subject_string.IsFlat()); + Isolate* isolate, Tagged code_array, + Tagged subject_string, int* output_registers, + int output_register_count, int total_register_count, int start_position, + RegExp::CallOrigin call_origin, uint32_t backtrack_limit) { + DCHECK(subject_string->IsFlat()); // TODO(chromium:1262676): Remove this CHECK once fixed. - CHECK(code_array.IsByteArray()); + CHECK(IsByteArray(code_array)); // Note: Heap allocation *is* allowed in two situations if calling from // Runtime: // 1. When creating & throwing a stack overflow exception. The interpreter // aborts afterwards, and thus possible-moved objects are never used. // 2. When handling interrupts. We manually relocate unhandlified references // after interrupts have run. DisallowGarbageCollection no_gc; base::uc16 previous_char = '\n'; - String::FlatContent subject_content = subject_string.GetFlatContent(no_gc); + String::FlatContent subject_content = subject_string->GetFlatContent(no_gc); // Because interrupts can result in GC and string content relocation, the // checksum verification in FlatContent may fail even though this code is // safe. See (2) above. subject_content.UnsafeDisableChecksumVerification(); if (subject_content.IsOneByte()) { @@ -1120,14 +1120,14 @@ DisallowGarbageCollection no_gc; DisallowJavascriptExecution no_js(isolate); DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; - String subject_string = String::cast(Object(subject)); - JSRegExp regexp_obj = JSRegExp::cast(Object(regexp)); + Tagged subject_string = String::cast(Tagged(subject)); + Tagged regexp_obj = JSRegExp::cast(Tagged(regexp)); - if (regexp_obj.MarkedForTierUp()) { + if (regexp_obj->MarkedForTierUp()) { // Returning RETRY will re-enter through runtime, where actual recompilation // for tier-up takes place. return IrregexpInterpreter::RETRY; } diff --git a/js/src/irregexp/imported/regexp-macro-assembler.h b/js/src/irregexp/imported/regexp-macro-assembler.h --- a/js/src/irregexp/imported/regexp-macro-assembler.h +++ b/js/src/irregexp/imported/regexp-macro-assembler.h @@ -299,16 +299,14 @@ // Returns a {Result} sentinel, or the number of successful matches. static int Match(Handle regexp, Handle subject, int* offsets_vector, int offsets_vector_length, int previous_index, Isolate* isolate); - V8_EXPORT_PRIVATE static int ExecuteForTesting(String input, int start_offset, - const uint8_t* input_start, - const uint8_t* input_end, - int* output, int output_size, - Isolate* isolate, - JSRegExp regexp); + V8_EXPORT_PRIVATE static int ExecuteForTesting( + Tagged input, int start_offset, const uint8_t* input_start, + const uint8_t* input_end, int* output, int output_size, Isolate* isolate, + Tagged regexp); bool CanReadUnaligned() const override; void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, bool check_bounds, int characters, @@ -328,13 +326,13 @@ // Called from generated code. static int CheckStackGuardState(Isolate* isolate, int start_index, RegExp::CallOrigin call_origin, Address* return_address, - InstructionStream re_code, Address* subject, - const uint8_t** input_start, - const uint8_t** input_end); + Tagged re_code, + Address* subject, const uint8_t** input_start, + const uint8_t** input_end, uintptr_t gap); static Address word_character_map_address() { return reinterpret_cast
(&word_character_map[0]); } @@ -346,13 +344,14 @@ Handle GetOrAddRangeArray(const ZoneList* ranges); private: // Returns a {Result} sentinel, or the number of successful matches. - static int Execute(String input, int start_offset, const uint8_t* input_start, - const uint8_t* input_end, int* output, int output_size, - Isolate* isolate, JSRegExp regexp); + static int Execute(Tagged input, int start_offset, + const uint8_t* input_start, const uint8_t* input_end, + int* output, int output_size, Isolate* isolate, + Tagged regexp); ZoneUnorderedMap> range_array_cache_; }; } // namespace internal diff --git a/js/src/irregexp/imported/regexp-macro-assembler.cc b/js/src/irregexp/imported/regexp-macro-assembler.cc --- a/js/src/irregexp/imported/regexp-macro-assembler.cc +++ b/js/src/irregexp/imported/regexp-macro-assembler.cc @@ -180,39 +180,40 @@ // Use uint32_t to avoid complexity around bool return types (which may be // optimized to use only the least significant byte). static constexpr uint32_t kTrue = 1; static constexpr uint32_t kFalse = 0; - FixedUInt16Array ranges = FixedUInt16Array::cast(Object(raw_byte_array)); - DCHECK_GE(ranges.length(), 1); + Tagged ranges = + FixedUInt16Array::cast(Tagged(raw_byte_array)); + DCHECK_GE(ranges->length(), 1); // Shortcut for fully out of range chars. - if (current_char < ranges.get(0)) return kFalse; - if (current_char >= ranges.get(ranges.length() - 1)) { + if (current_char < ranges->get(0)) return kFalse; + if (current_char >= ranges->get(ranges->length() - 1)) { // The last range may be open-ended. - return (ranges.length() % 2) == 0 ? kFalse : kTrue; + return (ranges->length() % 2) == 0 ? kFalse : kTrue; } // Binary search for the matching range. `ranges` is encoded as // [from0, to0, from1, to1, ..., fromN, toN], or // [from0, to0, from1, to1, ..., fromN] (open-ended last interval). - int mid, lower = 0, upper = ranges.length(); + int mid, lower = 0, upper = ranges->length(); do { mid = lower + (upper - lower) / 2; - const base::uc16 elem = ranges.get(mid); + const base::uc16 elem = ranges->get(mid); if (current_char < elem) { upper = mid; } else if (current_char > elem) { lower = mid + 1; } else { DCHECK_EQ(current_char, elem); break; } } while (lower < upper); - const bool current_char_ge_last_elem = current_char >= ranges.get(mid); + const bool current_char_ge_last_elem = current_char >= ranges->get(mid); const int current_range_start_index = current_char_ge_last_elem ? mid : mid - 1; // Ranges start at even indices and end at odd indices. return (current_range_start_index % 2) == 0 ? kTrue : kFalse; @@ -275,19 +276,20 @@ // This method may only be called after an interrupt. // static int NativeRegExpMacroAssembler::CheckStackGuardState( Isolate* isolate, int start_index, RegExp::CallOrigin call_origin, - Address* return_address, InstructionStream re_code, Address* subject, - const uint8_t** input_start, const uint8_t** input_end) { + Address* return_address, Tagged re_code, + Address* subject, const uint8_t** input_start, const uint8_t** input_end, + uintptr_t gap) { DisallowGarbageCollection no_gc; Address old_pc = PointerAuthentication::AuthenticatePC(return_address, 0); - DCHECK_LE(re_code.instruction_start(), old_pc); - DCHECK_LE(old_pc, re_code.code(kAcquireLoad).instruction_end()); + DCHECK_LE(re_code->instruction_start(), old_pc); + DCHECK_LE(old_pc, re_code->code(kAcquireLoad)->instruction_end()); StackLimitCheck check(isolate); - bool js_has_overflowed = check.JsHasOverflowed(); + bool js_has_overflowed = check.JsHasOverflowed(gap); if (call_origin == RegExp::CallOrigin::kFromJs) { // Direct calls from JavaScript can be interrupted in two ways: // 1. A real stack overflow, in which case we let the caller throw the // exception. @@ -308,11 +310,12 @@ DCHECK(call_origin == RegExp::CallOrigin::kFromRuntime); // Prepare for possible GC. HandleScope handles(isolate); Handle code_handle(re_code, isolate); - Handle subject_handle(String::cast(Object(*subject)), isolate); + Handle subject_handle(String::cast(Tagged(*subject)), + isolate); bool is_one_byte = String::IsOneByteRepresentationUnderneath(*subject_handle); int return_value = 0; { DisableGCMole no_gc_mole; @@ -320,12 +323,12 @@ AllowGarbageCollection yes_gc; isolate->StackOverflow(); return_value = EXCEPTION; } else if (check.InterruptRequested()) { AllowGarbageCollection yes_gc; - Object result = isolate->stack_guard()->HandleInterrupts(); - if (result.IsException(isolate)) return_value = EXCEPTION; + Tagged result = isolate->stack_guard()->HandleInterrupts(); + if (IsException(result, isolate)) return_value = EXCEPTION; } // We are not using operator == here because it does a slow DCHECK // CheckObjectComparisonAllowed() which might crash when trying to access // the page header of the stale pointer. @@ -369,65 +372,66 @@ // No allocations before calling the regexp, but we can't use // DisallowGarbageCollection, since regexps might be preempted, and another // thread might do allocation anyway. - String subject_ptr = *subject; + Tagged subject_ptr = *subject; // Character offsets into string. int start_offset = previous_index; - int char_length = subject_ptr.length() - start_offset; + int char_length = subject_ptr->length() - start_offset; int slice_offset = 0; // The string has been flattened, so if it is a cons string it contains the // full string in the first part. if (StringShape(subject_ptr).IsCons()) { - DCHECK_EQ(0, ConsString::cast(subject_ptr).second().length()); - subject_ptr = ConsString::cast(subject_ptr).first(); + DCHECK_EQ(0, ConsString::cast(subject_ptr)->second()->length()); + subject_ptr = ConsString::cast(subject_ptr)->first(); } else if (StringShape(subject_ptr).IsSliced()) { - SlicedString slice = SlicedString::cast(subject_ptr); - subject_ptr = slice.parent(); - slice_offset = slice.offset(); + Tagged slice = SlicedString::cast(subject_ptr); + subject_ptr = slice->parent(); + slice_offset = slice->offset(); } if (StringShape(subject_ptr).IsThin()) { - subject_ptr = ThinString::cast(subject_ptr).actual(); + subject_ptr = ThinString::cast(subject_ptr)->actual(); } // Ensure that an underlying string has the same representation. - bool is_one_byte = subject_ptr.IsOneByteRepresentation(); - DCHECK(subject_ptr.IsExternalString() || subject_ptr.IsSeqString()); + bool is_one_byte = subject_ptr->IsOneByteRepresentation(); + DCHECK(IsExternalString(subject_ptr) || IsSeqString(subject_ptr)); // String is now either Sequential or External int char_size_shift = is_one_byte ? 0 : 1; DisallowGarbageCollection no_gc; const uint8_t* input_start = - subject_ptr.AddressOfCharacterAt(start_offset + slice_offset, no_gc); + subject_ptr->AddressOfCharacterAt(start_offset + slice_offset, no_gc); int byte_length = char_length << char_size_shift; const uint8_t* input_end = input_start + byte_length; return Execute(*subject, start_offset, input_start, input_end, offsets_vector, offsets_vector_length, isolate, *regexp); } // static int NativeRegExpMacroAssembler::ExecuteForTesting( - String input, int start_offset, const uint8_t* input_start, + Tagged input, int start_offset, const uint8_t* input_start, const uint8_t* input_end, int* output, int output_size, Isolate* isolate, - JSRegExp regexp) { + Tagged regexp) { return Execute(input, start_offset, input_start, input_end, output, output_size, isolate, regexp); } // Returns a {Result} sentinel, or the number of successful matches. // TODO(pthier): The JSRegExp object is passed to native irregexp code to match // the signature of the interpreter. We should get rid of JS objects passed to // internal methods. int NativeRegExpMacroAssembler::Execute( - String input, // This needs to be the unpacked (sliced, cons) string. + Tagged + input, // This needs to be the unpacked (sliced, cons) string. int start_offset, const uint8_t* input_start, const uint8_t* input_end, - int* output, int output_size, Isolate* isolate, JSRegExp regexp) { + int* output, int output_size, Isolate* isolate, Tagged regexp) { RegExpStackScope stack_scope(isolate); bool is_one_byte = String::IsOneByteRepresentationUnderneath(input); - Code code = Code::cast(regexp.code(is_one_byte)); + Tagged code = Code::cast(regexp->code(is_one_byte)); RegExp::CallOrigin call_origin = RegExp::CallOrigin::kFromRuntime; using RegexpMatcherSig = // NOLINTNEXTLINE(readability/casting) int(Address input_string, int start_offset, const uint8_t* input_start, diff --git a/js/src/irregexp/imported/regexp-parser.cc b/js/src/irregexp/imported/regexp-parser.cc --- a/js/src/irregexp/imported/regexp-parser.cc +++ b/js/src/irregexp/imported/regexp-parser.cc @@ -65,12 +65,11 @@ bool NeedsDesugaringForIgnoreCase(base::uc32 c); void AddClassRangesForDesugaring(base::uc32 c); bool ignore_case() const { return IsIgnoreCase(flags_); } bool IsUnicodeMode() const { // Either /v or /u enable UnicodeMode - // TODO(v8:11935): Change permalink once proposal is in stage 4. - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#sec-parsepattern + // https://tc39.es/ecma262/#sec-parsepattern return IsUnicode(flags_) || IsUnicodeSets(flags_); } Zone* zone() const { return zone_; } Zone* const zone_; @@ -313,12 +312,11 @@ private: void FlushTerms(); bool IsUnicodeMode() const { // Either /v or /u enable UnicodeMode - // TODO(v8:11935): Change permalink once proposal is in stage 4. - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#sec-parsepattern + // https://tc39.es/ecma262/#sec-parsepattern return IsUnicode(flags_) || IsUnicodeSets(flags_); } Zone* zone() const { return zone_; } RegExpTextBuilder& text_builder() { return text_builder_; } @@ -461,21 +459,26 @@ RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, ClassSetOperandType* type_out); RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, ClassSetOperandType* type_out, ZoneList* ranges, - CharacterClassStrings* strings); + CharacterClassStrings* strings, + base::uc32* character); base::uc32 ParseClassSetCharacter(); // Parses and returns a single escaped character. base::uc32 ParseCharacterEscape(InClassEscapeState in_class_escape_state, bool* is_escaped_unicode_character); + void AddMaybeSimpleCaseFoldedRange(ZoneList* ranges, + CharacterRange new_range); + RegExpTree* ParseClassUnion(const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, ClassSetOperandType first_operand_type, ZoneList* ranges, - CharacterClassStrings* strings); + CharacterClassStrings* strings, + base::uc32 first_character); RegExpTree* ParseClassIntersection(const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, ClassSetOperandType first_operand_type); RegExpTree* ParseClassSubtraction(const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, @@ -505,12 +508,11 @@ int position() const { return next_pos_ - 1; } bool failed() const { return failed_; } RegExpFlags flags() const { return top_level_flags_; } bool IsUnicodeMode() const { // Either /v or /u enable UnicodeMode - // TODO(v8:11935): Change permalink once proposal is in stage 4. - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#sec-parsepattern + // https://tc39.es/ecma262/#sec-parsepattern return IsUnicode(flags()) || IsUnicodeSets(flags()) || force_unicode_; } bool unicode_sets() const { return IsUnicodeSets(flags()); } bool ignore_case() const { return IsIgnoreCase(flags()); } @@ -593,10 +595,11 @@ RegExpError error_ = RegExpError::kNone; int error_pos_ = 0; ZoneList* captures_; ZoneSet* named_captures_; ZoneList* named_back_references_; + ZoneList* temp_ranges_; const CharT* const input_; const int input_length_; base::uc32 current_; const RegExpFlags top_level_flags_; bool force_unicode_ = false; // Force parser to act as if unicode were set. @@ -1888,11 +1891,11 @@ if (success) { if (set.hasStrings()) { ExtractStringsFromUnicodeSet(set, result_strings, flags, zone); } const bool needs_case_folding = IsUnicodeSets(flags) && IsIgnoreCase(flags); - if (needs_case_folding) CharacterRange::UnicodeSimpleCloseOver(set); + if (needs_case_folding) set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); set.removeAllStrings(); if (negate) set.complement(); for (int i = 0; i < set.getRangeCount(); i++) { result_ranges->Add( CharacterRange::Range(set.getRangeStart(i), set.getRangeEnd(i)), @@ -2094,17 +2097,26 @@ // Then attempt to interpret as binary property name with value name 'Y'. UProperty property = u_getPropertyEnum(name); if (!IsSupportedBinaryProperty(property, unicode_sets())) return false; if (!IsExactPropertyAlias(name, property)) return false; // Negation of properties with strings is not allowed. - // TODO(v8:11935): Change permalink once proposal is in stage 4. // See - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#sec-static-semantics-maycontainstrings + // https://tc39.es/ecma262/#sec-static-semantics-maycontainstrings if (negate && IsBinaryPropertyOfStrings(property)) return false; - return LookupPropertyValueName(property, negate ? "N" : "Y", false, - add_to_ranges, add_to_strings, flags(), - zone()); + if (unicode_sets()) { + // In /v mode we can't simple lookup the "false" binary property values, + // as the spec requires us to perform case folding before calculating the + // complement. + // See https://tc39.es/ecma262/#sec-compiletocharset + // UnicodePropertyValueExpression :: LoneUnicodePropertyNameOrValue + return LookupPropertyValueName(property, "Y", negate, add_to_ranges, + add_to_strings, flags(), zone()); + } else { + return LookupPropertyValueName(property, negate ? "N" : "Y", false, + add_to_ranges, add_to_strings, flags(), + zone()); + } } 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(); @@ -2323,12 +2335,11 @@ return 0; } return c; } -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassRanges +// https://tc39.es/ecma262/#prod-ClassRanges template RegExpTree* RegExpParserImpl::ParseClassRanges( ZoneList* ranges, bool add_unicode_case_equivalents) { base::uc32 char_1, char_2; bool is_class_1, is_class_2; @@ -2473,12 +2484,11 @@ } } } // namespace -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassStringDisjunction +// https://tc39.es/ecma262/#prod-ClassStringDisjunction template RegExpTree* RegExpParserImpl::ParseClassStringDisjunction( ZoneList* ranges, CharacterClassStrings* strings) { DCHECK(unicode_sets()); DCHECK_EQ(current(), '\\'); @@ -2524,28 +2534,28 @@ // report an error. Advance(); return nullptr; } -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassSetOperand +// https://tc39.es/ecma262/#prod-ClassSetOperand // Tree returned based on type_out: // * kNestedClass: RegExpClassSetExpression // * For all other types: RegExpClassSetOperand template RegExpTree* RegExpParserImpl::ParseClassSetOperand( const RegExpBuilder* builder, ClassSetOperandType* type_out) { ZoneList* ranges = zone()->template New>(1, zone()); CharacterClassStrings* strings = zone()->template New(zone()); - RegExpTree* tree = - ParseClassSetOperand(builder, type_out, ranges, strings CHECK_FAILED); + base::uc32 character; + RegExpTree* tree = ParseClassSetOperand(builder, type_out, ranges, strings, + &character CHECK_FAILED); DCHECK_IMPLIES(*type_out != ClassSetOperandType::kNestedClass, tree == nullptr); DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, - ranges->length() == 1); + ranges->is_empty()); DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, strings->empty()); DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, ranges->is_empty()); DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, @@ -2556,25 +2566,31 @@ DCHECK_NE(*type_out, ClassSetOperandType::kClassSetRange); // There are no restrictions for kCharacterClassEscape. // CharacterClassEscape includes \p{}, which can contain ranges, strings or // both and \P{}, which could contain nothing (i.e. \P{Any}). if (tree == nullptr) { + if (*type_out == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Singleton(character)); + } tree = zone()->template New(ranges, strings); } return tree; } -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassSetOperand -// Based on |type_out| either a tree is returned or ranges/strings modified. -// If a tree is returned, ranges/strings are not modified. -// If |type_out| is kNestedClass, a tree of type RegExpClassSetExpression is -// returned. For all other types, ranges is modified and nullptr is returned. +// https://tc39.es/ecma262/#prod-ClassSetOperand +// Based on |type_out| either a tree is returned or +// |ranges|/|strings|/|character| modified. If a tree is returned, +// ranges/strings are not modified. If |type_out| is kNestedClass, a tree of +// type RegExpClassSetExpression is returned. If | type_out| is +// kClassSetCharacter, |character| is set and nullptr returned. For all other +// types, |ranges|/|strings|/|character| is modified and nullptr is returned. template RegExpTree* RegExpParserImpl::ParseClassSetOperand( const RegExpBuilder* builder, ClassSetOperandType* type_out, - ZoneList* ranges, CharacterClassStrings* strings) { + ZoneList* ranges, CharacterClassStrings* strings, + base::uc32* character) { DCHECK(unicode_sets()); base::uc32 c = current(); if (c == '\\') { const base::uc32 next = Next(); if (next == 'q') { @@ -2597,11 +2613,11 @@ return ParseCharacterClass(builder); } *type_out = ClassSetOperandType::kClassSetCharacter; c = ParseClassSetCharacter(CHECK_FAILED); - ranges->Add(CharacterRange::Singleton(c), zone()); + *character = c; return nullptr; } template base::uc32 RegExpParserImpl::ParseClassSetCharacter() { @@ -2651,17 +2667,32 @@ } } } // namespace -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassUnion +template +void RegExpParserImpl::AddMaybeSimpleCaseFoldedRange( + ZoneList* ranges, CharacterRange new_range) { + DCHECK(unicode_sets()); + if (ignore_case()) { + ZoneList* new_ranges = + zone()->template New>(2, zone()); + new_ranges->Add(new_range, zone()); + CharacterRange::AddUnicodeCaseEquivalents(new_ranges, zone()); + ranges->AddAll(*new_ranges, zone()); + } else { + ranges->Add(new_range, zone()); + } + CharacterRange::Canonicalize(ranges); +} + +// https://tc39.es/ecma262/#prod-ClassUnion template RegExpTree* RegExpParserImpl::ParseClassUnion( const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, ClassSetOperandType first_operand_type, ZoneList* ranges, - CharacterClassStrings* strings) { + CharacterClassStrings* strings, base::uc32 character) { DCHECK(unicode_sets()); ZoneList* operands = zone()->template New>(2, zone()); bool may_contain_strings = false; // Add the lhs to operands if necessary. @@ -2671,11 +2702,10 @@ if (first_operand != nullptr) { may_contain_strings = MayContainStrings(first_operand_type, first_operand); operands->Add(first_operand, zone()); } ClassSetOperandType last_type = first_operand_type; - const bool needs_case_folding = ignore_case(); while (has_more() && current() != ']') { if (current() == '-') { // Mix of ClassSetRange and ClassSubtraction is not allowed. if (Next() == '-') { return ReportError(RegExpError::kInvalidClassSetOperation); @@ -2688,46 +2718,40 @@ } // If the lhs and rhs around '-' are both ClassSetCharacters, they // represent a character range. // In case one of them is not a ClassSetCharacter, it is a syntax error, // as '-' can not be used unescaped within a class with /v. - // TODO(v8:11935): Change permalink once proposal is in stage 4. // See - // https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassSetRange + // https://tc39.es/ecma262/#prod-ClassSetRange if (last_type != ClassSetOperandType::kClassSetCharacter) { return ReportError(RegExpError::kInvalidCharacterClass); } - ParseClassSetOperand(builder, &last_type, ranges, strings CHECK_FAILED); + base::uc32 from = character; + ParseClassSetOperand(builder, &last_type, ranges, strings, + &character CHECK_FAILED); if (last_type != ClassSetOperandType::kClassSetCharacter) { return ReportError(RegExpError::kInvalidCharacterClass); } - // Remove the last two singleton characters added to ranges, and combine - // them into a range. - auto rhs_ranges = ranges->RemoveLast(); - auto lhs_ranges = ranges->RemoveLast(); - DCHECK(lhs_ranges.IsSingleton()); - DCHECK(rhs_ranges.IsSingleton()); - base::uc32 from = lhs_ranges.from(); - base::uc32 to = rhs_ranges.from(); - if (from > to) { + if (from > character) { return ReportError(RegExpError::kOutOfOrderCharacterClass); } - ranges->Add(CharacterRange::Range(from, to), zone()); + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Range(from, character)); last_type = ClassSetOperandType::kClassSetRange; } else { DCHECK_NE(current(), '-'); - RegExpTree* operand = ParseClassSetOperand(builder, &last_type, ranges, - strings CHECK_FAILED); + if (last_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, + CharacterRange::Singleton(character)); + } + RegExpTree* operand = ParseClassSetOperand( + builder, &last_type, ranges, strings, &character CHECK_FAILED); if (operand != nullptr) { may_contain_strings |= MayContainStrings(last_type, operand); // Add the range we started building as operand and reset the current // range. if (!ranges->is_empty() || !strings->empty()) { - if (needs_case_folding) { - CharacterRange::Canonicalize(ranges); - CharacterRange::AddUnicodeCaseEquivalents(ranges, zone()); - } may_contain_strings |= !strings->empty(); operands->Add( zone()->template New(ranges, strings), zone()); ranges = zone()->template New>(2, zone()); @@ -2740,16 +2764,16 @@ if (!has_more()) { return ReportError(RegExpError::kUnterminatedCharacterClass); } + if (last_type == ClassSetOperandType::kClassSetCharacter) { + AddMaybeSimpleCaseFoldedRange(ranges, CharacterRange::Singleton(character)); + } + // Add the range we started building as operand. if (!ranges->is_empty() || !strings->empty()) { - if (needs_case_folding) { - CharacterRange::Canonicalize(ranges); - CharacterRange::AddUnicodeCaseEquivalents(ranges, zone()); - } may_contain_strings |= !strings->empty(); operands->Add(zone()->template New(ranges, strings), zone()); } @@ -2771,12 +2795,11 @@ return zone()->template New( RegExpClassSetExpression::OperationType::kUnion, is_negated, may_contain_strings, operands); } -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassIntersection +// https://tc39.es/ecma262/#prod-ClassIntersection template RegExpTree* RegExpParserImpl::ParseClassIntersection( const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, ClassSetOperandType first_operand_type) { DCHECK(unicode_sets()); @@ -2813,12 +2836,11 @@ return zone()->template New( RegExpClassSetExpression::OperationType::kIntersection, is_negated, may_contain_strings, operands); } -// TODO(v8:11935): Change permalink once proposal is in stage 4. -// https://arai-a.github.io/ecma262-compare/snapshot.html?pr=2418#prod-ClassSubtraction +// https://tc39.es/ecma262/#prod-ClassSubtraction template RegExpTree* RegExpParserImpl::ParseClassSubtraction( const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, ClassSetOperandType first_operand_type) { DCHECK(unicode_sets()); @@ -2889,16 +2911,20 @@ character_class_flags); } else { ClassSetOperandType operand_type; CharacterClassStrings* strings = zone()->template New(zone()); - RegExpTree* operand = ParseClassSetOperand(builder, &operand_type, ranges, - strings CHECK_FAILED); + base::uc32 character; + RegExpTree* operand = ParseClassSetOperand( + builder, &operand_type, ranges, strings, &character CHECK_FAILED); switch (current()) { case '-': if (Next() == '-') { if (operand == nullptr) { + if (operand_type == ClassSetOperandType::kClassSetCharacter) { + ranges->Add(CharacterRange::Singleton(character), zone()); + } operand = zone()->template New(ranges, strings); } return ParseClassSubtraction(builder, is_negated, operand, operand_type); @@ -2906,19 +2932,22 @@ // ClassSetRange is handled in ParseClassUnion(). break; case '&': if (Next() == '&') { if (operand == nullptr) { + if (operand_type == ClassSetOperandType::kClassSetCharacter) { + ranges->Add(CharacterRange::Singleton(character), zone()); + } operand = zone()->template New(ranges, strings); } return ParseClassIntersection(builder, is_negated, operand, operand_type); } } return ParseClassUnion(builder, is_negated, operand, operand_type, ranges, - strings); + strings, character); } } #undef CHECK_FAILED diff --git a/js/src/irregexp/imported/regexp.h b/js/src/irregexp/imported/regexp.h --- a/js/src/irregexp/imported/regexp.h +++ b/js/src/irregexp/imported/regexp.h @@ -209,18 +209,20 @@ public: enum ResultsCacheType { REGEXP_MULTIPLE_INDICES, STRING_SPLIT_SUBSTRINGS }; // Attempt to retrieve a cached result. On failure, 0 is returned as a Smi. // On success, the returned result is guaranteed to be a COW-array. - static Object Lookup(Heap* heap, String key_string, Object key_pattern, - FixedArray* last_match_out, ResultsCacheType type); + static Tagged Lookup(Heap* heap, Tagged key_string, + Tagged key_pattern, + Tagged* last_match_out, + ResultsCacheType type); // Attempt to add value_array to the cache specified by type. On success, // value_array is turned into a COW-array. static void Enter(Isolate* isolate, Handle key_string, Handle key_pattern, Handle value_array, Handle last_match_cache, ResultsCacheType type); - static void Clear(FixedArray cache); + static void Clear(Tagged cache); static constexpr int kRegExpResultsCacheSize = 0x100; private: static constexpr int kStringOffset = 0; diff --git a/js/src/irregexp/imported/special-case.h b/js/src/irregexp/imported/special-case.h --- a/js/src/irregexp/imported/special-case.h +++ b/js/src/irregexp/imported/special-case.h @@ -68,25 +68,15 @@ // result of closeOver for each BMP character, and finding sets for // which at least one character has a different canonical value than // another character. Characters that match no other characters in // their equivalence class are added to IgnoreSet. Characters that // match at least one other character are added to SpecialAddSet. -// -// For unicode ignoreCase ("iu" and "iv"), -// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) adds all characters that are in -// the same equivalence class. This includes characaters that are in the same -// equivalence class using full case folding. According to the spec, only -// simple case folding shall be considered. We therefore create -// UnicodeNonSimpleCloseOverSet containing all characters for which -// UnicodeSet::closeOver adds characters that are not simple case folds. This -// set should be used similar to IgnoreSet described above. class RegExpCaseFolding final : public AllStatic { public: static const icu::UnicodeSet& IgnoreSet(); static const icu::UnicodeSet& SpecialAddSet(); - static const icu::UnicodeSet& UnicodeNonSimpleCloseOverSet(); // This implements ECMAScript 2020 21.2.2.8.2 (Runtime Semantics: // Canonicalize) step 3, which is used to determine whether // characters match when ignoreCase is true and unicode is false. static UChar32 Canonicalize(UChar32 ch) { diff --git a/js/src/irregexp/moz.yaml b/js/src/irregexp/moz.yaml --- a/js/src/irregexp/moz.yaml +++ b/js/src/irregexp/moz.yaml @@ -7,12 +7,12 @@ origin: name: irregexp description: A fast regular expression engine from V8 url: https://v8.dev - release: 30a887aeb92153885619d8bb9fa57cda7adf9276 (Thu Jul 06 11:42:30 2023). - revision: 30a887aeb92153885619d8bb9fa57cda7adf9276 + release: fc1e0b90c3dae288a78d35280f92a6b04a7af7e6 (Mon Nov 27 17:11:44 2023). + revision: fc1e0b90c3dae288a78d35280f92a6b04a7af7e6 license: BSD-3-Clause license-file: LICENSE.v8 vendoring: